@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
package/CHANGELOG.md CHANGED
@@ -9,6 +9,40 @@ and what it means for a grid already in production.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.101.4] - 2026-10-14
13
+
14
+ ### Fixed
15
+
16
+ - Form select, combo and date picker lists open above whatever contains the field. Each open list is placed in the browser's top layer, so a slide-out, floating window, overlay or modal dialog with its own `z-index` never covers it, and it also takes the `--lattice-popup-z` stacking order (default: the maximum) for browsers without a top layer; a page that needs something of its own above an open list lowers that token (BACKLOG-0002336).
17
+
18
+ - A form select shows the option whose value is the empty string. A field whose value is `''` now selects a `{ value: '', label: '-- Select --' }` choice from config or from `setValue()` and the box shows its label, instead of staying blank; when no option has the empty string as its value, `''` still means nothing chosen and the placeholder shows. With `native: true` the control adds its own blank entry only when the options carry none, so the list never shows two empty rows (BACKLOG-0002337).
19
+
20
+ - A form colour item lays out correctly on a page with only the Lattice stylesheet: the swatch and the dropdown toggle are control-sized, the hex input takes the rest of the field, and nothing overflows the field's edge. The whole-control button rule now styles only a button that is the control itself, and a control's own part buttons keep their size (BACKLOG-0002338).
21
+
22
+ ## [1.101.3] - 2026-10-14
23
+
24
+ ### Fixed
25
+
26
+ - **A form wizard's forward moves now validate the current step exactly as `form.validate()` does** (BACKLOG-0002290). `next()`, `goToStep()` to a later step and a click on a later indicator step previously checked only `requiredWhen` and a control's own hook, so a step with a plain `required` item (or a `validation` rule) advanced unchecked. Now a forward move runs the same rule engine as `validate()`: the first invalid item is focused and painted with the same `aria-invalid` and message, the `validated` event fires, and the move resolves `false` and stays put. A backward move (`back()`, `goToStep()` to an earlier step, an earlier indicator step) still never validates, and on the last step `next()` validates in place, resolving `false` while it is invalid.
27
+ - **Opt-out.** `validateSteps: false` on the form config restores the unchecked navigation for an integrator that runs `validate()` itself.
28
+
29
+ - **The select and combo options list, and the date picker's calendar, no longer get clipped by a parent's `overflow: hidden` or fixed height** (BACKLOG-0002291). Before this card the popup rendered `position: absolute` inside its own control, so a 60px toolbar cell, a filter bar or a scrolling panel cut the list off and the content below painted over it. <!-- not-breaking: a clipping bug fix; no option, type or committed behaviour changed -->
30
+ - The popup now opens in a shared, document-level layer host, positioned `fixed` against the field's own rectangle: it flips above when there is no room below, follows the field on scroll and resize while open, and closes on an outside pointer down, Escape, Tab out, or the field scrolling out of view.
31
+ - ARIA (`aria-controls`, `aria-activedescendant`), keyboard navigation, type-ahead, focus return to the field, touch, RTL and the dark theme keep working; the popup keeps its existing class names, so theming and the preset gate are unchanged; `native: true` is unchanged.
32
+ - In a right-to-left document, the popup now positions against the field's own rectangle instead of the viewport edge: positioning clears the stylesheet's `right` alongside the inline `left` it sets, so the two no longer over-constrain the box's fixed position.
33
+ - The form's own `destroy()` restores and removes any popup left open.
34
+
35
+ - **A form `colsBreakpoint` of `0` or `false` now turns stacking off, and a full-width button keeps sharing a cols row** (BACKLOG-0002292). `resolveColsBreakpoint` used to drop `0` (and `false`) onto the 480px default, so a `cols` container still stacked on a narrow form and integrators wrote `1` to get a row that never stacks; `0` and `false` are now honoured as "never stack", a positive number keeps the old behaviour and the default stays 480px.
36
+ - A `fullWidth: true` button inside a `cols` row fills its own column while the columns keep sharing the row, so two full-width buttons sit side by side at half the width each and stack only when the breakpoint applies; outside a `cols` row `fullWidth` is unchanged. The option's type is now `number | false`.
37
+
38
+ - **A `select` now returns the chosen option's own typed value instead of the string it renders** (BACKLOG-0002293). Before this card, options written as numbers, booleans or `null` came back from `getValue()` as strings, because the control read the element's string value and never mapped it back to the option. <!-- not-breaking: string options and a value absent from options behave as before; the change payload and setValue now resolve through the options the host supplied -->
39
+ - `getValue()` looks the committed entry up in the item's `options`, so a chosen `{ value: 1 }` returns the number `1`, `{ value: true }` returns `true`, and `{ value: null }` returns `null`; a multi select returns an array of those typed values, and the `change` event payload carries the same typed value.
40
+ - `setValue(1)` and `setValue('1')` both select the option whose own value is `1` &mdash; a loose match on the string form &mdash; and the option's value wins; string options are unchanged, and a value absent from `options` behaves as today.
41
+ - This covers the styled select, `native: true`, and the multi select alike; docs note that option values keep their type.
42
+
43
+ - **The form date picker's hour and minute boxes dropped leading zeros** (BACKLOG-0002294). A value such as 21:05 drew the hour box as "21" and the minute box as "5", and a single digit typed into a box stayed as typed after the box was left, even though the field's own text and the committed value were already correct. The two boxes now always draw two digits (05, 09, 00) whenever they are drawn from a value or after a commit, while a box still shows exactly what was typed until it is left.
44
+ - **Unchanged.** The committed value, the field's formatted text and the min/max fencing behave exactly as before.
45
+
12
46
  ## [1.101.2] - 2026-10-13
