@toclocoinc/lattice-grid 1.101.2 → 1.101.4

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 (408) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/angular/package.json +1 -1
  4. package/docs/API.html +115 -17
  5. package/docs/THEMING.md +2 -1
  6. package/docs/api/adapters.html +1 -1
  7. package/docs/api/ai.html +1 -1
  8. package/docs/api/alarms.html +1 -1
  9. package/docs/api/calendar.html +1 -1
  10. package/docs/api/charts.html +1 -1
  11. package/docs/api/dashboard.html +1 -1
  12. package/docs/api/data-router.html +1 -1
  13. package/docs/api/deckgl.html +1 -1
  14. package/docs/api/designer.html +1 -1
  15. package/docs/api/form.html +114 -14
  16. package/docs/api/gantt.html +1 -1
  17. package/docs/api/geometry.html +1 -1
  18. package/docs/api/grid.html +2 -2
  19. package/docs/api/index.html +2 -2
  20. package/docs/api/kanban.html +1 -1
  21. package/docs/api/kpi.html +1 -1
  22. package/docs/api/layout.html +1 -1
  23. package/docs/api/leaflet.html +1 -1
  24. package/docs/api/list.html +1 -1
  25. package/docs/api/mapview.html +1 -1
  26. package/docs/api/mock-socket.html +1 -1
  27. package/docs/api/presets.html +1 -1
  28. package/docs/api/scheduler.html +1 -1
  29. package/docs/api/shell.html +1 -1
  30. package/docs/api/tabs.html +1 -1
  31. package/docs/api/types.html +1 -1
  32. package/docs/api-detail.html +9 -6
  33. package/docs/warnings.json +1 -1
  34. package/lattice-grid.d.ts +1 -1
  35. package/lattice-grid.esm.min.js +2 -2
  36. package/lattice-grid.min.cjs +2 -2
  37. package/lattice-grid.min.js +2 -2
  38. package/locales/all.d.ts +1 -1
  39. package/locales/all.esm.min.js +1 -1
  40. package/locales/all.min.cjs +1 -1
  41. package/locales/all.min.js +1 -1
  42. package/locales/ar.d.ts +1 -1
  43. package/locales/ar.esm.min.js +1 -1
  44. package/locales/ar.min.cjs +1 -1
  45. package/locales/ar.min.js +1 -1
  46. package/locales/cs-CZ.d.ts +1 -1
  47. package/locales/cs-CZ.esm.min.js +1 -1
  48. package/locales/cs-CZ.min.cjs +1 -1
  49. package/locales/cs-CZ.min.js +1 -1
  50. package/locales/da-DK.d.ts +1 -1
  51. package/locales/da-DK.esm.min.js +1 -1
  52. package/locales/da-DK.min.cjs +1 -1
  53. package/locales/da-DK.min.js +1 -1
  54. package/locales/de-DE.d.ts +1 -1
  55. package/locales/de-DE.esm.min.js +1 -1
  56. package/locales/de-DE.min.cjs +1 -1
  57. package/locales/de-DE.min.js +1 -1
  58. package/locales/el-GR.d.ts +1 -1
  59. package/locales/el-GR.esm.min.js +1 -1
  60. package/locales/el-GR.min.cjs +1 -1
  61. package/locales/el-GR.min.js +1 -1
  62. package/locales/en-US.d.ts +1 -1
  63. package/locales/en-US.esm.min.js +1 -1
  64. package/locales/en-US.min.cjs +1 -1
  65. package/locales/en-US.min.js +1 -1
  66. package/locales/es-ES.d.ts +1 -1
  67. package/locales/es-ES.esm.min.js +1 -1
  68. package/locales/es-ES.min.cjs +1 -1
  69. package/locales/es-ES.min.js +1 -1
  70. package/locales/fi-FI.d.ts +1 -1
  71. package/locales/fi-FI.esm.min.js +1 -1
  72. package/locales/fi-FI.min.cjs +1 -1
  73. package/locales/fi-FI.min.js +1 -1
  74. package/locales/fr-CA.d.ts +1 -1
  75. package/locales/fr-CA.esm.min.js +1 -1
  76. package/locales/fr-CA.min.cjs +1 -1
  77. package/locales/fr-CA.min.js +1 -1
  78. package/locales/fr-FR.d.ts +1 -1
  79. package/locales/fr-FR.esm.min.js +1 -1
  80. package/locales/fr-FR.min.cjs +1 -1
  81. package/locales/fr-FR.min.js +1 -1
  82. package/locales/hu-HU.d.ts +1 -1
  83. package/locales/hu-HU.esm.min.js +1 -1
  84. package/locales/hu-HU.min.cjs +1 -1
  85. package/locales/hu-HU.min.js +1 -1
  86. package/locales/it-IT.d.ts +1 -1
  87. package/locales/it-IT.esm.min.js +1 -1
  88. package/locales/it-IT.min.cjs +1 -1
  89. package/locales/it-IT.min.js +1 -1
  90. package/locales/ja-JP.d.ts +1 -1
  91. package/locales/ja-JP.esm.min.js +1 -1
  92. package/locales/ja-JP.min.cjs +1 -1
  93. package/locales/ja-JP.min.js +1 -1
  94. package/locales/nb-NO.d.ts +1 -1
  95. package/locales/nb-NO.esm.min.js +1 -1
  96. package/locales/nb-NO.min.cjs +1 -1
  97. package/locales/nb-NO.min.js +1 -1
  98. package/locales/nl-NL.d.ts +1 -1
  99. package/locales/nl-NL.esm.min.js +1 -1
  100. package/locales/nl-NL.min.cjs +1 -1
  101. package/locales/nl-NL.min.js +1 -1
  102. package/locales/pl-PL.d.ts +1 -1
  103. package/locales/pl-PL.esm.min.js +1 -1
  104. package/locales/pl-PL.min.cjs +1 -1
  105. package/locales/pl-PL.min.js +1 -1
  106. package/locales/pt-BR.d.ts +1 -1
  107. package/locales/pt-BR.esm.min.js +1 -1
  108. package/locales/pt-BR.min.cjs +1 -1
  109. package/locales/pt-BR.min.js +1 -1
  110. package/locales/ro-RO.d.ts +1 -1
  111. package/locales/ro-RO.esm.min.js +1 -1
  112. package/locales/ro-RO.min.cjs +1 -1
  113. package/locales/ro-RO.min.js +1 -1
  114. package/locales/sv-SE.d.ts +1 -1
  115. package/locales/sv-SE.esm.min.js +1 -1
  116. package/locales/sv-SE.min.cjs +1 -1
  117. package/locales/sv-SE.min.js +1 -1
  118. package/locales/uk-UA.d.ts +1 -1
  119. package/locales/uk-UA.esm.min.js +1 -1
  120. package/locales/uk-UA.min.cjs +1 -1
  121. package/locales/uk-UA.min.js +1 -1
  122. package/modules/ai.d.ts +1 -1
  123. package/modules/ai.esm.min.js +1 -1
  124. package/modules/ai.min.cjs +1 -1
  125. package/modules/ai.min.js +1 -1
  126. package/modules/alarms.d.ts +1 -1
  127. package/modules/alarms.esm.min.js +1 -1
  128. package/modules/alarms.min.cjs +1 -1
  129. package/modules/alarms.min.js +1 -1
  130. package/modules/angular.d.ts +1 -1
  131. package/modules/angular.esm.min.js +1 -1
  132. package/modules/angular.min.cjs +1 -1
  133. package/modules/angular.min.js +1 -1
  134. package/modules/calendar.d.ts +1 -1
  135. package/modules/calendar.esm.min.js +1 -1
  136. package/modules/calendar.min.cjs +1 -1
  137. package/modules/calendar.min.js +1 -1
  138. package/modules/chart-alluvial.d.ts +1 -1
  139. package/modules/chart-alluvial.esm.min.js +1 -1
  140. package/modules/chart-alluvial.min.cjs +1 -1
  141. package/modules/chart-alluvial.min.js +1 -1
  142. package/modules/chart-arc.d.ts +1 -1
  143. package/modules/chart-arc.esm.min.js +1 -1
  144. package/modules/chart-arc.min.cjs +1 -1
  145. package/modules/chart-arc.min.js +1 -1
  146. package/modules/chart-barrace.d.ts +1 -1
  147. package/modules/chart-barrace.esm.min.js +1 -1
  148. package/modules/chart-barrace.min.cjs +1 -1
  149. package/modules/chart-barrace.min.js +1 -1
  150. package/modules/chart-bubblemap.d.ts +1 -1
  151. package/modules/chart-bubblemap.esm.min.js +1 -1
  152. package/modules/chart-bubblemap.min.cjs +1 -1
  153. package/modules/chart-bubblemap.min.js +1 -1
  154. package/modules/chart-bump.d.ts +1 -1
  155. package/modules/chart-bump.esm.min.js +1 -1
  156. package/modules/chart-bump.min.cjs +1 -1
  157. package/modules/chart-bump.min.js +1 -1
  158. package/modules/chart-calendar.d.ts +1 -1
  159. package/modules/chart-calendar.esm.min.js +1 -1
  160. package/modules/chart-calendar.min.cjs +1 -1
  161. package/modules/chart-calendar.min.js +1 -1
  162. package/modules/chart-choropleth.d.ts +1 -1
  163. package/modules/chart-choropleth.esm.min.js +1 -1
  164. package/modules/chart-choropleth.min.cjs +1 -1
  165. package/modules/chart-choropleth.min.js +1 -1
  166. package/modules/chart-decomposition.d.ts +1 -1
  167. package/modules/chart-decomposition.esm.min.js +1 -1
  168. package/modules/chart-decomposition.min.cjs +1 -1
  169. package/modules/chart-decomposition.min.js +1 -1
  170. package/modules/chart-diverging.d.ts +1 -1
  171. package/modules/chart-diverging.esm.min.js +1 -1
  172. package/modules/chart-diverging.min.cjs +1 -1
  173. package/modules/chart-diverging.min.js +1 -1
  174. package/modules/chart-dumbbell.d.ts +1 -1
  175. package/modules/chart-dumbbell.esm.min.js +1 -1
  176. package/modules/chart-dumbbell.min.cjs +1 -1
  177. package/modules/chart-dumbbell.min.js +1 -1
  178. package/modules/chart-fan.d.ts +1 -1
  179. package/modules/chart-fan.esm.min.js +1 -1
  180. package/modules/chart-fan.min.cjs +1 -1
  181. package/modules/chart-fan.min.js +1 -1
  182. package/modules/chart-forcetree.d.ts +1 -1
  183. package/modules/chart-forcetree.esm.min.js +1 -1
  184. package/modules/chart-forcetree.min.cjs +1 -1
  185. package/modules/chart-forcetree.min.js +1 -1
  186. package/modules/chart-hexbin.d.ts +1 -1
  187. package/modules/chart-hexbin.esm.min.js +1 -1
  188. package/modules/chart-hexbin.min.cjs +1 -1
  189. package/modules/chart-hexbin.min.js +1 -1
  190. package/modules/chart-hexmap.d.ts +1 -1
  191. package/modules/chart-hexmap.esm.min.js +1 -1
  192. package/modules/chart-hexmap.min.cjs +1 -1
  193. package/modules/chart-hexmap.min.js +1 -1
  194. package/modules/chart-icicle.d.ts +1 -1
  195. package/modules/chart-icicle.esm.min.js +1 -1
  196. package/modules/chart-icicle.min.cjs +1 -1
  197. package/modules/chart-icicle.min.js +1 -1
  198. package/modules/chart-markermap.d.ts +1 -1
  199. package/modules/chart-markermap.esm.min.js +1 -1
  200. package/modules/chart-markermap.min.cjs +1 -1
  201. package/modules/chart-markermap.min.js +1 -1
  202. package/modules/chart-motion.d.ts +1 -1
  203. package/modules/chart-motion.esm.min.js +1 -1
  204. package/modules/chart-motion.min.cjs +1 -1
  205. package/modules/chart-motion.min.js +1 -1
  206. package/modules/chart-pack.d.ts +1 -1
  207. package/modules/chart-pack.esm.min.js +1 -1
  208. package/modules/chart-pack.min.cjs +1 -1
  209. package/modules/chart-pack.min.js +1 -1
  210. package/modules/chart-parallel.d.ts +1 -1
  211. package/modules/chart-parallel.esm.min.js +1 -1
  212. package/modules/chart-parallel.min.cjs +1 -1
  213. package/modules/chart-parallel.min.js +1 -1
  214. package/modules/chart-pictorial.d.ts +1 -1
  215. package/modules/chart-pictorial.esm.min.js +1 -1
  216. package/modules/chart-pictorial.min.cjs +1 -1
  217. package/modules/chart-pictorial.min.js +1 -1
  218. package/modules/chart-polararea.d.ts +1 -1
  219. package/modules/chart-polararea.esm.min.js +1 -1
  220. package/modules/chart-polararea.min.cjs +1 -1
  221. package/modules/chart-polararea.min.js +1 -1
  222. package/modules/chart-polarscatter.d.ts +1 -1
  223. package/modules/chart-polarscatter.esm.min.js +1 -1
  224. package/modules/chart-polarscatter.min.cjs +1 -1
  225. package/modules/chart-polarscatter.min.js +1 -1
  226. package/modules/chart-pyramid.d.ts +1 -1
  227. package/modules/chart-pyramid.esm.min.js +1 -1
  228. package/modules/chart-pyramid.min.cjs +1 -1
  229. package/modules/chart-pyramid.min.js +1 -1
  230. package/modules/chart-radialbar.d.ts +1 -1
  231. package/modules/chart-radialbar.esm.min.js +1 -1
  232. package/modules/chart-radialbar.min.cjs +1 -1
  233. package/modules/chart-radialbar.min.js +1 -1
  234. package/modules/chart-ridgeline.d.ts +1 -1
  235. package/modules/chart-ridgeline.esm.min.js +1 -1
  236. package/modules/chart-ridgeline.min.cjs +1 -1
  237. package/modules/chart-ridgeline.min.js +1 -1
  238. package/modules/chart-roc.d.ts +1 -1
  239. package/modules/chart-roc.esm.min.js +1 -1
  240. package/modules/chart-roc.min.cjs +1 -1
  241. package/modules/chart-roc.min.js +1 -1
  242. package/modules/chart-scatter3d.d.ts +1 -1
  243. package/modules/chart-scatter3d.esm.min.js +1 -1
  244. package/modules/chart-scatter3d.min.cjs +1 -1
  245. package/modules/chart-scatter3d.min.js +1 -1
  246. package/modules/chart-serpentine.d.ts +1 -1
  247. package/modules/chart-serpentine.esm.min.js +1 -1
  248. package/modules/chart-serpentine.min.cjs +1 -1
  249. package/modules/chart-serpentine.min.js +1 -1
  250. package/modules/chart-slope.d.ts +1 -1
  251. package/modules/chart-slope.esm.min.js +1 -1
  252. package/modules/chart-slope.min.cjs +1 -1
  253. package/modules/chart-slope.min.js +1 -1
  254. package/modules/chart-spiral.d.ts +1 -1
  255. package/modules/chart-spiral.esm.min.js +1 -1
  256. package/modules/chart-spiral.min.cjs +1 -1
  257. package/modules/chart-spiral.min.js +1 -1
  258. package/modules/chart-splom.d.ts +1 -1
  259. package/modules/chart-splom.esm.min.js +1 -1
  260. package/modules/chart-splom.min.cjs +1 -1
  261. package/modules/chart-splom.min.js +1 -1
  262. package/modules/chart-tree.d.ts +1 -1
  263. package/modules/chart-tree.esm.min.js +1 -1
  264. package/modules/chart-tree.min.cjs +1 -1
  265. package/modules/chart-tree.min.js +1 -1
  266. package/modules/chart-venn.d.ts +1 -1
  267. package/modules/chart-venn.esm.min.js +1 -1
  268. package/modules/chart-venn.min.cjs +1 -1
  269. package/modules/chart-venn.min.js +1 -1
  270. package/modules/chart-voronoi.d.ts +1 -1
  271. package/modules/chart-voronoi.esm.min.js +1 -1
  272. package/modules/chart-voronoi.min.cjs +1 -1
  273. package/modules/chart-voronoi.min.js +1 -1
  274. package/modules/chart-waffle.d.ts +1 -1
  275. package/modules/chart-waffle.esm.min.js +1 -1
  276. package/modules/chart-waffle.min.cjs +1 -1
  277. package/modules/chart-waffle.min.js +1 -1
  278. package/modules/chart-wordcloud.d.ts +1 -1
  279. package/modules/chart-wordcloud.esm.min.js +1 -1
  280. package/modules/chart-wordcloud.min.cjs +1 -1
  281. package/modules/chart-wordcloud.min.js +1 -1
  282. package/modules/charts.d.ts +1 -1
  283. package/modules/charts.esm.min.js +1 -1
  284. package/modules/charts.min.cjs +1 -1
  285. package/modules/charts.min.js +1 -1
  286. package/modules/dashboard.d.ts +1 -1
  287. package/modules/dashboard.esm.min.js +1 -1
  288. package/modules/dashboard.min.cjs +1 -1
  289. package/modules/dashboard.min.js +1 -1
  290. package/modules/data-router.d.ts +1 -1
  291. package/modules/data-router.esm.min.js +1 -1
  292. package/modules/data-router.min.cjs +1 -1
  293. package/modules/data-router.min.js +1 -1
  294. package/modules/deckgl.d.ts +1 -1
  295. package/modules/deckgl.esm.min.js +1 -1
  296. package/modules/deckgl.min.cjs +1 -1
  297. package/modules/deckgl.min.js +1 -1
  298. package/modules/designer.d.ts +1 -1
  299. package/modules/designer.esm.min.js +1 -1
  300. package/modules/designer.min.cjs +1 -1
  301. package/modules/designer.min.js +1 -1
  302. package/modules/devtools.d.ts +1 -1
  303. package/modules/devtools.esm.min.js +1 -1
  304. package/modules/devtools.min.cjs +1 -1
  305. package/modules/devtools.min.js +1 -1
  306. package/modules/dhtmlx-compat.d.ts +1 -1
  307. package/modules/dhtmlx-compat.esm.min.js +1 -1
  308. package/modules/dhtmlx-compat.min.cjs +1 -1
  309. package/modules/dhtmlx-compat.min.js +1 -1
  310. package/modules/form.d.ts +5 -3
  311. package/modules/form.esm.min.js +221 -196
  312. package/modules/form.esm.min.js.map +2 -2
  313. package/modules/form.min.cjs +221 -196
  314. package/modules/form.min.js +221 -196
  315. package/modules/form.min.js.map +2 -2
  316. package/modules/gantt.d.ts +1 -1
  317. package/modules/gantt.esm.min.js +1 -1
  318. package/modules/gantt.min.cjs +1 -1
  319. package/modules/gantt.min.js +1 -1
  320. package/modules/geo-europe-nuts.d.ts +1 -1
  321. package/modules/geo-europe-nuts.esm.min.js +1 -1
  322. package/modules/geo-europe-nuts.min.cjs +1 -1
  323. package/modules/geo-europe-nuts.min.js +1 -1
  324. package/modules/geo-uk.d.ts +1 -1
  325. package/modules/geo-uk.esm.min.js +1 -1
  326. package/modules/geo-uk.min.cjs +1 -1
  327. package/modules/geo-uk.min.js +1 -1
  328. package/modules/geo-us-states.d.ts +1 -1
  329. package/modules/geo-us-states.esm.min.js +1 -1
  330. package/modules/geo-us-states.min.cjs +1 -1
  331. package/modules/geo-us-states.min.js +1 -1
  332. package/modules/geo-world-110m.d.ts +1 -1
  333. package/modules/geo-world-110m.esm.min.js +1 -1
  334. package/modules/geo-world-110m.min.cjs +1 -1
  335. package/modules/geo-world-110m.min.js +1 -1
  336. package/modules/geo-world-50m.d.ts +1 -1
  337. package/modules/geo-world-50m.esm.min.js +1 -1
  338. package/modules/geo-world-50m.min.cjs +1 -1
  339. package/modules/geo-world-50m.min.js +1 -1
  340. package/modules/geometry.d.ts +1 -1
  341. package/modules/geometry.esm.min.js +1 -1
  342. package/modules/geometry.min.cjs +1 -1
  343. package/modules/geometry.min.js +1 -1
  344. package/modules/htmx.d.ts +1 -1
  345. package/modules/htmx.esm.min.js +2 -2
  346. package/modules/htmx.min.cjs +2 -2
  347. package/modules/htmx.min.js +2 -2
  348. package/modules/kanban.d.ts +1 -1
  349. package/modules/kanban.esm.min.js +1 -1
  350. package/modules/kanban.min.cjs +1 -1
  351. package/modules/kanban.min.js +1 -1
  352. package/modules/kpi.d.ts +1 -1
  353. package/modules/kpi.esm.min.js +1 -1
  354. package/modules/kpi.min.cjs +1 -1
  355. package/modules/kpi.min.js +1 -1
  356. package/modules/layout.d.ts +1 -1
  357. package/modules/layout.esm.min.js +1 -1
  358. package/modules/layout.min.cjs +1 -1
  359. package/modules/layout.min.js +1 -1
  360. package/modules/leaflet.d.ts +1 -1
  361. package/modules/leaflet.esm.min.js +1 -1
  362. package/modules/leaflet.min.cjs +1 -1
  363. package/modules/leaflet.min.js +1 -1
  364. package/modules/list.d.ts +1 -1
  365. package/modules/list.esm.min.js +1 -1
  366. package/modules/list.min.cjs +1 -1
  367. package/modules/list.min.js +1 -1
  368. package/modules/mapview.d.ts +1 -1
  369. package/modules/mapview.esm.min.js +1 -1
  370. package/modules/mapview.min.cjs +1 -1
  371. package/modules/mapview.min.js +1 -1
  372. package/modules/mock-socket.d.ts +1 -1
  373. package/modules/mock-socket.esm.min.js +1 -1
  374. package/modules/mock-socket.min.cjs +1 -1
  375. package/modules/mock-socket.min.js +1 -1
  376. package/modules/presets.d.ts +1 -1
  377. package/modules/presets.esm.min.js +1 -1
  378. package/modules/presets.min.cjs +1 -1
  379. package/modules/presets.min.js +1 -1
  380. package/modules/react.d.ts +1 -1
  381. package/modules/react.esm.min.js +1 -1
  382. package/modules/react.min.cjs +1 -1
  383. package/modules/react.min.js +1 -1
  384. package/modules/scheduler.d.ts +1 -1
  385. package/modules/scheduler.esm.min.js +1 -1
  386. package/modules/scheduler.min.cjs +1 -1
  387. package/modules/scheduler.min.js +1 -1
  388. package/modules/shell.d.ts +1 -1
  389. package/modules/shell.esm.min.js +1 -1
  390. package/modules/shell.min.cjs +1 -1
  391. package/modules/shell.min.js +1 -1
  392. package/modules/svelte.d.ts +1 -1
  393. package/modules/svelte.esm.min.js +1 -1
  394. package/modules/svelte.min.cjs +1 -1
  395. package/modules/svelte.min.js +1 -1
  396. package/modules/tabs.d.ts +1 -1
  397. package/modules/tabs.esm.min.js +1 -1
  398. package/modules/tabs.min.cjs +1 -1
  399. package/modules/tabs.min.js +1 -1
  400. package/modules/vue.d.ts +1 -1
  401. package/modules/vue.esm.min.js +1 -1
  402. package/modules/vue.min.cjs +1 -1
  403. package/modules/vue.min.js +1 -1
  404. package/modules/webcomponent.d.ts +1 -1
  405. package/modules/webcomponent.esm.min.js +2 -2
  406. package/modules/webcomponent.min.cjs +2 -2
  407. package/modules/webcomponent.min.js +2 -2
  408. package/package.json +1 -1
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Form API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Form API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -391,6 +391,7 @@
391
391
  <a class="rail__sub-link" href="#form-live-example">Values, a committed edit, a veto and density, executed</a>