13
47
 
14
48
  ### Changed
package/README.md CHANGED
@@ -7,7 +7,7 @@ tiles and map views, all reading the same rows, filters and theme. Vanilla
7
7
  JavaScript, no runtime dependencies, no build step; adapters for React, Vue,
8
8
  Svelte, Web Components and htmx, Angular compiled inside the package.
9
9
 
10
- Version 1.101.2 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
10
+ Version 1.101.4 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
11
11
 
12
12
  ---
13
13
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.101.2",
2
+ "version": "1.101.4",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
5
  "module": "./fesm2022/toclocoinc-lattice-grid-angular.mjs",
package/docs/API.html CHANGED
@@ -382,7 +382,7 @@
382
382
  <div class="shell">
383
383
  <aside class="rail">
384
384
  <p class="rail__brand">Lattice Grid</p>
385
- <p class="rail__sub">API reference · v1.101.2</p>
385
+ <p class="rail__sub">API reference · v1.101.4</p>
386
386
  <nav>
387
387
  <div class="rail__group">
388
388
  <span class="rail__label">Start</span>
@@ -584,7 +584,7 @@
584
584
  </header>
585
585
 
586
586
  <p class="chips">
587
- <span class="chip">Version 1.101.2</span>
587
+ <span class="chip">Version 1.101.4</span>
588
588
  <span class="chip">Zero dependencies</span>
589
589
  <span class="chip"><a href="api-detail.html">Developer guide &rarr;</a></span>
590
590
  </p>
@@ -3506,7 +3506,7 @@ grid.destroy();
3506
3506
  <table>
3507
3507
  <thead><tr><th>Member</th><th>Returns</th><th>Description</th></tr></thead>
3508
3508
  <tbody>
3509
- <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>
3509
+ <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>
3510
3510
  <tr><td class="sig">get(key)</td><td class="type">unknown</td><td class="desc">Read any configuration key.</td></tr>
3511
3511
  <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>
3512
3512
  <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>
@@ -16035,7 +16035,7 @@ form.getValue(); <span class="cmt">// { name: 'Grace', a
16035
16035
  form.on('beforeChange', (e) =&gt; { if (e.name === 'age' &amp;&amp; e.value &gt; 120) e.preventDefault(); });