392
392
  <a class="rail__sub-link" href="#form-spacing-example">Spacing and label gap, executed</a>
393
393
  <a class="rail__sub-link" href="#form-width-example">Percentage widths in a cols row, executed</a>
394
+ <a class="rail__sub-link" href="#form-cols-breakpoint-example">Cols breakpoint and full-width buttons, executed</a>
394
395
  <a class="rail__sub-link" href="#form-disabled-readonly-example">Disabled and read-only, executed</a>
395
396
  <a class="rail__sub-link" href="#form-label-options-example">Label position, width and hidden labels, executed</a>
396
397
  <a class="rail__sub-link" href="#form-chrome-example">Help tooltips, read-only locks, field addons and panels, executed</a>
@@ -400,6 +401,7 @@
400
401
  <a class="rail__sub-link" href="#form-bind-live-example">Binding, dirty tracking, save, revert and conflict, executed</a>
401
402
  <a class="rail__sub-link" href="#form-router-bind-live-example">Router-bound forms and a form group, executed</a>
402
403
  <a class="rail__sub-link" href="#form-wizard-live-example">A custom control, declarative rules and a wizard, executed</a>
404
+ <a class="rail__sub-link" href="#form-step-validation-example">Step validation, the validateSteps opt-out and the refused move, executed</a>
403
405
  <a class="rail__sub-link" href="#form-validation-example">Required, a regex, a function, modes and the validated event, executed</a>
404
406
  <a class="rail__sub-link" href="#form-enhance-live-example">A server form enhanced in place, with the htmx adapter, executed</a>
405
407
  <a class="rail__sub-link" href="#form-controls-example">Choice, toggle and date controls, executed</a>
@@ -493,7 +495,7 @@ form.getValue(); <span class="cmt">// { name: 'Grace', a
493
495
  form.on('beforeChange', (e) =&gt; { if (e.name === 'age' &amp;&amp; e.value &gt; 120) e.preventDefault(); });
494
496
  form.setDensity('comfortable'); <span class="cmt">// control heights follow the grid's row height</span>
495
497
  form.destroy();</code></pre>
496
- <p><strong>Rows, columns and blocks.</strong> A form is a tree of <code>rows</code> and <code>cols</code>, each nesting the other to any depth, plus <code>items</code> to lay controls side by side. A <code>rows</code> block stacks its children vertically and stretches them to its width; a <code>cols</code> block lays its children side by side, sharing the width equally by default (<code>flex: 1 1 0</code>, <code>min-width: 0</code> &mdash; a child&rsquo;s own <code>width</code>, <code>flex</code> or <code>css</code> still wins, and a percentage <code>width</code> is a share of the row&rsquo;s width <em>after</em> its gaps, so three <code>33.3%</code> items sit on one line) and stacking them into one column once the container drops below <code>config.colsBreakpoint</code> (default 480px, via a container query). A <code>container</code> is the default block shape (a flex box along its axis); a <code>fieldset</code> is a native <code>&lt;fieldset&gt;</code>/<code>&lt;legend&gt;</code> with its children in a body below; a <code>spacer</code> is flexible space and a <code>separator</code> a rule &mdash; or, given a <code>label</code> (a literal such as <code>'or'</code>, or a key resolved through <code>config.messages</code>), a muted label centred between two rules (&ldquo;&mdash;&mdash; or &mdash;&mdash;&rdquo;), the label also its accessible name &mdash; both plain blocks. A <code>text</code> item renders a read-only display carrying its value &mdash; a label/value pair, not an editable control &mdash; and the basic controls are <code>input</code> (subtype <code>text</code>, <code>email</code>, <code>password</code> or <code>url</code>), <code>number</code> (<code>step</code>/<code>min</code>/<code>max</code>), <code>textarea</code>, <code>checkbox</code>, <code>button</code>, <code>rating</code> (a row of icons, whole or half steps, click or arrow), <code>stage</code> (a pipeline of chevron steps), <code>chips</code> (a row of pill buttons for one or several options) and <code>combo</code> (a searchable combobox, described below).</p>
498
+ <p><strong>Rows, columns and blocks.</strong> A form is a tree of <code>rows</code> and <code>cols</code>, each nesting the other to any depth, plus <code>items</code> to lay controls side by side. A <code>rows</code> block stacks its children vertically and stretches them to its width; a <code>cols</code> block lays its children side by side, sharing the width equally by default (<code>flex: 1 1 0</code>, <code>min-width: 0</code> &mdash; a child&rsquo;s own <code>width</code>, <code>flex</code> or <code>css</code> still wins, and a percentage <code>width</code> is a share of the row&rsquo;s width <em>after</em> its gaps, so three <code>33.3%</code> items sit on one line) and stacking them into one column once the container drops below <code>config.colsBreakpoint</code> (default 480px, via a container query &mdash; <code>0</code> or <code>false</code> turns stacking off, so the row never stacks at any width). A <code>container</code> is the default block shape (a flex box along its axis); a <code>fieldset</code> is a native <code>&lt;fieldset&gt;</code>/<code>&lt;legend&gt;</code> with its children in a body below; a <code>spacer</code> is flexible space and a <code>separator</code> a rule &mdash; or, given a <code>label</code> (a literal such as <code>'or'</code>, or a key resolved through <code>config.messages</code>), a muted label centred between two rules (&ldquo;&mdash;&mdash; or &mdash;&mdash;&rdquo;), the label also its accessible name &mdash; both plain blocks. A <code>text</code> item renders a read-only display carrying its value &mdash; a label/value pair, not an editable control &mdash; and the basic controls are <code>input</code> (subtype <code>text</code>, <code>email</code>, <code>password</code> or <code>url</code>), <code>number</code> (<code>step</code>/<code>min</code>/<code>max</code>), <code>textarea</code>, <code>checkbox</code>, <code>button</code>, <code>rating</code> (a row of icons, whole or half steps, click or arrow), <code>stage</code> (a pipeline of chevron steps), <code>chips</code> (a row of pill buttons for one or several options) and <code>combo</code> (a searchable combobox, described below).</p>
497
499
  <p><strong>One item, one handle.</strong> Every item is a wrapper holding an optional <code>&lt;label&gt;</code> (associated to its control by <code>for</code>/<code>id</code>), the control, and an optional help line. Per item: <code>name</code>, <code>label</code>, <code>labelPosition</code> (<code>top</code>/<code>left</code>), <code>labelWidth</code>, <code>width</code>, <code>placeholder</code>, <code>helpMessage</code>, <code>hidden</code>, <code>disabled</code>, <code>readOnly</code> and <code>value</code>. <code>getItem(name)</code> returns the handle &mdash; <code>{ el, name, getValue, setValue, show, hide, enable, disable, focus, blur, isVisible }</code> &mdash; or <code>undefined</code> for an unknown name, and <code>forEach(fn)</code> hands every handle to <code>fn</code> in document order. <code>setValue(values)</code> writes values silently by name (unknown keys are ignored), <code>getValue()</code> snapshots every named value-holding item as <code>{ name: value }</code> (a button holds no value and is not included), and <code>clear()</code> resets each user-editable item to its empty value &mdash; <code>''</code> for text, <code>null</code> for a number, a stage or a single <code>chips</code>, <code>[]</code> for a multi <code>chips</code>, <code>false</code> for a checkbox, <code>0</code> for a rating &mdash; without firing an event.</p>
498
500
  <p><strong>Events.</strong> A field fires <code>input</code> as the user types (the value is live, not yet committed) and <code>change</code> once the edit commits; a checkbox fires <code>change</code> on toggle; a button fires <code>click</code>. Every interactive item fires <code>focus</code> and <code>blur</code>, and an <code>otp</code> fires <code>complete</code> once every box is filled. A <code>change</code> is gated by a cancellable <code>beforeChange</code> &mdash; a handler calls <code>event.preventDefault(reason?)</code> or returns <code>false</code> (or a Promise of either) to refuse the commit, which then reverts the control to its last committed value &mdash; and the emitted events are subscribed with <code>form.on(name, fn)</code> (or <code>'*'</code> for every past-tense event) and detached with <code>form.off(name, fn)</code>.</p>