16036
16036
  form.setDensity('comfortable'); <span class="cmt">// control heights follow the grid's row height</span>
16037
16037
  form.destroy();</code></pre>
16038
- <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>
16038
+ <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>
16039
16039
  <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>
16040
16040
  <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>
16041
16041
  <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>
@@ -16045,10 +16045,10 @@ form.destroy();</code></pre>
16045
16045
  <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>
16046
16046
  <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>
16047
16047
  <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>
16048
- <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>
16048
+ <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>
16049
16049
  <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>
16050
16050
  <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>
16051
- <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>
16051
+ <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>
16052
16052
  <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>
16053
16053
  <div class="table-wrap">
16054
16054
  <table>
@@ -16067,7 +16067,8 @@ form.destroy();</code></pre>
16067
16067
  <table>
16068
16068
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
16069
16069
  <tbody>
16070
- <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>
16070
+ <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>
16071
+ <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>
16071
16072
  <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>
16072
16073
  <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>
16073
16074
  <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>
@@ -16086,9 +16087,9 @@ form.destroy();</code></pre>
16086
16087
  <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>
16087
16088
  <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>
16088
16089
  <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>
16089
- <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>
16090
+ <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>
16090
16091
  <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>
16091
- <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>
16092
+ <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>
16092
16093
  <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>
16093
16094
  </tbody>
16094
16095
  </table>
@@ -16224,6 +16225,46 @@ form.destroy();
16224
16225
  form.destroy();
16225
16226
  <span class="kw">return</span> widths;</code></pre>
16226
16227
 
16228
+ <h3 id="form-cols-breakpoint-example">Cols breakpoint and full-width buttons, executed</h3>
16229
+ <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>
16230
+ <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');
16231
+ <span class="kw">const</span> { createForm } = <span class="kw">await</span> import('../packages/modules/form/index.js');
16232
+
16233
+ <span class="kw">const</span> { root } = createTestDom();
16234
+ <span class="kw">const</span> form = createForm(root, {
16235
+ colsBreakpoint: <span class="kw">false</span>, <span class="cmt">// false (or 0): never stack</span>
16236
+ cols: [
16237
+ { type: <span class="str">'button'</span>, name: <span class="str">'ok'</span>, label: <span class="str">'OK'</span>, fullWidth: <span class="kw">true</span> },
16238
+ { type: <span class="str">'button'</span>, name: <span class="str">'cancel'</span>, label: <span class="str">'Cancel'</span>, fullWidth: <span class="kw">true</span> },
16239
+ ],
16240
+ });
16241
+ <span class="kw">const</span> neverFalse = root.style.getPropertyValue('--lattice-cols-container'); <span class="cmt">// 'lat-cols-never'</span>
16242
+ <span class="kw">const</span> okFull = form.getItem('ok').el.querySelector('button').getAttribute('data-full'); <span class="cmt">// 'true'</span>
16243
+ <span class="kw">const</span> cancelFull = form.getItem('cancel').el.querySelector('button').getAttribute('data-full'); <span class="cmt">// 'true'</span>
16244
+ form.destroy();
16245
+
16246
+ <span class="kw">const</span> zero = createForm(root, {
16247
+ colsBreakpoint: 0, <span class="cmt">// 0 means never stack too</span>
16248
+ cols: [
16249
+ { type: <span class="str">'input'</span>, name: <span class="str">'a'</span>, label: <span class="str">'A'</span> },
16250
+ { type: <span class="str">'input'</span>, name: <span class="str">'b'</span>, label: <span class="str">'B'</span> },
16251
+ ],
16252
+ });
16253
+ <span class="kw">const</span> neverZero = root.style.getPropertyValue('--lattice-cols-container'); <span class="cmt">// 'lat-cols-never'</span>
16254
+ zero.destroy();
16255
+
16256
+ <span class="kw">const</span> positive = createForm(root, {
16257
+ colsBreakpoint: 200, <span class="cmt">// a positive number keeps the old behaviour</span>
16258
+ cols: [
16259
+ { type: <span class="str">'input'</span>, name: <span class="str">'c'</span>, label: <span class="str">'C'</span> },
16260
+ { type: <span class="str">'input'</span>, name: <span class="str">'d'</span>, label: <span class="str">'D'</span> },
16261
+ ],
16262
+ });
16263
+ <span class="kw">const</span> positiveBp = root.style.getPropertyValue('--lattice-cols-container'); <span class="cmt">// 'lat-cols-bp-200'</span>
16264
+ positive.destroy();
16265
+
16266
+ <span class="kw">return</span> [neverFalse, okFull, cancelFull, neverZero, positiveBp].join('|');</code></pre>
16267
+
16227
16268
  <h3 id="form-disabled-readonly-example">Disabled and read-only, executed</h3>