499
501
  <p><strong>Validation.</strong> An item declares <code>required</code> (an empty value fails), a <code>validation</code> rule &mdash; a built-in name (<code>email</code>, <code>number</code>, <code>integer</code>, <code>url</code>), a whole-value RegExp, or a function <code>(value, item) =&gt; boolean | string | void</code> that returns <code>true</code>/<code>undefined</code> for pass, <code>false</code> for a fail, or a string that <em>is</em> the failure message &mdash; and the message options <code>errorMessage</code>, <code>successMessage</code> and <code>preMessage</code>. <code>form.validate()</code> checks every rule-bearing item and returns <code>true</code>/<code>false</code>, marking each invalid item with <code>aria-invalid</code> and an associated message (and each pass with its <code>successMessage</code>); <code>item.validate()</code> checks one item. A <code>validationMode</code> of <code>change</code> re-checks an item once its edit commits, <code>blur</code> re-checks it as it loses focus, and <code>submit</code> (the default) checks it only on <code>validate()</code>. The default rule messages live in the module's catalogue &mdash; overridable through <code>config.messages</code> &mdash; and a <code>validated</code> event reports every failure as <code>{ name, rule, message }</code>. The control registry exposes a per-control <code>validate(el, item, value)</code> hook that later controls (file, combo, custom) plug into.</p>
@@ -503,10 +505,10 @@ form.destroy();</code></pre>
503
505
  <p><strong>Data binding.</strong> <code>config.bind</code> binds the form to a record instead of a static config. <code>bind: { grid, rowKey }</code> binds a grid row: when no layout is declared, one item per column is generated from the grid's own columns &mdash; their titles, types and editors &mdash; so a boolean column becomes a <code>checkbox</code> and a number column a <code>number</code>; the form follows the grid's density by default; <code>save()</code> writes the dirty items once through the grid's edit path (permissions, read-only, parse, validation and the beforeEdit gate honoured); and <code>revert()</code> restores the bound values. External edits to the row update a clean item in place and leave a dirty one alone, flagging it with <code>isConflicted()</code>. <code>bind: { router, route, key, onSave? }</code> follows a data-router record with the same update-unless-dirty rule: the form reads the record eagerly through <code>router.get(route, key)</code> &mdash; so a form created <em>after</em> the ingest still fills at once, whatever the order &mdash; then subscribes to the route's keyed diffs, and <code>destroy()</code> unsubscribes. <code>save()</code> writes the dirty items back through <code>router.apply</code>, then calls <code>bind.onSave(changes, record)</code> when the host gave one (may be async; a rejection reverts the router record and leaves the form dirty). A form <strong>group</strong> takes the same <code>bind</code> once: every member fills with its own fields off the record, and <code>group.save()</code> writes <em>one</em> router update with all the members' dirty fields, calling <code>onSave</code> once. A plain object of values initialises the form and is written back by <code>save()</code>. <code>isDirty(name?)</code> reports an unsaved user edit on one item or the whole form, and editing is the only thing that marks an item dirty &mdash; <code>setValue()</code>, <code>revert()</code> and bound-record updates are silent. The binding is duck-typed against the grid's public API, so the module still imports no grid and no core source.</p>
504
506
  <p><strong>Custom controls.</strong> <code>registerControl(type, definition)</code> makes a control type a first-class item: the definition's <code>create(el, item, api)</code> builds the control's DOM into <code>el</code>, <code>getValue(el)</code>/<code>setValue(el, value)</code> round-trip the value through <code>getValue()</code>/<code>setValue()</code>, and an optional <code>validate(value, item, el)</code> refuses a bad commit (return <code>true</code> to accept, <code>false</code> or a message to refuse, a Promise is awaited). The registered control gets the same wrapper, label, item API, events and density as a built-in, its <code>api.onChange()</code>/<code>api.onInput()</code> commit and report through the same path a native edit takes, and a later registration for the same type replaces the earlier one.</p>
505
507
  <p><strong>Declarative rules.</strong> An item's <code>visibleWhen</code>, <code>readOnlyWhen</code> or <code>requiredWhen</code> is a rule re-evaluated on every committed change: a constant, a function of the whole <code>{ name: value }</code> snapshot, or a condition on one named item's value with <code>equals</code>, <code>in</code>, <code>empty</code> or <code>fn</code>. A <code>visibleWhen</code> that is false hides the item, a <code>readOnlyWhen</code> that is true makes its control read-only, and a <code>requiredWhen</code> that is true marks it required &mdash; its commit path then refuses an empty value. A rule composes on the item's own <code>hidden</code>/<code>readOnly</code> baseline, so it never fights the item API's <code>hide()</code>/<code>show()</code>/<code>enable()</code>/<code>disable()</code>.</p>
506
- <p><strong>Wizards.</strong> A <code>steps</code> array turns the form into a wizard: each <code>{ id, label, rows }</code> step is one body, only the active one visible, under a step indicator whose <code>stepOrientation</code> is <code>horizontal</code> (default) or <code>vertical</code>. <code>form.next()</code> validates the current step's items before advancing, <code>form.back()</code> goes back without validating, and <code>form.goToStep(index)</code> moves to a step validating the ones passed over on the way forward; the indicator marks each step pending, active, complete or in error, and never disagrees with the form. On the last step <code>next()</code> reads "Finish" and validates in place.</p>
508
+ <p><strong>Wizards.</strong> A <code>steps</code> array turns the form into a wizard: each <code>{ id, label, rows }</code> step is one body, only the active one visible, under a step indicator whose <code>stepOrientation</code> is <code>horizontal</code> (default) or <code>vertical</code>. <code>form.next()</code>, <code>form.goToStep(index)</code> to a later step, and a click on a later indicator step all validate the current step's items first &mdash; exactly as <code>form.validate()</code> does, so a refused move shows the same errors, fires the <code>validated</code> event and moves focus to the first invalid item, resolving <code>false</code> and staying put. <code>form.back()</code>, <code>goToStep(index)</code> to an earlier step and an earlier indicator step go back without validating; the indicator marks each step pending, active, complete or in error, and never disagrees with the form. On the last step <code>next()</code> reads "Finish" and validates in place, resolving <code>false</code> while it is invalid. Set <code>validateSteps: false</code> to opt out &mdash; the integrator then runs <code>validate()</code> itself, and forward navigation is unchecked.</p>
507
509
  <p><strong>Files and rich text.</strong> A <code>file</code> item is an uploader field: drop, paste or pick, with <code>accept</code> (extensions or MIME patterns), <code>maxSize</code> and <code>maxFiles</code> enforced at admission, and a host <code>upload(file, { onProgress, signal })</code> callback that does the actual upload. The file list shows per-file progress and status with retry and remove, and the value is the accepted file entries &mdash; a rejected file stays visible with a message but never enters the value. A <code>richtext</code> item is a sanitised HTML editor: toolbar presets (<code>basic</code>/<code>html</code>/<code>rich</code> or an item-name array) over bold, italic, underline, strikethrough, colour, case, paragraph styles, lists, alignment, links, images, source view and undo/redo, with keyboard shortcuts and paste cleanup. Its whitelist sanitiser is the security boundary, shared with the grid's rich-text editor as one implementation: scripts, event handlers, <code>srcdoc</code>, <code>id</code>/<code>name</code> clobbering and <code>javascript:</code>/<code>vbscript:</code>/<code>data:</code> URLs never survive pasting, dropping or <code>setValue</code>, and every <code>_blank</code> link carries <code>rel="noopener noreferrer"</code>. Both honour density, theme tokens, <code>readOnly</code>/<code>disabled</code> and the item API, and clear to <code>[]</code> (file) or <code>''</code> (richtext). The same rich-text editor is available as a grid pop-up editor for long text with <code>edit: { editor: 'richtext' }</code>.</p>
508
510
  <p><strong>Passwords and one-time codes.</strong> A <code>password</code> <code>input</code> carries a reveal button inside the field: an eye that toggles the input between the native <code>password</code> and <code>text</code> types, keyboard-operable like any button, carrying <code>aria-pressed</code> and a name from the catalogue (&ldquo;Show password&rdquo;/&ldquo;Hide password&rdquo;). It returns to hidden when focus leaves the field and when the form validates or submits, and <code>reveal: false</code> renders a bare password input with no button &mdash; the value, events and validation are unchanged. An <code>otp</code> item is a one-time-code entry of single-character boxes: <code>length</code> (default 6) sets how many, <code>mode</code> (<code>numeric</code> default or <code>alphanumeric</code>) drives each box&rsquo;s <code>inputmode</code>/<code>pattern</code> and the characters it admits, the first box advertises <code>autocomplete="one-time-code"</code>, and <code>mask</code> hides each glyph behind the password disc. Typing advances, Backspace steps back and clears, the arrow keys move, and pasting fills every box (spaces and dashes stripped). <code>getValue()</code> returns the joined string, <code>setValue()</code> fills the boxes, and a <code>complete</code> event fires once every box is filled. Screen readers hear one labelled group, each box announced as &ldquo;Digit <em>n</em> of <em>length</em>&rdquo;.</p>
509
- <p><strong>Buttons and button rows.</strong> A <code>button</code> item is an acting control, not a value: it takes an <code>icon</code> (a name from the grid&rsquo;s icon set, an image URL or data: URI, or an element) with <code>iconPosition</code> (<code>start</code> default / <code>end</code>), sitting a density-scaled gap (about 0.5em) from the text and vertically centred with it, a <code>variant</code> (<code>primary</code>, <code>secondary</code> default, <code>tonal</code>, <code>outline</code>, <code>text</code> or <code>danger</code> &mdash; theme-token colours, light and dark), <code>fullWidth</code> to span the form, a <code>size</code> (<code>sm</code>/<code>md</code>/<code>lg</code>), a <code>textTransform</code> (<code>none</code> default / <code>uppercase</code>) and a <code>buttonType</code> (<code>button</code>, <code>submit</code> or <code>reset</code>). A <code>submit</code> button runs <code>form.validate()</code> first and only then fires a <code>submit</code> event on the form&rsquo;s element with the values in <code>event.detail.values</code> when valid; a <code>reset</code> button calls <code>form.clear()</code>. <code>loading: true</code> (or <code>item.setLoading(bool)</code>) swaps the icon for a spinner, keeps the width, sets <code>aria-busy</code> and makes the button ignore clicks; a <code>href</code> turns it into a link styled as a button. An icon with no label is icon-only: it requires <code>ariaLabel</code> and renders square. A <code>{ type: 'buttons', align, items }</code> row lays a group of buttons out along the width with <code>align</code> <code>start</code> (default), <code>end</code>, <code>center</code>, <code>stretch</code> or <code>space-between</code>, and each child takes the same options as a <code>button</code> item.</p>
511
+ <p><strong>Buttons and button rows.</strong> A <code>button</code> item is an acting control, not a value: it takes an <code>icon</code> (a name from the grid&rsquo;s icon set, an image URL or data: URI, or an element) with <code>iconPosition</code> (<code>start</code> default / <code>end</code>), sitting a density-scaled gap (about 0.5em) from the text and vertically centred with it, a <code>variant</code> (<code>primary</code>, <code>secondary</code> default, <code>tonal</code>, <code>outline</code>, <code>text</code> or <code>danger</code> &mdash; theme-token colours, light and dark), <code>fullWidth</code> to span the form (inside a <code>cols</code> row it fills its own column, so two full-width buttons keep sharing the row), a <code>size</code> (<code>sm</code>/<code>md</code>/<code>lg</code>), a <code>textTransform</code> (<code>none</code> default / <code>uppercase</code>) and a <code>buttonType</code> (<code>button</code>, <code>submit</code> or <code>reset</code>). A <code>submit</code> button runs <code>form.validate()</code> first and only then fires a <code>submit</code> event on the form&rsquo;s element with the values in <code>event.detail.values</code> when valid; a <code>reset</code> button calls <code>form.clear()</code>. <code>loading: true</code> (or <code>item.setLoading(bool)</code>) swaps the icon for a spinner, keeps the width, sets <code>aria-busy</code> and makes the button ignore clicks; a <code>href</code> turns it into a link styled as a button. An icon with no label is icon-only: it requires <code>ariaLabel</code> and renders square. A <code>{ type: 'buttons', align, items }</code> row lays a group of buttons out along the width with <code>align</code> <code>start</code> (default), <code>end</code>, <code>center</code>, <code>stretch</code> or <code>space-between</code>, and each child takes the same options as a <code>button</code> item.</p>
510
512
  <p><strong>A button is an action item, and its states are named tokens.</strong> Because a button acts rather than holds a value, the view draws no field <code>&lt;label&gt;</code>, required asterisk, help line or hint line for it &mdash; its <code>label</code> is its own text, shown once &mdash; and in a <code>labelPosition: 'left'</code> form it lines up with the fields&rsquo; input column (unless <code>fullWidth</code> spans the row). Every variant defines the same state matrix in light and dark, each state&rsquo;s colours read from an overridable <code>--lattice-button-&lt;variant&gt;-&lt;state&gt;</code> token: <strong>rest</strong>; <strong>hover</strong> (one step toward the foreground &mdash; a shade in light, a tint in dark); <strong>active</strong> (a deeper step); <strong>focus-visible</strong> (a 2px accent ring with a 2px offset, never removed); <strong>disabled</strong> (muted text, border and fill with text at or above 3:1 against its fill, <code>cursor: not-allowed</code>, no hover or pressed change, <code>aria-disabled</code>, and still in the tab order only when <code>focusableWhenDisabled: true</code> keeps it focusable instead of natively disabled); <strong>loading</strong> (the spinner from <code>loading</code>); and <strong>toggled</strong> (a distinct pressed look). <code>toggle: true</code> makes the button two-state: it flips <code>aria-pressed</code> and fires <code>change</code> with the new boolean (starting pressed when <code>value</code> is <code>true</code>). The swatches below show each variant&rsquo;s fill per state, light and dark.</p>
511
513
  <div class="table-wrap">
512
514
  <table>
@@ -525,7 +527,8 @@ form.destroy();</code></pre>
525
527
  <table>
526
528
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
527
529
  <tbody>