16228
16269
  <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>
16229
16270
  <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');
@@ -16438,7 +16479,7 @@ form.destroy();
16438
16479
  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>
16439
16480
 
16440
16481
  <h3 id="combo">The combo control</h3>
16441
- <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>
16482
+ <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>
16442
16483
  <div class="table-wrap">
16443
16484
  <table>
16444
16485
  <thead><tr><th>Member</th><th>Description</th></tr></thead>
@@ -16487,8 +16528,9 @@ const sized = createCombo(sizedHost, {
16487
16528
  options: [{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }],
16488
16529
  optionTemplate: (opt) =&gt; 'T:' + opt.label,
16489
16530
  });
16531
+ 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>
16490
16532
  sized.open();
16491
- const renderedRow = Array.from(sized.root.querySelectorAll('.lat-combo__option')).find((r) =&gt; r.textContent === 'T:Alpha');
16533
+ const renderedRow = Array.from(sizedPopup.querySelectorAll('.lat-combo__option')).find((r) =&gt; r.textContent === 'T:Alpha');
16492
16534
  const rendered = renderedRow.textContent; <span class="cmt">// 'T:Alpha' &mdash; the optionTemplate applied</span>
16493
16535
 
16494
16536
  // A static combo with a real 'user' SVG icon, an initials avatar and a
@@ -16502,8 +16544,9 @@ const deco = createCombo(decoHost, {
16502
16544
  ],
16503
16545
  selectedTemplate: (opt) =&gt; 'S:' + opt.label,
16504
16546
  });
16547
+ 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>
16505
16548
  deco.open();
16506
- const decoRows = Array.from(deco.root.querySelectorAll('.lat-combo__option'));
16549
+ const decoRows = Array.from(decoPopup.querySelectorAll('.lat-combo__option'));
16507
16550
  const hasIcon = decoRows.some((r) =&gt; r.querySelector('.lat-combo__icon') !== null); <span class="cmt">// true &mdash; the 'user' SVG</span>
16508
16551
  const hasAvatar = decoRows.some((r) =&gt; r.querySelector('.lat-combo__avatar') !== null); <span class="cmt">// true &mdash; round initials</span>
16509
16552
  deco.setValue('u');
@@ -16873,6 +16916,53 @@ form.setValue({ rating: '5' }); <span class="cmt">// re-evalua
16873
16916
  form.destroy();
16874
16917
  <span class="kw">return</span> [blocked, advanced, active, custom, noteHidden, reviewReadOnly, wentBack, jumped, finish, vertical].join('|');</code></pre>
16875
16918
 