528
- <tr><td class="sig">createForm(el, config?)</td><td class="desc">Create a form. <code>config</code> is <code>{ rows | cols, density, spacing, labelGap, labelPosition, labelWidth, requiredHint, colsBreakpoint, bind, css, messages, steps, stepOrientation }</code>; <code>rows</code> stacks the top-level blocks vertically, <code>cols</code> lays them left-to-right sharing the width equally (used when <code>rows</code> is absent), <code>spacing</code> is the vertical gap between items (a px number or a CSS length, defaulting to the density's gap) and <code>labelGap</code> the gap between an item's label and its field (defaulting to half the density's space), <code>labelPosition</code> (<code>top</code> default / <code>left</code> / <code>hidden</code>) and <code>labelWidth</code> are the defaults every item inherits (an item&rsquo;s own value wins), <code>requiredHint</code> is the placeholder marker a required field with no visible label shows (<code>true</code> appends <code>" *"</code>, a string resolves through the catalogue, <code>false</code> shows nothing), <code>colsBreakpoint</code> is the container width below which a <code>cols</code> container stacks into one column (default 480px), <code>bind</code> binds the form to a record (<code>{ grid, rowKey }</code>, <code>{ router, route, key, onSave? }</code> or a plain values object), <code>messages</code> supplies a <code>t(key, params)</code> catalogue for the separator's accessible label and the validation defaults, and a <code>steps</code> array turns the form into a wizard with a <code>horizontal</code> (default) or <code>vertical</code> <code>stepOrientation</code>. Returns <code>{ el, config, getItem, getValue, setValue, clear, validate, forEach, on, off, setDensity, setSpacing, setReadOnly, setDisabled, isDirty, isConflicted, save, revert, steps, activeStep, next, back, goToStep, destroy }</code>.</td></tr>
530
+ <tr><td class="sig">createForm(el, config?)</td><td class="desc">Create a form. <code>config</code> is <code>{ rows | cols, density, spacing, labelGap, labelPosition, labelWidth, requiredHint, colsBreakpoint, bind, css, messages, steps, stepOrientation }</code>; <code>rows</code> stacks the top-level blocks vertically, <code>cols</code> lays them left-to-right sharing the width equally (used when <code>rows</code> is absent), <code>spacing</code> is the vertical gap between items (a px number or a CSS length, defaulting to the density's gap) and <code>labelGap</code> the gap between an item's label and its field (defaulting to half the density's space), <code>labelPosition</code> (<code>top</code> default / <code>left</code> / <code>hidden</code>) and <code>labelWidth</code> are the defaults every item inherits (an item&rsquo;s own value wins), <code>requiredHint</code> is the placeholder marker a required field with no visible label shows (<code>true</code> appends <code>" *"</code>, a string resolves through the catalogue, <code>false</code> shows nothing), <code>colsBreakpoint</code> is the container width below which a <code>cols</code> container stacks into one column (default 480px; <code>0</code> or <code>false</code> never stacks), <code>bind</code> binds the form to a record (<code>{ grid, rowKey }</code>, <code>{ router, route, key, onSave? }</code> or a plain values object), <code>messages</code> supplies a <code>t(key, params)</code> catalogue for the separator's accessible label and the validation defaults, and a <code>steps</code> array turns the form into a wizard with a <code>horizontal</code> (default) or <code>vertical</code> <code>stepOrientation</code>. Returns <code>{ el, config, getItem, getValue, setValue, clear, validate, forEach, on, off, setDensity, setSpacing, setReadOnly, setDisabled, isDirty, isConflicted, save, revert, steps, activeStep, next, back, goToStep, destroy }</code>.</td></tr>
531
+ <tr><td class="sig">createForm(el, config?)</td><td class="desc">Create a form. <code>config</code> is <code>{ rows | cols, density, spacing, labelGap, labelPosition, labelWidth, requiredHint, colsBreakpoint, bind, css, messages, steps, stepOrientation, validateSteps }</code>; <code>rows</code> stacks the top-level blocks vertically, <code>cols</code> lays them left-to-right sharing the width equally (used when <code>rows</code> is absent), <code>spacing</code> is the vertical gap between items (a px number or a CSS length, defaulting to the density's gap) and <code>labelGap</code> the gap between an item's label and its field (defaulting to half the density's space), <code>labelPosition</code> (<code>top</code> default / <code>left</code> / <code>hidden</code>) and <code>labelWidth</code> are the defaults every item inherits (an item&rsquo;s own value wins), <code>requiredHint</code> is the placeholder marker a required field with no visible label shows (<code>true</code> appends <code>" *"</code>, a string resolves through the catalogue, <code>false</code> shows nothing), <code>colsBreakpoint</code> is the container width below which a <code>cols</code> container stacks into one column (default 480px), <code>bind</code> binds the form to a record (<code>{ grid, rowKey }</code>, <code>{ router, route, key, onSave? }</code> or a plain values object), <code>messages</code> supplies a <code>t(key, params)</code> catalogue for the separator's accessible label and the validation defaults, a <code>steps</code> array turns the form into a wizard with a <code>horizontal</code> (default) or <code>vertical</code> <code>stepOrientation</code>, and <code>validateSteps: false</code> opts out of validating the current step on a forward move (the integrator runs <code>validate()</code> itself). Returns <code>{ el, config, getItem, getValue, setValue, clear, validate, forEach, on, off, setDensity, setSpacing, setReadOnly, setDisabled, isDirty, isConflicted, save, revert, steps, activeStep, next, back, goToStep, destroy }</code>.</td></tr>
529
532
  <tr><td class="sig">registerControl(type, definition)</td><td class="desc">Register a custom control type as a first-class item. <code>definition</code> is <code>{ create(el, item, api), getValue(el), setValue(el, value), validate?(value, item, el), destroy?(el) }</code>; the control gets the same wrapper, label, item API, events and density as a built-in. A later registration for the same type replaces the earlier one.</td></tr>
530
533
  <tr><td class="sig">item</td><td class="desc">Each item: <code>type</code> (a built-in <code>input</code>/<code>number</code>/<code>textarea</code>/<code>checkbox</code>/<code>button</code>/<code>text</code>/<code>combo</code>/<code>rating</code>/<code>stage</code> or a registered custom type), <code>name</code>, <code>label</code>, <code>labelPosition</code> (<code>top</code>/<code>left</code>), <code>labelWidth</code>, <code>width</code>, <code>placeholder</code>, <code>helpMessage</code>, <code>hidden</code>, <code>disabled</code>, <code>readOnly</code>, <code>value</code>, <code>required</code>, <code>validation</code>, <code>errorMessage</code>, <code>successMessage</code>, <code>preMessage</code>, <code>validationMode</code>, <code>css</code>, and the rules <code>visibleWhen</code>, <code>readOnlyWhen</code>, <code>requiredWhen</code>. <code>input</code> takes <code>subtype</code> (a <code>password</code> subtype also <code>reveal</code>); <code>number</code> takes <code>step</code>/<code>min</code>/<code>max</code>. <code>rating</code> takes <code>max</code>, <code>allowHalf</code>, <code>allowClear</code>, <code>icon</code> or <code>icons</code> (<code>{ filled, empty }</code>), <code>labels</code>, <code>showValue</code> and <code>size</code> (<code>sm</code>/<code>md</code>/<code>lg</code>); <code>stage</code> takes <code>stages</code>, <code>current</code>, <code>completed</code>, <code>clickable</code> and <code>showIcons</code>; <code>chips</code> takes <code>options</code> (<code>{ value, label, icon?, disabled? }</code>), <code>multiple</code>, <code>allowEmpty</code>, <code>wrap</code> and <code>max</code>; <code>otp</code> takes <code>length</code>, <code>mode</code> (<code>numeric</code>/<code>alphanumeric</code>) and <code>mask</code>. <code>select</code>, <code>radiogroup</code> and <code>checkboxgroup</code> take <code>options</code>; a <code>radiogroup</code>/<code>checkboxgroup</code> also takes <code>orientation</code> (<code>vertical</code>/<code>horizontal</code>) or <code>columns</code> (an n-column grid read across then down). <code>button</code> takes <code>icon</code> (a name, URL/data: URI or element), <code>iconPosition</code> (<code>start</code>/<code>end</code>), <code>variant</code> (<code>primary</code>/<code>secondary</code>/<code>tonal</code>/<code>outline</code>/<code>text</code>/<code>danger</code>), <code>fullWidth</code>, <code>size</code> (<code>sm</code>/<code>md</code>/<code>lg</code>), <code>textTransform</code> (<code>none</code>/<code>uppercase</code>), <code>buttonType</code> (<code>button</code>/<code>submit</code>/<code>reset</code>), <code>loading</code>, <code>href</code>, <code>ariaLabel</code> (required for an icon-only button), <code>toggle</code> (a two-state button that flips <code>aria-pressed</code> and fires <code>change</code> with the new boolean) and <code>focusableWhenDisabled</code> (keep a disabled button focusable and in the tab order, reading <code>aria-disabled</code> instead of being natively disabled); a <code>{ type: 'buttons', align, items }</code> row lays buttons out by <code>align</code> (<code>start</code>/<code>end</code>/<code>center</code>/<code>stretch</code>/<code>space-between</code>), each child taking the same options.</td></tr>
531
534
  <tr><td class="sig">getItem(name)</td><td class="desc">The item handle <code>{ el, name, getValue, setValue, show, hide, enable, disable, focus, blur, isVisible, validate, setLoading }</code> &mdash; <code>el</code> is the item's wrapper element. <code>undefined</code> for an unknown name.</td></tr>
@@ -544,9 +547,9 @@ form.destroy();</code></pre>
544
547
  <tr><td class="sig">isConflicted(name?)</td><td class="desc">Whether an item (or the whole form) is in conflict: the user has an unsaved edit, and an external change to the bound record arrived for the same item, which the form therefore did not overwrite.</td></tr>
545
548
  <tr><td class="sig">save()</td><td class="desc">Save the dirty items. For a grid binding the writes go through the grid's own edit path in one call (permissions, read-only, parse, validation and the beforeEdit gate honoured); for a router binding they are applied back through <code>router.apply</code> and, when <code>bind.onSave</code> was given, handed to it (may be async &mdash; a rejection reverts the router record and keeps the items dirty); for a values binding they are written back into the bound object. Returns <code>true</code> when saved (or nothing to save), or a <code>Promise&lt;boolean&gt;</code> when the write is asynchronous. An unbound form returns <code>false</code>.</td></tr>
546
549
  <tr><td class="sig">revert()</td><td class="desc">Restore every dirty item to its bound/committed value and clear the dirty and conflict flags.</td></tr>
547
- <tr><td class="sig">next()</td><td class="desc">Advance a wizard one step, validating the current step's items first. Resolves <code>true</code> when the wizard moved (or the final step validated), <code>false</code> when the current step is invalid or there is no next step. Not a wizard: resolves <code>false</code>.</td></tr>
550
+ <tr><td class="sig">next()</td><td class="desc">Advance a wizard one step, validating the current step's items first &mdash; as <code>form.validate()</code> does, so a refused move shows the same errors, fires <code>validated</code> and focuses the first invalid item. Resolves <code>true</code> when the wizard moved (or the final step validated), <code>false</code> while the current step is invalid or there is no next step; <code>validateSteps: false</code> skips the validation. Not a wizard: resolves <code>false</code>.</td></tr>
548
551
  <tr><td class="sig">back()</td><td class="desc">Go back one step &mdash; always allowed, never validated. Resolves <code>false</code> when already on the first step.</td></tr>
549
- <tr><td class="sig">goToStep(index)</td><td class="desc">Move a wizard to a step by index. Forward moves validate the steps passed over (the first invalid one stops the move and is marked in error); backward moves are always allowed. Resolves <code>false</code> when an intermediate step is invalid or the index is out of range.</td></tr>
552
+ <tr><td class="sig">goToStep(index)</td><td class="desc">Move a wizard to a step by index. Forward moves validate the steps passed over (the first invalid one is focused, marked in error and stops the move); backward moves are always allowed, and <code>validateSteps: false</code> skips the forward validation. Resolves <code>false</code> when an intermediate step is invalid or the index is out of range.</td></tr>
550
553
  <tr><td class="sig">destroy()</td><td class="desc">Empty the element, remove only what the module added (class, density attribute, own inline style), and drop every item record, listener and handler. The host still owns the element.</td></tr>
551
554
  </tbody>
552
555
  </table>
@@ -682,6 +685,46 @@ form.destroy();
682
685
  form.destroy();
683
686
  <span class="kw">return</span> widths;</code></pre>
684
687
 
688
+ <h3 id="form-cols-breakpoint-example">Cols breakpoint and full-width buttons, executed</h3>
689
+ <p class="section-note"><code>colsBreakpoint: 0</code> (or <code>false</code>) turns stacking off so a <code>cols</code> row never stacks, and a <code>fullWidth</code> button fills its own column while the columns keep sharing the row &mdash; with the real-Chrome geometry asserted in <code>test/form-cols-breakpoint-2292-browser.test.js</code>.</p>
690
+ <pre data-run="js" data-expect="lat-cols-never|true|true|lat-cols-never|lat-cols-bp-200" data-covers="export:createForm config:FormConfig.cols config:FormConfig.colsBreakpoint config:FormItemConfig.fullWidth"><code><span class="kw">const</span> { createTestDom } = <span class="kw">await</span> import('../packages/dom/src/renderer/testdom.js');
691
+ <span class="kw">const</span> { createForm } = <span class="kw">await</span> import('../packages/modules/form/index.js');
692
+
693
+ <span class="kw">const</span> { root } = createTestDom();
694
+ <span class="kw">const</span> form = createForm(root, {
695
+ colsBreakpoint: <span class="kw">false</span>, <span class="cmt">// false (or 0): never stack</span>
696
+ cols: [
697
+ { type: <span class="str">'button'</span>, name: <span class="str">'ok'</span>, label: <span class="str">'OK'</span>, fullWidth: <span class="kw">true</span> },
698
+ { type: <span class="str">'button'</span>, name: <span class="str">'cancel'</span>, label: <span class="str">'Cancel'</span>, fullWidth: <span class="kw">true</span> },
699
+ ],
700
+ });
701
+ <span class="kw">const</span> neverFalse = root.style.getPropertyValue('--lattice-cols-container'); <span class="cmt">// 'lat-cols-never'</span>
702
+ <span class="kw">const</span> okFull = form.getItem('ok').el.querySelector('button').getAttribute('data-full'); <span class="cmt">// 'true'</span>
703
+ <span class="kw">const</span> cancelFull = form.getItem('cancel').el.querySelector('button').getAttribute('data-full'); <span class="cmt">// 'true'</span>
704
+ form.destroy();
705
+
706
+ <span class="kw">const</span> zero = createForm(root, {
707
+ colsBreakpoint: 0, <span class="cmt">// 0 means never stack too</span>
708
+ cols: [
709
+ { type: <span class="str">'input'</span>, name: <span class="str">'a'</span>, label: <span class="str">'A'</span> },
710
+ { type: <span class="str">'input'</span>, name: <span class="str">'b'</span>, label: <span class="str">'B'</span> },
711
+ ],
712
+ });
713
+ <span class="kw">const</span> neverZero = root.style.getPropertyValue('--lattice-cols-container'); <span class="cmt">// 'lat-cols-never'</span>
714
+ zero.destroy();
715
+
716
+ <span class="kw">const</span> positive = createForm(root, {
717
+ colsBreakpoint: 200, <span class="cmt">// a positive number keeps the old behaviour</span>
718
+ cols: [
719
+ { type: <span class="str">'input'</span>, name: <span class="str">'c'</span>, label: <span class="str">'C'</span> },
720
+ { type: <span class="str">'input'</span>, name: <span class="str">'d'</span>, label: <span class="str">'D'</span> },
721
+ ],
722
+ });
723
+ <span class="kw">const</span> positiveBp = root.style.getPropertyValue('--lattice-cols-container'); <span class="cmt">// 'lat-cols-bp-200'</span>
724
+ positive.destroy();
725
+
726
+ <span class="kw">return</span> [neverFalse, okFull, cancelFull, neverZero, positiveBp].join('|');</code></pre>
727
+
685
728
  <h3 id="form-disabled-readonly-example">Disabled and read-only, executed</h3>
686
729
  <p class="section-note">The two states are one contract across every control, set per item or form-wide &mdash; with the real Chrome focus, cursor, token and value assertions over all 16 control types in <code>test/form-disabled-readonly-2198-browser.test.js</code>.</p>
687
730
  <pre data-run="js" data-expect="true|true|true|Ada|false|true|true|true|true|false" data-covers="export:createForm method:form.setReadOnly method:form.setDisabled config:FormItemConfig.disabled config:FormItemConfig.readOnly"><code><span class="kw">const</span> { createTestDom } = <span class="kw">await</span> import('../packages/dom/src/renderer/testdom.js');
@@ -896,7 +939,7 @@ form.destroy();
896
939
  return [variant, full, iconPosition, googleImg, caps, iconOnly, helpLabel, linkTag, blocked, submitted, submittedValues, cleared, busy, spinner, rowAlign, rowButtons, pinStart, pinAfter, toggleValue, ghostDisabled, ghostAriaDisabled].join('|');</code></pre>
897
940
 
898
941
  <h3 id="combo">The combo control</h3>
899
- <p>The same module also ships <code>createCombo(el, config)</code>, the standalone form of the form's <code>type: 'combo'</code> item: a searchable combobox with dynamic server values, dependent lists, multi-select and multi-line options. It is a <code>load</code>-driven paging list &mdash; the search is debounced, a newer load aborts the older one, pages load on scroll and are cached, and <code>reload()</code> drops the cache &mdash; or a static <code>options</code> list when there is no server. Options are a bare value or <code>{ value, label, description, icon, avatar, color, dot, disabled }</code>; a <code>description</code> adds a second line, an <code>icon</code> is a glyph name from the grid's icon set drawn as the real SVG (or a DOM node) and an <code>avatar</code> is an image URL or initials, each drawn round on the left of the list rows, the multi-select chips and the single-select shown value, in a virtualised list of variable-height rows; <code>color</code> tints the initials avatar and the icon (an SVG takes it as its fill or stroke, an element as its colour), and <code>dot</code> draws a small filled circle in that colour instead of any icon or avatar, with no letter; a <code>disabled</code> option shows but refuses a click. A <code>multiple</code> combo keeps its value as an array in selection order and draws chips that wrap, cap at <code>maxHeight</code> and scroll, with a <code>+N</code> summary and a <code>selectAll</code> action over the loaded set. <code>chipPlacement</code> moves those chips out of the control &mdash; <code>'above'</code> or <code>'below'</code> puts them in their own wrapping row beside the field, so the input stays a single clean search line and the list still opens directly under it. <code>resolveLabels(values)</code> supplies labels for a preselected value outside the first page, <code>newOptions</code> turns the box into free entry (with a create row while typing), <code>optionTemplate</code> replaces the row renderer and <code>selectedTemplate</code> the chips and shown value &mdash; either may return a DOM node or an HTML string, which is sanitised before insertion &mdash; and a form item's <code>dependsOn</code> clears and reloads it when a named parent item changes.</p>
942
+ <p>The same module also ships <code>createCombo(el, config)</code>, the standalone form of the form's <code>type: 'combo'</code> item: a searchable combobox with dynamic server values, dependent lists, multi-select and multi-line options. It is a <code>load</code>-driven paging list &mdash; the search is debounced, a newer load aborts the older one, pages load on scroll and are cached, and <code>reload()</code> drops the cache &mdash; or a static <code>options</code> list when there is no server. Options are a bare value or <code>{ value, label, description, icon, avatar, color, dot, disabled }</code>; a <code>description</code> adds a second line, an <code>icon</code> is a glyph name from the grid's icon set drawn as the real SVG (or a DOM node) and an <code>avatar</code> is an image URL or initials, each drawn round on the left of the list rows, the multi-select chips and the single-select shown value, in a virtualised list of variable-height rows; <code>color</code> tints the initials avatar and the icon (an SVG takes it as its fill or stroke, an element as its colour), and <code>dot</code> draws a small filled circle in that colour instead of any icon or avatar, with no letter; a <code>disabled</code> option shows but refuses a click. A <code>multiple</code> combo keeps its value as an array in selection order and draws chips that wrap, cap at <code>maxHeight</code> and scroll, with a <code>+N</code> summary and a <code>selectAll</code> action over the loaded set. <code>chipPlacement</code> moves those chips out of the control &mdash; <code>'above'</code> or <code>'below'</code> puts them in their own wrapping row beside the field, so the input stays a single clean search line and the list still opens directly under it. <code>resolveLabels(values)</code> supplies labels for a preselected value outside the first page, <code>newOptions</code> turns the box into free entry (with a create row while typing), <code>optionTemplate</code> replaces the row renderer and <code>selectedTemplate</code> the chips and shown value &mdash; either may return a DOM node or an HTML string, which is sanitised before insertion &mdash; and a form item's <code>dependsOn</code> clears and reloads it when a named parent item changes. The list opens in the same shared top-level layer the select and the date picker use, positioned against the field so a clipping or fixed-height ancestor never cuts it off (BACKLOG-0002291).</p>
900
943
  <div class="table-wrap">
901
944
  <table>
902
945
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
@@ -945,8 +988,9 @@ const sized = createCombo(sizedHost, {
945
988
  options: [{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }],
946
989
  optionTemplate: (opt) =&gt; 'T:' + opt.label,
947
990
  });
991
+ const sizedPopup = sized.root.querySelector('.lat-combo__popup'); <span class="cmt">// captured before open: the popup moves into the shared layer host while open (BACKLOG-0002291)</span>
948
992
  sized.open();
949
- const renderedRow = Array.from(sized.root.querySelectorAll('.lat-combo__option')).find((r) =&gt; r.textContent === 'T:Alpha');
993
+ const renderedRow = Array.from(sizedPopup.querySelectorAll('.lat-combo__option')).find((r) =&gt; r.textContent === 'T:Alpha');
950
994
  const rendered = renderedRow.textContent; <span class="cmt">// 'T:Alpha' &mdash; the optionTemplate applied</span>
951
995
 
952
996
  // A static combo with a real 'user' SVG icon, an initials avatar and a
@@ -960,8 +1004,9 @@ const deco = createCombo(decoHost, {
960
1004
  ],
961
1005
  selectedTemplate: (opt) =&gt; 'S:' + opt.label,
962
1006
  });
1007
+ const decoPopup = deco.root.querySelector('.lat-combo__popup'); <span class="cmt">// captured before open: the popup moves into the shared layer host while open (BACKLOG-0002291)</span>
963
1008
  deco.open();
964
- const decoRows = Array.from(deco.root.querySelectorAll('.lat-combo__option'));
1009
+ const decoRows = Array.from(decoPopup.querySelectorAll('.lat-combo__option'));
965
1010
  const hasIcon = decoRows.some((r) =&gt; r.querySelector('.lat-combo__icon') !== null); <span class="cmt">// true &mdash; the 'user' SVG</span>
966
1011
  const hasAvatar = decoRows.some((r) =&gt; r.querySelector('.lat-combo__avatar') !== null); <span class="cmt">// true &mdash; round initials</span>
967
1012
  deco.setValue('u');
@@ -1331,6 +1376,53 @@ form.setValue({ rating: '5' }); <span class="cmt">// re-evalua
1331
1376
  form.destroy();
1332
1377
  <span class="kw">return</span> [blocked, advanced, active, custom, noteHidden, reviewReadOnly, wentBack, jumped, finish, vertical].join('|');</code></pre>
1333
1378
 
1379
+ <h3 id="form-step-validation-example">Step validation, the <code>validateSteps</code> opt-out and the refused move, executed</h3>
1380
+ <p class="section-note">A forward move &mdash; <code>next()</code>, <code>goToStep()</code> to a later step, or a click on a later indicator
1381
+ step &mdash; validates the current step's items exactly as <code>form.validate()</code> does: a <code>required</code> item
1382
+ left empty refuses the move, resolves <code>false</code>, paints the same <code>aria-invalid</code> and message, fires
1383
+ <code>validated</code> and focuses the first invalid item, while <code>back()</code> never validates. <code>validateSteps:
1384
+ false</code> restores the unchecked navigation &mdash; with the real Chrome indicator-click assertions in
1385
+ <code>test/form-steps-validate-2290-browser.test.js</code>.</p>
1386
+ <pre data-run="js" data-expect="false|0|true|This field is required.|true|true|true|true|true" data-covers="export:createForm method:form.next method:form.back method:form.on config:FormConfig.steps config:FormConfig.validateSteps config:FormItemConfig.required"><code><span class="kw">const</span> { createTestDom } = <span class="kw">await</span> import('../packages/dom/src/renderer/testdom.js');
1387
+ <span class="kw">const</span> { createForm } = <span class="kw">await</span> import('../packages/modules/form/index.js');
1388
+
1389
+ <span class="kw">const</span> { root } = createTestDom();
1390
+ <span class="kw">let</span> validated = 0;
1391
+ <span class="kw">const</span> form = createForm(root, {
1392
+ steps: [
1393
+ { id: 'a', label: 'A', rows: [
1394
+ { type: 'input', name: 'x', label: 'X', required: true },
1395
+ { type: 'input', name: 'y', label: 'Y' },
1396
+ ] },
1397
+ { id: 'b', label: 'B', rows: [ { type: 'checkbox', name: 'done', label: 'Done', required: true } ] },
1398
+ ],
1399
+ });
1400
+ form.on('validated', () =&gt; { validated += 1; });
1401
+
1402
+ <span class="kw">const</span> blocked = <span class="kw">await</span> form.next(); <span class="cmt">// false &mdash; x is required and empty</span>
1403
+ <span class="kw">const</span> stayed = form.activeStep; <span class="cmt">// 0 &mdash; the move was refused</span>
1404
+ <span class="kw">const</span> xInput = form.getItem('x').el.querySelector('input');
1405
+ <span class="kw">const</span> invalid = xInput.getAttribute('aria-invalid'); <span class="cmt">// 'true' &mdash; as validate() paints it</span>
1406
+ <span class="kw">const</span> message = form.getItem('x').el.querySelector('.lat-form__validation').textContent; <span class="cmt">// 'This field is required.'</span>
1407
+ <span class="kw">const</span> focused = root.ownerDocument.activeElement === xInput; <span class="cmt">// focus lands on the first invalid item</span>
1408
+ <span class="kw">const</span> fired = validated &gt; 0; <span class="cmt">// the refused move emitted `validated`</span>
1409
+
1410
+ form.getItem('x').setValue('Ada'); <span class="cmt">// fill the required item</span>
1411
+ <span class="kw">const</span> advanced = <span class="kw">await</span> form.next(); <span class="cmt">// true &mdash; step one now validates</span>
1412
+ <span class="kw">const</span> wentBack = <span class="kw">await</span> form.back(); <span class="cmt">// true &mdash; back never validates</span>
1413
+ form.destroy();
1414
+
1415
+ <span class="kw">const</span> uncheckedForm = createForm(root, {
1416
+ validateSteps: <span class="kw">false</span>, <span class="cmt">// the integrator runs validate() itself</span>
1417
+ steps: [
1418
+ { id: 'a', label: 'A', rows: [ { type: 'input', name: 'x', label: 'X', required: true } ] },
1419
+ { id: 'b', label: 'B', rows: [] },
1420
+ ],
1421
+ });
1422
+ <span class="kw">const</span> unchecked = <span class="kw">await</span> uncheckedForm.next(); <span class="cmt">// true &mdash; no validation on the way</span>
1423
+ uncheckedForm.destroy();
1424
+ <span class="kw">return</span> [blocked, stayed, invalid, message, focused, fired, advanced, wentBack, unchecked].join('|');</code></pre>
1425
+
1334
1426
  <h3 id="form-validation-example">Required, a regex, a function, modes and the validated event, executed</h3>
1335
1427
  <p class="section-note">An empty required item fails <code>form.validate()</code> with its catalogue message (overridden here to French) and
1336
1428
  <code>aria-invalid</code>; a regex rule and a function rule each pass and fail through <code>item.validate()</code>; a
@@ -1431,7 +1523,11 @@ built[0].setValue({ email: 'changed@x.y' }); <span class="cmt">// a lat
1431
1523
 
1432
1524
  <h3 id="form-controls-example">Choice, toggle and date controls, executed</h3>
1433
1525
  <p class="section-note">A <code>select</code> renders its own trigger and drop-down listbox on the combo's listbox
1434
- (BACKLOG-0002168) and commits when an option is chosen &mdash; or keeps the native element with <code>native: true</code>;
1526
+ (BACKLOG-0002168) and commits when an option is chosen &mdash; or keeps the native element with <code>native: true</code>.
1527
+ The select's, the combo's and the date picker's popup all open in the same shared top-level layer, positioned
1528
+ against the field and never clipped by an ancestor's <code>overflow: hidden</code> or fixed height (BACKLOG-0002291):
1529
+ it flips above when there is no room below, follows the field on scroll and resize, and closes on an outside
1530
+ pointer down, Escape, Tab out or the field scrolling out of view;
1435
1531
  a <code>radiogroup</code> and a <code>checkboxgroup</code> commit on their options' change &mdash; laid out vertically by
1436
1532
  default, on one wrapping row with <code>orientation: 'horizontal'</code> (the radiogroup carries a matching
1437
1533
  <code>aria-orientation</code> and ArrowLeft/ArrowRight move between radios), or in an n-column grid with
@@ -1443,7 +1539,9 @@ built[0].setValue({ email: 'changed@x.y' }); <span class="cmt">// a lat
1443
1539
  <code>icon</code>, a round <code>avatar</code>, a <code>color</code> that tints them, and a <code>dot</code>
1444
1540
  colour shorthand &mdash; and both the list rows and the
1445
1541
  shown value accept <code>optionTemplate</code>/<code>selectedTemplate</code> renderers (a DOM node or an
1446
- HTML string, sanitised before insertion).</p>
1542
+ HTML string, sanitised before insertion). Values supplied in <code>options</code> keep their own type through
1543
+ <code>getValue()</code> and the <code>change</code> payload &mdash; choosing <code>{ value: 1 }</code> commits the
1544
+ number <code>1</code>, not the string <code>'1'</code> (BACKLOG-0002293).</p>
1447
1545
  <pre data-run="js" data-expect="m|green|tow|true|2026-06-15|2026-06-15T09:30|07:45|l|red|roof,tow|false|4|2026-06-01|2026-06-01|2026-07-04|23:59|true|l" data-covers="export:createForm method:form.getItem method:form.getValue method:form.setValue method:form.on method:form.destroy config:FormItemConfig.type config:FormItemConfig.options config:FormItemConfig.mode config:FormItemConfig.locale config:FormItemConfig.timeZone config:FormItemConfig.min config:FormItemConfig.max config:FormItemConfig.value config:FormItemConfig.native"><code>const { createTestDom, TestEvent } = await import('../packages/dom/src/renderer/testdom.js');
1448
1546
  const { createForm } = await import('../packages/modules/form/index.js');
1449
1547
 
@@ -1485,8 +1583,9 @@ boxes[0].dispatchEvent(new TestEvent('change', { bubbles: true }));
1485
1583
  const committed = [form.getValue().size, form.getValue().colour, form.getValue().extras.join(','), form.getValue().auto, changes].join('|');
1486
1584
 
1487
1585
  const dayItem = form.getItem('day');
1586
+ const dayPopup = dayItem.el.querySelector('.lat-form__datepicker-popup'); <span class="cmt">// captured before open: the popup moves into the shared layer host while open (BACKLOG-0002291)</span>
1488
1587
  dayItem.el.querySelector('.lat-form__datepicker-toggle').dispatchEvent(new TestEvent('click', { bubbles: true }));
1489
- dayItem.el.querySelector('.lat-form__datepicker-day').dispatchEvent(new TestEvent('click', { bubbles: true }));
1588
+ dayPopup.querySelector('.lat-form__datepicker-day').dispatchEvent(new TestEvent('click', { bubbles: true }));
1490
1589
  const clicked = form.getValue().day;
1491
1590
 
1492
1591
  const dayInput = form.getItem('day').el.querySelector('input');
@@ -2479,11 +2578,12 @@ shell.destroy();
2479
2578
  <tr><td class="name">labelPosition</td><td class="type"><a href="types.html#type-FormLabelPosition">FormLabelPosition</a> <small class="literals">'top' | 'left' | 'hidden'</small></td><td class="desc">The default label position every item inherits: `top`, `left` or `hidden`. An item's own `labelPosition` wins. <small>(optional)</small></td></tr>
2480
2579
  <tr><td class="name">labelWidth</td><td class="type">number | string</td><td class="desc">The default label column width every item inherits when its label is `left`; an item's own `labelWidth` wins. <small>(optional)</small></td></tr>
2481
2580
  <tr><td class="name">requiredHint</td><td class="type">boolean | string</td><td class="desc">The default marker a required field with no visible label shows on its placeholder: `true` appends `" *"`, a string resolves through the message catalogue, `false` shows nothing. An item's own `requiredHint` wins. <small>(optional)</small></td></tr>
2482
- <tr><td class="name">colsBreakpoint</td><td class="type">number</td><td class="desc">The width, in pixels, below which a `cols` container stacks its children into one column. Default 480. <small>(optional)</small></td></tr>
2581
+ <tr><td class="name">colsBreakpoint</td><td class="type">number | false</td><td class="desc">The width, in pixels, below which a `cols` container stacks its children into one column, or `false` (or `0`) to never stack. A positive number is used as-is. Default 480. <small>(optional)</small></td></tr>
2483
2582
  <tr><td class="name">bind</td><td class="type"><a href="types.html#type-FormBind">FormBind</a></td><td class="desc">Bind the form to a record. A grid row generates one item per column and saves through the grid's edit path; a routed record follows the route's keyed diffs; a plain object is initialised from and written back to. <small>(optional)</small></td></tr>
2484
2583
  <tr><td class="name">css</td><td class="type">Record&lt;string, string | number&gt; | string</td><td class="desc">Extra CSS on the form root. <small>(optional)</small></td></tr>
2485
2584
  <tr><td class="name">steps</td><td class="type"><a href="#type-FormStep">FormStep</a>[]</td><td class="desc">Wizard steps: one labelled group of rows shown at a time. <small>(optional)</small></td></tr>
2486
2585
  <tr><td class="name">stepOrientation</td><td class="type"><a href="types.html#type-FormStepOrientation">FormStepOrientation</a> <small class="literals">'horizontal' | 'vertical'</small></td><td class="desc">The step indicator's orientation: `horizontal` (default) or `vertical`. <small>(optional)</small></td></tr>
2586
+ <tr><td class="name">validateSteps</td><td class="type">boolean</td><td class="desc">Whether a forward step move validates the current step first (`true`, the default). Set `false` to run `validate()` yourself and navigate unchecked. <small>(optional)</small></td></tr>
2487
2587
  <tr><td class="name">messages</td><td class="type">{ t(key: string, params?: object): string }</td><td class="desc">A `t(key, params)` catalogue for the separator's accessible label. <small>(optional)</small></td></tr>
2488
2588
  </tbody>
2489
2589
  </table>
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Gantt API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Gantt API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Geometry API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Geometry API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Grid API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Grid API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -1912,7 +1912,7 @@ grid.destroy();
1912
1912
  <table>
1913
1913
  <thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
1914
1914
  <tbody>
1915
- <tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.101.2'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
1915
+ <tr><td class="sig">getVersion()</td><td class="type">string</td><td class="desc">The version this grid came from, e.g. <code>'1.101.4'</code>. Also on the module as <code>getVersion()</code>, for when you have no grid to hand.</td></tr>
1916
1916
  <tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
1917
1917
  <tr><td class="sig">set(key, value)</td><td class="type">void</td><td class="desc">Write one key. Every key is live; nothing needs a rebuild.</td></tr>
1918
1918
  <tr><td class="sig">setAll(values)</td><td class="type">void</td><td class="desc">Write several in one pass. Emits one <code>config:changed</code> for the batch, not one per key.</td></tr>
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>API Reference — Lattice Grid 1.101.2</title>
16
+ <title>API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -455,7 +455,7 @@
455
455
  <tr><td class="name"><a href="tabs.html">The tabbed grid</a></td><td class="desc"><code>createTabs</code>: a tab strip where each tab holds a grid, a board, a KPI strip or a Gantt, and the tablist keyboard model.</td><td class="type">1</td><td class="type">7</td><td class="type">57</td></tr>
456
456
  <tr><td class="name"><a href="layout.html">The dashboard layout</a></td><td class="desc"><code>createLayout</code>: windows a reader moves, resizes and closes, the arrangement saved as plain JSON, locking, and the cancellable gestures.</td><td class="type">1</td><td class="type">15</td><td class="type">133</td></tr>
457
457
  <tr><td class="name"><a href="shell.html">The shell module</a></td><td class="desc"><code>createShell</code>: cells nested as rows and columns to any depth, sized by flexbox so the DOM is complete and measured the moment the factory returns.</td><td class="type">1</td><td class="type">25</td><td class="type">153</td></tr>
458
- <tr><td class="name"><a href="form.html">The form module</a></td><td class="desc"><code>createForm</code>: named items laid out in nested rows and columns, themed by the grid's own density tokens, so the DOM is complete and measured the moment the factory returns.</td><td class="type">1</td><td class="type">40</td><td class="type">379</td></tr>
458
+ <tr><td class="name"><a href="form.html">The form module</a></td><td class="desc"><code>createForm</code>: named items laid out in nested rows and columns, themed by the grid's own density tokens, so the DOM is complete and measured the moment the factory returns.</td><td class="type">1</td><td class="type">40</td><td class="type">380</td></tr>
459
459
  <tr><td class="name"><a href="dashboard.html">Dashboard composition</a></td><td class="desc"><code>createDashboard</code>: a dashboard described as a JSON spec &mdash; a layout, grid, chart, KPI, map and html panels, a source per panel, links between them &mdash; built into the wired page, handed back by <code>spec()</code>, and proposed by the AI.</td><td class="type">1</td><td class="type">15</td><td class="type">81</td></tr>
460
460
  <tr><td class="name"><a href="designer.html">The Designer</a></td><td class="desc"><code>createDesigner</code>: a dashboard designer whose state is the dashboard spec extended with pages, in edit and view mode, with JSON state in and out.</td><td class="type">1</td><td class="type">63</td><td class="type">406</td></tr>
461
461
  <tr><td class="name"><a href="ai.html">The AI narrative and insights layer</a></td><td class="desc">The narrative module: summaries, insights and risk text over what the grid already holds. The <code>grid.ai</code> intent layer is on the grid page.</td><td class="type">1</td><td class="type">17</td><td class="type">124</td></tr>
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Kanban API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Kanban API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
package/docs/api/kpi.html CHANGED
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>KPI API Reference — Lattice Grid 1.101.2</title>
16
+ <title>KPI API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Layout API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Layout API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Leaflet API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Leaflet API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>List API Reference — Lattice Grid 1.101.2</title>
16
+ <title>List API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Map view API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Map view API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Mock socket API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Mock socket API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Presets API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Presets API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Scheduler API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Scheduler API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Shell API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Shell API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Tabs API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Tabs API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an
@@ -13,7 +13,7 @@
13
13
  Self-contained: no stylesheet, script or font is fetched, so this opens from disk, from
14
14
  a file share or from behind a firewall with nothing else present.
15
15
  -->
16
- <title>Types API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Types API Reference — Lattice Grid 1.101.4</title>
17
17
  <style>
18
18
  /* ---------------------------------------------------------------
19
19
  Palette lifted from the grid's own theme, so the reference reads as an