16919
+ <h3 id="form-step-validation-example">Step validation, the <code>validateSteps</code> opt-out and the refused move, executed</h3>
16920
+ <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
16921
+ step &mdash; validates the current step's items exactly as <code>form.validate()</code> does: a <code>required</code> item
16922
+ left empty refuses the move, resolves <code>false</code>, paints the same <code>aria-invalid</code> and message, fires
16923
+ <code>validated</code> and focuses the first invalid item, while <code>back()</code> never validates. <code>validateSteps:
16924
+ false</code> restores the unchecked navigation &mdash; with the real Chrome indicator-click assertions in
16925
+ <code>test/form-steps-validate-2290-browser.test.js</code>.</p>
16926
+ <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');
16927
+ <span class="kw">const</span> { createForm } = <span class="kw">await</span> import('../packages/modules/form/index.js');
16928
+
16929
+ <span class="kw">const</span> { root } = createTestDom();
16930
+ <span class="kw">let</span> validated = 0;
16931
+ <span class="kw">const</span> form = createForm(root, {
16932
+ steps: [
16933
+ { id: 'a', label: 'A', rows: [
16934
+ { type: 'input', name: 'x', label: 'X', required: true },
16935
+ { type: 'input', name: 'y', label: 'Y' },
16936
+ ] },
16937
+ { id: 'b', label: 'B', rows: [ { type: 'checkbox', name: 'done', label: 'Done', required: true } ] },
16938
+ ],
16939
+ });
16940
+ form.on('validated', () =&gt; { validated += 1; });
16941
+
16942
+ <span class="kw">const</span> blocked = <span class="kw">await</span> form.next(); <span class="cmt">// false &mdash; x is required and empty</span>
16943
+ <span class="kw">const</span> stayed = form.activeStep; <span class="cmt">// 0 &mdash; the move was refused</span>
16944
+ <span class="kw">const</span> xInput = form.getItem('x').el.querySelector('input');
16945
+ <span class="kw">const</span> invalid = xInput.getAttribute('aria-invalid'); <span class="cmt">// 'true' &mdash; as validate() paints it</span>
16946
+ <span class="kw">const</span> message = form.getItem('x').el.querySelector('.lat-form__validation').textContent; <span class="cmt">// 'This field is required.'</span>
16947
+ <span class="kw">const</span> focused = root.ownerDocument.activeElement === xInput; <span class="cmt">// focus lands on the first invalid item</span>
16948
+ <span class="kw">const</span> fired = validated &gt; 0; <span class="cmt">// the refused move emitted `validated`</span>
16949
+
16950
+ form.getItem('x').setValue('Ada'); <span class="cmt">// fill the required item</span>
16951
+ <span class="kw">const</span> advanced = <span class="kw">await</span> form.next(); <span class="cmt">// true &mdash; step one now validates</span>
16952
+ <span class="kw">const</span> wentBack = <span class="kw">await</span> form.back(); <span class="cmt">// true &mdash; back never validates</span>
16953
+ form.destroy();
16954
+
16955
+ <span class="kw">const</span> uncheckedForm = createForm(root, {
16956
+ validateSteps: <span class="kw">false</span>, <span class="cmt">// the integrator runs validate() itself</span>
16957
+ steps: [
16958
+ { id: 'a', label: 'A', rows: [ { type: 'input', name: 'x', label: 'X', required: true } ] },
16959
+ { id: 'b', label: 'B', rows: [] },
16960
+ ],
16961
+ });
16962
+ <span class="kw">const</span> unchecked = <span class="kw">await</span> uncheckedForm.next(); <span class="cmt">// true &mdash; no validation on the way</span>
16963
+ uncheckedForm.destroy();
16964
+ <span class="kw">return</span> [blocked, stayed, invalid, message, focused, fired, advanced, wentBack, unchecked].join('|');</code></pre>
16965
+
16876
16966
  <h3 id="form-validation-example">Required, a regex, a function, modes and the validated event, executed</h3>
16877
16967
  <p class="section-note">An empty required item fails <code>form.validate()</code> with its catalogue message (overridden here to French) and
16878
16968
  <code>aria-invalid</code>; a regex rule and a function rule each pass and fail through <code>item.validate()</code>; a
@@ -16973,7 +17063,11 @@ built[0].setValue({ email: 'changed@x.y' }); <span class="cmt">// a lat
16973
17063
 
16974
17064
  <h3 id="form-controls-example">Choice, toggle and date controls, executed</h3>
16975
17065
  <p class="section-note">A <code>select</code> renders its own trigger and drop-down listbox on the combo's listbox
16976
- (BACKLOG-0002168) and commits when an option is chosen &mdash; or keeps the native element with <code>native: true</code>;
17066
+ (BACKLOG-0002168) and commits when an option is chosen &mdash; or keeps the native element with <code>native: true</code>.
17067
+ The select's, the combo's and the date picker's popup all open in the same shared top-level layer, positioned
17068
+ against the field and never clipped by an ancestor's <code>overflow: hidden</code> or fixed height (BACKLOG-0002291):
17069
+ it flips above when there is no room below, follows the field on scroll and resize, and closes on an outside
17070
+ pointer down, Escape, Tab out or the field scrolling out of view;
16977
17071
  a <code>radiogroup</code> and a <code>checkboxgroup</code> commit on their options' change &mdash; laid out vertically by
16978
17072
  default, on one wrapping row with <code>orientation: 'horizontal'</code> (the radiogroup carries a matching
16979
17073
  <code>aria-orientation</code> and ArrowLeft/ArrowRight move between radios), or in an n-column grid with
@@ -16985,7 +17079,9 @@ built[0].setValue({ email: 'changed@x.y' }); <span class="cmt">// a lat
16985
17079
  <code>icon</code>, a round <code>avatar</code>, a <code>color</code> that tints them, and a <code>dot</code>
16986
17080
  colour shorthand &mdash; and both the list rows and the
16987
17081
  shown value accept <code>optionTemplate</code>/<code>selectedTemplate</code> renderers (a DOM node or an
16988
- HTML string, sanitised before insertion).</p>
17082
+ HTML string, sanitised before insertion). Values supplied in <code>options</code> keep their own type through
17083
+ <code>getValue()</code> and the <code>change</code> payload &mdash; choosing <code>{ value: 1 }</code> commits the
17084
+ number <code>1</code>, not the string <code>'1'</code> (BACKLOG-0002293).</p>
16989
17085
  <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');
16990
17086
  const { createForm } = await import('../packages/modules/form/index.js');
16991
17087
 
@@ -17027,8 +17123,9 @@ boxes[0].dispatchEvent(new TestEvent('change', { bubbles: true }));
17027
17123
  const committed = [form.getValue().size, form.getValue().colour, form.getValue().extras.join(','), form.getValue().auto, changes].join('|');
17028
17124
 
17029
17125
  const dayItem = form.getItem('day');
17126
+ 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>
17030
17127
  dayItem.el.querySelector('.lat-form__datepicker-toggle').dispatchEvent(new TestEvent('click', { bubbles: true }));
17031
- dayItem.el.querySelector('.lat-form__datepicker-day').dispatchEvent(new TestEvent('click', { bubbles: true }));
17128
+ dayPopup.querySelector('.lat-form__datepicker-day').dispatchEvent(new TestEvent('click', { bubbles: true }));
17032
17129
  const clicked = form.getValue().day;
17033
17130
 
17034
17131
  const dayInput = form.getItem('day').el.querySelector('input');
@@ -32548,11 +32645,12 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
32548
32645
  <tr><td class="name">labelPosition</td><td class="type"><a href="#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>
32549
32646
  <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>
32550
32647
  <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>
32551
- <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>
32648
+ <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>
32552
32649
  <tr><td class="name">bind</td><td class="type"><a href="#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>
32553
32650
  <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>
32554
32651
  <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>
32555
32652
  <tr><td class="name">stepOrientation</td><td class="type"><a href="#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>
32653
+ <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>
32556
32654
  <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>
32557
32655
  </tbody>
32558
32656
  </table>
@@ -38720,7 +38818,7 @@ return `next ${p.mean.toFixed(1)}; r2 ${f.r2.toFixed(1)}; band ${p.upper - p.low
38720
38818
  <!-- END GENERATED TYPE REFERENCE -->
38721
38819
 
38722
38820
  <footer>
38723
- Lattice Grid 1.101.2 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
38821
+ Lattice Grid 1.101.4 · Copyright © 2026 TOCLOCO Inc. All rights reserved.
38724
38822
  This document describes the behaviour of the shipped library. Where this guide and the code
38725
38823
  disagree, the code wins: please <a href="https://www.latticegrid.dev">tell us</a>.
38726
38824
  </footer>
package/docs/THEMING.md CHANGED
@@ -387,7 +387,7 @@ The Designer module's editing chrome (rails, panels and the properties form) dra
387
387
 
388
388
  ### Form module tokens (module-owned, fallback-only)
389
389
 
390
- The form module (`modules/form`, BACKLOG-0002141, spacing BACKLOG-0002183, disabled/read-only states BACKLOG-0002198) reads these names only from its own injected stylesheet — they are never declared in the theme CSS above, so they are not part of the machine-checked public-token list and a preset sets them only for closer fidelity. `createForm({ spacing, labelGap })` and `form.setSpacing()` write the two spacing names as inline custom properties on the form root, which always win over a stylesheet, so a theme sets them only as the default a form without an explicit spacing falls back to. The two spacing names default from the density scale; the six disabled/read-only names are derived from the colour tokens so they follow light and dark automatically. All eight are **MAY**: `checkPresetCompleteness` never requires a preset edit for them.
390
+ The form module (`modules/form`, BACKLOG-0002141, spacing BACKLOG-0002183, disabled/read-only states BACKLOG-0002198) reads these names only from its own injected stylesheet — they are never declared in the theme CSS above, so they are not part of the machine-checked public-token list and a preset sets them only for closer fidelity. `createForm({ spacing, labelGap })` and `form.setSpacing()` write the two spacing names as inline custom properties on the form root, which always win over a stylesheet, so a theme sets them only as the default a form without an explicit spacing falls back to. The two spacing names default from the density scale; the six disabled/read-only names are derived from the colour tokens so they follow light and dark automatically; the popup stacking order is a plain number. All nine are **MAY**: `checkPresetCompleteness` never requires a preset edit for them.
391
391
 
392
392
  | Token | Default (the fallback) | Affects |
393
393
  |---|---|---|
@@ -399,6 +399,7 @@ The form module (`modules/form`, BACKLOG-0002141, spacing BACKLOG-0002183, disab
399
399
  | `--lattice-readonly-foreground` | `var(--lattice-foreground)` | the text of a read-only field (fully legible, so a value can still be read and copied) |
400
400
  | `--lattice-readonly-background` | `color-mix(in srgb, var(--lattice-background) 92%, var(--lattice-foreground-muted))` | the subtle fill of a read-only field |
401
401
  | `--lattice-readonly-border` | `var(--lattice-border-color)` | the border of a read-only field |
402
+ | `--lattice-popup-z` | `2147483647` | the stacking order of an open select, combo or date-picker list while it sits in the page-level layer host; a page that needs its own element above an open list sets this lower (BACKLOG-0002336). Where the browser has a top layer the list is placed there as well, which no `z-index` can cover |
402
403
 
403
404
  ## Internal, off-contract
404
405
 
@@ -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>Adapters API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Adapters 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/ai.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>AI API Reference — Lattice Grid 1.101.2</title>
16
+ <title>AI 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>Alarms API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Alarms 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>Calendar API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Calendar 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>Charts API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Charts 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>Dashboard API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Dashboard 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>Data router API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Data router 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>deck.gl API Reference — Lattice Grid 1.101.2</title>
16
+ <title>deck.gl 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>Designer API Reference — Lattice Grid 1.101.2</title>
16
+ <title>Designer 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