@livenetworks/ashlar 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/LICENSE +641 -0
  2. package/README.md +202 -86
  3. package/{js → components}/COMPONENTS.md +1116 -1108
  4. package/{js → components}/index.js +81 -58
  5. package/{js → components}/ln-accordion/README.md +7 -6
  6. package/components/ln-accordion/ln-accordion-dev.scss +8 -0
  7. package/components/ln-accordion/ln-accordion.js +1 -0
  8. package/components/ln-accordion/ln-accordion.schema.json +30 -0
  9. package/components/ln-accordion/ln-accordion.scss +8 -0
  10. package/{js → components}/ln-accordion/src/ln-accordion.js +8 -2
  11. package/{js → components}/ln-ajax/README.md +171 -160
  12. package/components/ln-ajax/ln-ajax-dev.scss +8 -0
  13. package/components/ln-ajax/ln-ajax.js +1 -0
  14. package/components/ln-ajax/ln-ajax.schema.json +30 -0
  15. package/{js → components}/ln-ajax/src/ln-ajax.js +65 -30
  16. package/{js → components}/ln-api-connector/README.md +27 -5
  17. package/components/ln-api-connector/ln-api-connector.js +1 -0
  18. package/components/ln-api-connector/ln-api-connector.schema.json +96 -0
  19. package/components/ln-api-connector/src/connector-core.js +97 -0
  20. package/components/ln-api-connector/src/ln-api-connector.js +447 -0
  21. package/{js → components}/ln-api-queue/README.md +12 -9
  22. package/components/ln-api-queue/ln-api-queue.js +1 -0
  23. package/components/ln-api-queue/ln-api-queue.schema.json +34 -0
  24. package/{js → components}/ln-api-queue/src/ln-api-queue.js +33 -14
  25. package/{js → components}/ln-api-queue/src/queue-storage.js +7 -3
  26. package/{js → components}/ln-autoresize/README.md +129 -127
  27. package/components/ln-autoresize/ln-autoresize-dev.scss +8 -0
  28. package/components/ln-autoresize/ln-autoresize.js +1 -0
  29. package/components/ln-autoresize/ln-autoresize.schema.json +22 -0
  30. package/{js → components}/ln-autoresize/src/ln-autoresize.js +8 -1
  31. package/{js → components}/ln-autosave/README.md +127 -124
  32. package/components/ln-autosave/ln-autosave-dev.scss +13 -0
  33. package/components/ln-autosave/ln-autosave.js +1 -0
  34. package/components/ln-autosave/ln-autosave.schema.json +48 -0
  35. package/components/ln-autosave/src/autosave-model.js +34 -0
  36. package/{js → components}/ln-autosave/src/ln-autosave.js +65 -58
  37. package/components/ln-chart/README.md +145 -0
  38. package/components/ln-chart/ln-chart-dev.scss +20 -0
  39. package/components/ln-chart/ln-chart.js +1 -0
  40. package/components/ln-chart/ln-chart.schema.json +146 -0
  41. package/components/ln-chart/ln-chart.scss +8 -0
  42. package/components/ln-chart/src/chart-model.js +154 -0
  43. package/components/ln-chart/src/ln-chart.js +176 -0
  44. package/{js → components}/ln-circular-progress/README.md +15 -14
  45. package/components/ln-circular-progress/ln-circular-progress-dev.scss +8 -0
  46. package/components/ln-circular-progress/ln-circular-progress.js +1 -0
  47. package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -0
  48. package/{js → components}/ln-circular-progress/src/ln-circular-progress.js +32 -50
  49. package/components/ln-confirm/README.md +216 -0
  50. package/components/ln-confirm/ln-confirm-dev.scss +24 -0
  51. package/components/ln-confirm/ln-confirm.js +1 -0
  52. package/components/ln-confirm/ln-confirm.schema.json +66 -0
  53. package/components/ln-confirm/ln-confirm.scss +24 -0
  54. package/{js → components}/ln-confirm/src/ln-confirm.js +180 -167
  55. package/{js → components}/ln-core/README.md +374 -149
  56. package/components/ln-core/attrs.js +273 -0
  57. package/components/ln-core/compare.js +40 -0
  58. package/components/ln-core/crypto.js +265 -0
  59. package/components/ln-core/date.js +50 -0
  60. package/components/ln-core/dom.js +115 -0
  61. package/components/ln-core/events.js +122 -0
  62. package/components/ln-core/form.js +136 -0
  63. package/{js → components}/ln-core/hash.js +149 -92
  64. package/components/ln-core/helpers.js +33 -0
  65. package/components/ln-core/http.js +48 -0
  66. package/components/ln-core/index.js +11 -0
  67. package/components/ln-core/lifecycle.js +403 -0
  68. package/components/ln-core/ln-core.schema.json +109 -0
  69. package/components/ln-core/locale.js +55 -0
  70. package/components/ln-core/matching.js +78 -0
  71. package/components/ln-core/number.js +125 -0
  72. package/components/ln-core/progress.js +30 -0
  73. package/components/ln-core/template.js +206 -0
  74. package/{js → components}/ln-core/window-cache.js +247 -213
  75. package/{js → components}/ln-couchdb-connector/README.md +7 -5
  76. package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
  77. package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -0
  78. package/{js → components}/ln-couchdb-connector/src/ln-couchdb-connector.js +29 -43
  79. package/{js → components}/ln-data-coordinator/README.md +109 -73
  80. package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -0
  81. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -0
  82. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +196 -0
  83. package/components/ln-data-coordinator/src/data-read-policy.js +41 -0
  84. package/{js → components}/ln-data-coordinator/src/ln-data-coordinator.js +411 -154
  85. package/{js → components}/ln-data-coordinator/src/mutation-receipts.js +0 -5
  86. package/{js → components}/ln-data-store/README.md +42 -17
  87. package/components/ln-data-store/ln-data-store-dev.scss +13 -0
  88. package/components/ln-data-store/ln-data-store.js +1 -0
  89. package/components/ln-data-store/ln-data-store.schema.json +84 -0
  90. package/components/ln-data-store/src/data-store-model.js +155 -0
  91. package/{js → components}/ln-data-store/src/ln-data-store.js +407 -105
  92. package/components/ln-data-store/src/window-index.js +119 -0
  93. package/{js → components}/ln-date/README.md +17 -7
  94. package/{js → components}/ln-date/ln-date-dev.scss +14 -14
  95. package/components/ln-date/ln-date.js +1 -0
  96. package/components/ln-date/ln-date.schema.json +92 -0
  97. package/components/ln-date/src/date-model.js +186 -0
  98. package/components/ln-date/src/ln-date.js +486 -0
  99. package/components/ln-debug/README.md +132 -0
  100. package/components/ln-debug/ln-debug.js +1 -0
  101. package/components/ln-debug/ln-debug.schema.json +28 -0
  102. package/components/ln-debug/src/console-sink.js +19 -0
  103. package/components/ln-debug/src/debug-verifier.js +331 -0
  104. package/components/ln-debug/src/gate.js +95 -0
  105. package/components/ln-debug/src/generated-attributes.js +299 -0
  106. package/components/ln-debug/src/ln-debug.js +60 -0
  107. package/{js → components}/ln-dropdown/README.md +150 -140
  108. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -0
  109. package/components/ln-dropdown/ln-dropdown.js +1 -0
  110. package/components/ln-dropdown/ln-dropdown.schema.json +90 -0
  111. package/components/ln-dropdown/ln-dropdown.scss +18 -0
  112. package/{js → components}/ln-dropdown/src/ln-dropdown.js +114 -11
  113. package/{js → components}/ln-editor/README.md +11 -9
  114. package/components/ln-editor/ln-editor-dev.scss +18 -0
  115. package/components/ln-editor/ln-editor.js +1 -0
  116. package/components/ln-editor/ln-editor.schema.json +58 -0
  117. package/components/ln-editor/ln-editor.scss +13 -0
  118. package/{js → components}/ln-editor/src/ln-editor.js +59 -11
  119. package/{js → components}/ln-external-links/README.md +10 -8
  120. package/components/ln-external-links/ln-external-links.js +1 -0
  121. package/components/ln-external-links/ln-external-links.schema.json +18 -0
  122. package/{js → components}/ln-external-links/src/ln-external-links.js +13 -11
  123. package/{js → components}/ln-fill/README.md +6 -8
  124. package/components/ln-fill/ln-fill.js +1 -0
  125. package/components/ln-fill/ln-fill.schema.json +23 -0
  126. package/{js → components}/ln-fill/src/ln-fill.js +7 -1
  127. package/components/ln-filter/README.md +131 -0
  128. package/components/ln-filter/ln-filter-dev.scss +13 -0
  129. package/components/ln-filter/ln-filter.js +1 -0
  130. package/components/ln-filter/ln-filter.schema.json +153 -0
  131. package/components/ln-filter/src/filter-model.js +185 -0
  132. package/components/ln-filter/src/ln-filter.js +435 -0
  133. package/{js → components}/ln-form/README.md +260 -264
  134. package/components/ln-form/ln-form-dev.scss +8 -0
  135. package/components/ln-form/ln-form.js +1 -0
  136. package/components/ln-form/ln-form.schema.json +45 -0
  137. package/{js → components}/ln-form/src/ln-form.js +107 -99
  138. package/{js → components}/ln-http/README.md +11 -6
  139. package/components/ln-http/ln-http.js +1 -0
  140. package/components/ln-http/ln-http.schema.json +6 -0
  141. package/components/ln-http/src/http-core.js +51 -0
  142. package/{js → components}/ln-http/src/ln-http.js +28 -33
  143. package/{js → components}/ln-icon/README.md +16 -9
  144. package/components/ln-icon/ln-icon.schema.json +6 -0
  145. package/components/ln-include/README.md +75 -0
  146. package/components/ln-include/ln-include-dev.scss +13 -0
  147. package/components/ln-include/ln-include.js +1 -0
  148. package/components/ln-include/ln-include.schema.json +23 -0
  149. package/components/ln-include/src/ln-include.js +111 -0
  150. package/components/ln-key/README.md +130 -0
  151. package/components/ln-key/ln-key-dev.scss +18 -0
  152. package/components/ln-key/ln-key.js +1 -0
  153. package/components/ln-key/ln-key.schema.json +56 -0
  154. package/components/ln-key/src/key-model.js +146 -0
  155. package/components/ln-key/src/ln-key.js +201 -0
  156. package/{js → components}/ln-link/README.md +7 -9
  157. package/components/ln-link/ln-link-dev.scss +8 -0
  158. package/components/ln-link/ln-link.js +1 -0
  159. package/components/ln-link/ln-link.schema.json +22 -0
  160. package/{js → components}/ln-link/src/ln-link.js +16 -11
  161. package/components/ln-list/README.md +152 -0
  162. package/components/ln-list/ln-list-dev.scss +8 -0
  163. package/components/ln-list/ln-list.js +1 -0
  164. package/components/ln-list/ln-list.schema.json +166 -0
  165. package/components/ln-list/ln-list.scss +25 -0
  166. package/{js → components}/ln-list/src/ln-list.js +1301 -1216
  167. package/{js → components}/ln-modal/README.md +13 -10
  168. package/components/ln-modal/ln-modal-dev.scss +18 -0
  169. package/components/ln-modal/ln-modal.js +1 -0
  170. package/components/ln-modal/ln-modal.schema.json +54 -0
  171. package/components/ln-modal/ln-modal.scss +51 -0
  172. package/{js → components}/ln-modal/src/ln-modal.js +164 -137
  173. package/{js → components}/ln-nav/README.md +11 -11
  174. package/components/ln-nav/ln-nav.js +1 -0
  175. package/components/ln-nav/ln-nav.schema.json +25 -0
  176. package/{js → components}/ln-nav/src/ln-nav.js +25 -14
  177. package/components/ln-number/README.md +205 -0
  178. package/{js → components}/ln-number/ln-number-dev.scss +13 -13
  179. package/components/ln-number/ln-number.js +1 -0
  180. package/components/ln-number/ln-number.schema.json +63 -0
  181. package/components/ln-number/src/ln-number.js +400 -0
  182. package/components/ln-number/src/number-model.js +31 -0
  183. package/components/ln-obfuscator/README.md +60 -0
  184. package/components/ln-obfuscator/ln-obfuscator.js +1 -0
  185. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -0
  186. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -0
  187. package/components/ln-obfuscator/src/obfuscator-model.js +250 -0
  188. package/{js → components}/ln-options/README.md +3 -1
  189. package/components/ln-options/ln-options-dev.scss +13 -0
  190. package/components/ln-options/ln-options.js +1 -0
  191. package/components/ln-options/ln-options.schema.json +41 -0
  192. package/{js → components}/ln-options/src/ln-options.js +13 -5
  193. package/components/ln-persist/README.md +151 -0
  194. package/components/ln-persist/ln-persist.js +1 -0
  195. package/components/ln-persist/ln-persist.schema.json +28 -0
  196. package/components/ln-persist/src/ln-persist.js +83 -0
  197. package/components/ln-picklist/README.md +230 -0
  198. package/components/ln-picklist/ln-picklist-dev.scss +22 -0
  199. package/components/ln-picklist/ln-picklist.js +1 -0
  200. package/components/ln-picklist/ln-picklist.schema.json +49 -0
  201. package/components/ln-picklist/src/ln-picklist.js +228 -0
  202. package/{js → components}/ln-popover/README.md +164 -162
  203. package/components/ln-popover/ln-popover-dev.scss +13 -0
  204. package/components/ln-popover/ln-popover.js +1 -0
  205. package/components/ln-popover/ln-popover.schema.json +82 -0
  206. package/components/ln-popover/ln-popover.scss +18 -0
  207. package/{js → components}/ln-popover/src/ln-popover.js +42 -36
  208. package/{js → components}/ln-progress/README.md +410 -413
  209. package/components/ln-progress/ln-progress-dev.scss +8 -0
  210. package/components/ln-progress/ln-progress.js +1 -0
  211. package/components/ln-progress/ln-progress.schema.json +40 -0
  212. package/components/ln-progress/src/ln-progress.js +91 -0
  213. package/components/ln-progress/src/progress-model.js +20 -0
  214. package/{js → components}/ln-router/README.md +443 -432
  215. package/components/ln-router/ln-router-dev.scss +13 -0
  216. package/components/ln-router/ln-router.js +1 -0
  217. package/components/ln-router/ln-router.schema.json +68 -0
  218. package/{js → components}/ln-router/src/ln-router.js +585 -539
  219. package/components/ln-router/src/router-model.js +50 -0
  220. package/components/ln-scroll/README.md +45 -0
  221. package/components/ln-scroll/ln-scroll.js +1 -0
  222. package/components/ln-scroll/ln-scroll.schema.json +85 -0
  223. package/components/ln-scroll/src/ln-scroll.js +150 -0
  224. package/components/ln-search/README.md +188 -0
  225. package/components/ln-search/ln-search-dev.scss +13 -0
  226. package/components/ln-search/ln-search.js +1 -0
  227. package/components/ln-search/ln-search.schema.json +103 -0
  228. package/components/ln-search/src/ln-search.js +330 -0
  229. package/{js → components}/ln-slug/README.md +6 -3
  230. package/components/ln-slug/ln-slug-dev.scss +18 -0
  231. package/components/ln-slug/ln-slug.js +1 -0
  232. package/components/ln-slug/ln-slug.schema.json +23 -0
  233. package/{js → components}/ln-slug/src/ln-slug.js +20 -13
  234. package/components/ln-slug/src/slug-model.js +21 -0
  235. package/{js → components}/ln-sort/README.md +178 -170
  236. package/{js → components}/ln-sort/ln-sort-dev.scss +8 -8
  237. package/components/ln-sort/ln-sort.js +1 -0
  238. package/components/ln-sort/ln-sort.schema.json +104 -0
  239. package/components/ln-sort/src/ln-sort.js +321 -0
  240. package/components/ln-sort/src/sort-model.js +117 -0
  241. package/{js → components}/ln-sortable/README.md +7 -11
  242. package/components/ln-sortable/ln-sortable-dev.scss +13 -0
  243. package/components/ln-sortable/ln-sortable.js +1 -0
  244. package/components/ln-sortable/ln-sortable.schema.json +36 -0
  245. package/{js → components}/ln-sortable/src/ln-sortable.js +8 -13
  246. package/{js → components}/ln-stat/README.md +3 -1
  247. package/components/ln-stat/ln-stat-dev.scss +8 -0
  248. package/components/ln-stat/ln-stat.js +1 -0
  249. package/components/ln-stat/ln-stat.schema.json +32 -0
  250. package/{js → components}/ln-stat/src/ln-stat.js +14 -18
  251. package/components/ln-stat/src/stat-model.js +28 -0
  252. package/{js → components}/ln-table/README.md +37 -24
  253. package/{js → components}/ln-table/ln-table-dev.scss +17 -17
  254. package/components/ln-table/ln-table.js +1 -0
  255. package/components/ln-table/ln-table.schema.json +211 -0
  256. package/components/ln-table/ln-table.scss +15 -0
  257. package/{js → components}/ln-table/src/ln-table.js +1365 -1493
  258. package/components/ln-table/src/table-model.js +98 -0
  259. package/components/ln-table-coordinator/README.md +61 -0
  260. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -0
  261. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -0
  262. package/components/ln-table-coordinator/src/ln-table-coordinator.js +193 -0
  263. package/{js → components}/ln-tabs/README.md +32 -14
  264. package/components/ln-tabs/ln-tabs-dev.scss +24 -0
  265. package/components/ln-tabs/ln-tabs.js +1 -0
  266. package/components/ln-tabs/ln-tabs.schema.json +75 -0
  267. package/components/ln-tabs/ln-tabs.scss +8 -0
  268. package/components/ln-tabs/src/ln-tabs.js +207 -0
  269. package/components/ln-tabs/src/tabs-model.js +82 -0
  270. package/{js → components}/ln-time/README.md +96 -95
  271. package/components/ln-time/ln-time-dev.scss +13 -0
  272. package/components/ln-time/ln-time.js +1 -0
  273. package/components/ln-time/ln-time.schema.json +38 -0
  274. package/{js → components}/ln-time/src/ln-time.js +206 -223
  275. package/components/ln-time/src/time-model.js +64 -0
  276. package/{js → components}/ln-toast/README.md +23 -5
  277. package/components/ln-toast/ln-toast-dev.scss +8 -0
  278. package/components/ln-toast/ln-toast.js +1 -0
  279. package/components/ln-toast/ln-toast.schema.json +65 -0
  280. package/components/ln-toast/ln-toast.scss +59 -0
  281. package/{js → components}/ln-toast/src/ln-toast.js +233 -232
  282. package/{js → components}/ln-toggle/README.md +198 -196
  283. package/{js → components}/ln-toggle/ln-toggle-dev.scss +13 -13
  284. package/components/ln-toggle/ln-toggle.js +1 -0
  285. package/components/ln-toggle/ln-toggle.schema.json +52 -0
  286. package/components/ln-toggle/ln-toggle.scss +37 -0
  287. package/{js → components}/ln-toggle/src/ln-toggle.js +61 -46
  288. package/{js → components}/ln-tooltip/README.md +6 -6
  289. package/{js → components}/ln-tooltip/ln-tooltip-dev.scss +9 -9
  290. package/components/ln-tooltip/ln-tooltip.js +1 -0
  291. package/components/ln-tooltip/ln-tooltip.schema.json +86 -0
  292. package/components/ln-tooltip/ln-tooltip.scss +9 -0
  293. package/{js → components}/ln-tooltip/src/ln-tooltip.js +14 -3
  294. package/{js → components}/ln-translations/README.md +3 -1
  295. package/components/ln-translations/ln-translations-dev.scss +13 -0
  296. package/components/ln-translations/ln-translations.js +1 -0
  297. package/components/ln-translations/ln-translations.schema.json +120 -0
  298. package/{js → components}/ln-translations/src/ln-translations.js +25 -10
  299. package/components/ln-ui-coordinator/README.md +101 -0
  300. package/components/ln-ui-coordinator/ln-ui-coordinator-dev.scss +8 -0
  301. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -0
  302. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +60 -0
  303. package/{js/ln-modal-coordinator/src/ln-modal-coordinator.js → components/ln-ui-coordinator/src/ln-ui-coordinator.js} +403 -345
  304. package/components/ln-upload/README.md +133 -0
  305. package/components/ln-upload/ln-upload-dev.scss +18 -0
  306. package/components/ln-upload/ln-upload.js +1 -0
  307. package/components/ln-upload/ln-upload.schema.json +193 -0
  308. package/components/ln-upload/ln-upload.scss +20 -0
  309. package/components/ln-upload/src/ln-upload.js +652 -0
  310. package/components/ln-upload/src/upload-model.js +78 -0
  311. package/{js → components}/ln-validate/README.md +182 -180
  312. package/{js → components}/ln-validate/ln-validate-dev.scss +15 -15
  313. package/components/ln-validate/ln-validate.js +1 -0
  314. package/components/ln-validate/ln-validate.schema.json +50 -0
  315. package/components/ln-validate/ln-validate.scss +7 -0
  316. package/{js → components}/ln-validate/src/ln-validate.js +23 -45
  317. package/components/ln-validate/src/validate-model.js +64 -0
  318. package/components/ln-websocket-connector/README.md +166 -0
  319. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -0
  320. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -0
  321. package/components/ln-websocket-connector/src/ln-websocket-connector.js +279 -0
  322. package/ln-ashlar.html-data.json +1796 -0
  323. package/package.json +84 -61
  324. package/{scss → theme}/base/_global.scss +1 -2
  325. package/{scss → theme}/base/_reset.scss +1 -1
  326. package/{scss → theme}/base/_typography.scss +2 -0
  327. package/theme/brand.css +73 -0
  328. package/{scss → theme}/components/_app-shell.scss +8 -0
  329. package/theme/components/_button.scss +24 -0
  330. package/theme/components/_chart.scss +5 -0
  331. package/theme/components/_confirm.scss +6 -0
  332. package/{scss → theme}/components/_date.scss +30 -22
  333. package/{scss → theme}/components/_dropdown.scss +6 -1
  334. package/theme/components/_form.scss +140 -0
  335. package/{scss → theme}/components/_layout.scss +6 -0
  336. package/{scss → theme}/components/_ln-table.scss +1 -6
  337. package/theme/components/_picklist.scss +3 -0
  338. package/{scss → theme}/components/_sort.scss +10 -10
  339. package/theme/components/_status-badge.scss +6 -0
  340. package/{scss → theme}/components/_table.scss +16 -0
  341. package/{scss → theme}/components/_tabs.scss +0 -4
  342. package/theme/components/_toast.scss +48 -0
  343. package/{scss → theme}/components/_toggle.scss +1 -1
  344. package/theme/components/_upload.scss +45 -0
  345. package/{scss → theme}/config/_breakpoints.scss +1 -1
  346. package/theme/config/_contrast.scss +128 -0
  347. package/{scss → theme}/config/_density.scss +191 -205
  348. package/theme/config/_palette.scss +274 -0
  349. package/theme/config/_theme.scss +293 -0
  350. package/{scss → theme}/config/_tokens.scss +285 -441
  351. package/{scss → theme}/config/mixins/_accordion.scss +1 -1
  352. package/{scss → theme}/config/mixins/_alert.scss +1 -0
  353. package/{scss → theme}/config/mixins/_app-shell.scss +4 -2
  354. package/{scss → theme}/config/mixins/_borders.scss +2 -0
  355. package/theme/config/mixins/_btn.scss +175 -0
  356. package/{scss → theme}/config/mixins/_card.scss +55 -46
  357. package/theme/config/mixins/_chart.scss +82 -0
  358. package/{scss → theme}/config/mixins/_collapsible.scss +2 -1
  359. package/{scss → theme}/config/mixins/_colors.scss +8 -14
  360. package/{scss → theme}/config/mixins/_diagnostics.scss +48 -48
  361. package/theme/config/mixins/_dropdown.scss +169 -0
  362. package/{scss → theme}/config/mixins/_editor.scss +0 -7
  363. package/{scss → theme}/config/mixins/_filter.scss +47 -47
  364. package/{scss → theme}/config/mixins/_footer.scss +1 -1
  365. package/{scss → theme}/config/mixins/_form.scss +49 -18
  366. package/{scss → theme}/config/mixins/_index.scss +62 -61
  367. package/{scss → theme}/config/mixins/_layout.scss +6 -2
  368. package/{scss → theme}/config/mixins/_ln-table.scss +31 -10
  369. package/{scss → theme}/config/mixins/_loader.scss +1 -1
  370. package/{scss → theme}/config/mixins/_modal.scss +145 -149
  371. package/{scss → theme}/config/mixins/_motion.scss +13 -0
  372. package/{scss → theme}/config/mixins/_nav.scss +7 -7
  373. package/theme/config/mixins/_picklist.scss +66 -0
  374. package/theme/config/mixins/_popover.scss +96 -0
  375. package/{scss → theme}/config/mixins/_sidebar.scss +7 -4
  376. package/{scss → theme}/config/mixins/_sort.scss +43 -42
  377. package/{scss → theme}/config/mixins/_status-badge.scss +15 -2
  378. package/{scss → theme}/config/mixins/_table.scss +25 -14
  379. package/{scss → theme}/config/mixins/_tabs.scss +1 -1
  380. package/{scss → theme}/config/mixins/_toast.scss +174 -179
  381. package/{scss → theme}/config/mixins/_tooltip.scss +2 -2
  382. package/{scss → theme}/config/mixins/_typography.scss +16 -5
  383. package/{scss → theme}/config/mixins/_upload.scss +8 -6
  384. package/theme/ln-ashlar-core.scss +24 -0
  385. package/theme/ln-ashlar-dev.scss +66 -0
  386. package/theme/ln-ashlar-landing.scss +56 -0
  387. package/{scss/ln-ashlar.scss → theme/ln-ashlar-theme.scss} +63 -69
  388. package/theme/ln-ashlar.scss +8 -0
  389. package/theme/presentation/_bento.scss +151 -0
  390. package/theme/presentation/_cta.scss +111 -0
  391. package/theme/presentation/_effects.scss +216 -0
  392. package/theme/presentation/_features.scss +108 -0
  393. package/theme/presentation/_footer.scss +129 -0
  394. package/theme/presentation/_hero.scss +158 -0
  395. package/theme/presentation/_layout.scss +170 -0
  396. package/theme/presentation/_navbar.scss +140 -0
  397. package/theme/presentation/_pricing.scss +262 -0
  398. package/theme/presentation/_stats.scss +87 -0
  399. package/theme/presentation/_testimonials.scss +124 -0
  400. package/theme/presentation/_tokens.scss +39 -0
  401. package/theme/presentation/_typography.scss +163 -0
  402. package/{scss → theme}/utilities/_animations.scss +38 -0
  403. package/{scss → theme}/utilities/_utilities.scss +6 -0
  404. package/web-types.json +2312 -0
  405. package/js/ln-accordion/ln-accordion.js +0 -1
  406. package/js/ln-ajax/ln-ajax.js +0 -1
  407. package/js/ln-api-connector/ln-api-connector.js +0 -1
  408. package/js/ln-api-connector/src/ln-api-connector.js +0 -368
  409. package/js/ln-api-queue/ln-api-queue.js +0 -1
  410. package/js/ln-autoresize/ln-autoresize.js +0 -1
  411. package/js/ln-autosave/ln-autosave.js +0 -1
  412. package/js/ln-circular-progress/ln-circular-progress.js +0 -1
  413. package/js/ln-confirm/README.md +0 -162
  414. package/js/ln-confirm/_ln-confirm.scss +0 -24
  415. package/js/ln-confirm/ln-confirm.js +0 -1
  416. package/js/ln-core/crypto.js +0 -83
  417. package/js/ln-core/helpers.js +0 -788
  418. package/js/ln-core/index.js +0 -7
  419. package/js/ln-core/persist.js +0 -97
  420. package/js/ln-couchdb-connector/ln-couchdb-connector.js +0 -1
  421. package/js/ln-data-coordinator/ln-data-coordinator.js +0 -1
  422. package/js/ln-data-coordinator/src/data-read-policy.js +0 -18
  423. package/js/ln-data-store/ln-data-store.js +0 -1
  424. package/js/ln-date/ln-date.js +0 -1
  425. package/js/ln-date/src/ln-date.js +0 -603
  426. package/js/ln-debug/README.md +0 -58
  427. package/js/ln-debug/ln-debug.js +0 -1
  428. package/js/ln-debug/src/ln-debug.js +0 -19
  429. package/js/ln-dropdown/ln-dropdown.js +0 -1
  430. package/js/ln-dropdown/ln-dropdown.scss +0 -15
  431. package/js/ln-editor/ln-editor.js +0 -1
  432. package/js/ln-editor/ln-editor.scss +0 -43
  433. package/js/ln-external-links/ln-external-links.js +0 -1
  434. package/js/ln-fill/ln-fill.js +0 -1
  435. package/js/ln-filter/README.md +0 -189
  436. package/js/ln-filter/ln-filter-dev.scss +0 -8
  437. package/js/ln-filter/ln-filter.js +0 -1
  438. package/js/ln-filter/src/ln-filter.js +0 -419
  439. package/js/ln-form/ln-form.js +0 -1
  440. package/js/ln-http/ln-http.js +0 -1
  441. package/js/ln-link/ln-link.js +0 -1
  442. package/js/ln-list/README.md +0 -129
  443. package/js/ln-list/ln-list.js +0 -1
  444. package/js/ln-list/ln-list.scss +0 -28
  445. package/js/ln-modal/ln-modal-dev.scss +0 -13
  446. package/js/ln-modal/ln-modal.js +0 -1
  447. package/js/ln-modal/ln-modal.scss +0 -35
  448. package/js/ln-modal-coordinator/README.md +0 -99
  449. package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +0 -8
  450. package/js/ln-modal-coordinator/ln-modal-coordinator.js +0 -1
  451. package/js/ln-nav/ln-nav.js +0 -1
  452. package/js/ln-number/README.md +0 -149
  453. package/js/ln-number/ln-number.js +0 -1
  454. package/js/ln-number/src/ln-number.js +0 -443
  455. package/js/ln-options/ln-options.js +0 -1
  456. package/js/ln-popover/ln-popover.js +0 -1
  457. package/js/ln-progress/ln-progress.js +0 -1
  458. package/js/ln-progress/src/ln-progress.js +0 -98
  459. package/js/ln-router/ln-router.js +0 -1
  460. package/js/ln-search/README.md +0 -158
  461. package/js/ln-search/ln-search-dev.scss +0 -8
  462. package/js/ln-search/ln-search.js +0 -1
  463. package/js/ln-search/src/ln-search.js +0 -117
  464. package/js/ln-slug/ln-slug.js +0 -1
  465. package/js/ln-sort/ln-sort.js +0 -1
  466. package/js/ln-sort/src/ln-sort.js +0 -178
  467. package/js/ln-sortable/ln-sortable.js +0 -1
  468. package/js/ln-stat/ln-stat.js +0 -1
  469. package/js/ln-table/ln-table.js +0 -1
  470. package/js/ln-table/ln-table.scss +0 -11
  471. package/js/ln-table-coordinator/README.md +0 -73
  472. package/js/ln-table-coordinator/ln-table-coordinator.js +0 -1
  473. package/js/ln-table-coordinator/src/ln-table-coordinator.js +0 -160
  474. package/js/ln-tabs/ln-tabs-dev.scss +0 -18
  475. package/js/ln-tabs/ln-tabs.js +0 -1
  476. package/js/ln-tabs/src/ln-tabs.js +0 -188
  477. package/js/ln-time/ln-time.js +0 -1
  478. package/js/ln-toast/ln-toast.js +0 -1
  479. package/js/ln-toggle/ln-toggle.js +0 -1
  480. package/js/ln-tooltip/ln-tooltip.js +0 -1
  481. package/js/ln-tooltip/ln-tooltip.scss +0 -9
  482. package/js/ln-translations/ln-translations.js +0 -1
  483. package/js/ln-upload/README.md +0 -255
  484. package/js/ln-upload/ln-upload.js +0 -1
  485. package/js/ln-upload/ln-upload.scss +0 -20
  486. package/js/ln-upload/src/ln-upload.js +0 -423
  487. package/js/ln-validate/ln-validate.js +0 -1
  488. package/scss/components/_button.scss +0 -20
  489. package/scss/components/_confirm.scss +0 -5
  490. package/scss/components/_form.scss +0 -107
  491. package/scss/components/_status-badge.scss +0 -4
  492. package/scss/components/_toast.scss +0 -68
  493. package/scss/components/_upload.scss +0 -51
  494. package/scss/config/_theme.scss +0 -216
  495. package/scss/config/mixins/_btn.scss +0 -179
  496. package/scss/config/mixins/_confirm.scss +0 -31
  497. package/scss/config/mixins/_dropdown.scss +0 -141
  498. package/scss/config/mixins/_popover.scss +0 -64
  499. package/scss/ln-ashlar-dev.scss +0 -39
  500. /package/{js → components}/ln-core/positioning.js +0 -0
  501. /package/{js → components}/ln-core/reactive.js +0 -0
  502. /package/{js → components}/ln-filter/ln-filter.scss +0 -0
  503. /package/{js → components}/ln-icon/ln-icon.js +0 -0
  504. /package/{js → components}/ln-icon/src/ln-icon.js +0 -0
  505. /package/{js → components}/ln-search/ln-search.scss +0 -0
  506. /package/{js → components}/ln-toast/template.html +0 -0
  507. /package/{scss → theme}/components/_accordion.scss +0 -0
  508. /package/{scss → theme}/components/_ajax.scss +0 -0
  509. /package/{scss → theme}/components/_alert.scss +0 -0
  510. /package/{scss → theme}/components/_avatar.scss +0 -0
  511. /package/{scss → theme}/components/_breadcrumbs.scss +0 -0
  512. /package/{scss → theme}/components/_card.scss +0 -0
  513. /package/{scss → theme}/components/_chip.scss +0 -0
  514. /package/{scss → theme}/components/_circular-progress.scss +0 -0
  515. /package/{scss → theme}/components/_editor.scss +0 -0
  516. /package/{scss → theme}/components/_empty-state.scss +0 -0
  517. /package/{scss → theme}/components/_filter.scss +0 -0
  518. /package/{scss → theme}/components/_link.scss +0 -0
  519. /package/{scss → theme}/components/_loader.scss +0 -0
  520. /package/{scss → theme}/components/_modal.scss +0 -0
  521. /package/{scss → theme}/components/_nav.scss +0 -0
  522. /package/{scss → theme}/components/_page-header.scss +0 -0
  523. /package/{scss → theme}/components/_popover.scss +0 -0
  524. /package/{scss → theme}/components/_progress.scss +0 -0
  525. /package/{scss → theme}/components/_prose.scss +0 -0
  526. /package/{scss → theme}/components/_scrollbar.scss +0 -0
  527. /package/{scss → theme}/components/_sections.scss +0 -0
  528. /package/{scss → theme}/components/_sidebar.scss +0 -0
  529. /package/{scss → theme}/components/_stat-card.scss +0 -0
  530. /package/{scss → theme}/components/_stepper.scss +0 -0
  531. /package/{scss → theme}/components/_timeline.scss +0 -0
  532. /package/{scss → theme}/components/_tooltip.scss +0 -0
  533. /package/{scss → theme}/components/_translations.scss +0 -0
  534. /package/{scss → theme}/config/_icons.scss +0 -0
  535. /package/{scss → theme}/config/_mixins.scss +0 -0
  536. /package/{scss → theme}/config/mixins/_ajax.scss +0 -0
  537. /package/{scss → theme}/config/mixins/_avatar.scss +0 -0
  538. /package/{scss → theme}/config/mixins/_breadcrumbs.scss +0 -0
  539. /package/{scss → theme}/config/mixins/_breakpoints.scss +0 -0
  540. /package/{scss → theme}/config/mixins/_chip.scss +0 -0
  541. /package/{scss → theme}/config/mixins/_circular-progress.scss +0 -0
  542. /package/{scss → theme}/config/mixins/_display.scss +0 -0
  543. /package/{scss → theme}/config/mixins/_empty-state.scss +0 -0
  544. /package/{scss → theme}/config/mixins/_focus.scss +0 -0
  545. /package/{scss → theme}/config/mixins/_interaction.scss +0 -0
  546. /package/{scss → theme}/config/mixins/_kbd.scss +0 -0
  547. /package/{scss → theme}/config/mixins/_link.scss +0 -0
  548. /package/{scss → theme}/config/mixins/_page-header.scss +0 -0
  549. /package/{scss → theme}/config/mixins/_position.scss +0 -0
  550. /package/{scss → theme}/config/mixins/_progress.scss +0 -0
  551. /package/{scss → theme}/config/mixins/_prose.scss +0 -0
  552. /package/{scss → theme}/config/mixins/_shadows.scss +0 -0
  553. /package/{scss → theme}/config/mixins/_sizing.scss +0 -0
  554. /package/{scss → theme}/config/mixins/_spacing.scss +0 -0
  555. /package/{scss → theme}/config/mixins/_stat-card.scss +0 -0
  556. /package/{scss → theme}/config/mixins/_stepper.scss +0 -0
  557. /package/{scss → theme}/config/mixins/_timeline.scss +0 -0
  558. /package/{scss → theme}/config/mixins/_transitions.scss +0 -0
  559. /package/{scss → theme}/config/mixins/_translations.scss +0 -0
  560. /package/{scss → theme}/tabler-icons.txt +0 -0
@@ -1,32 +1,45 @@
1
1
  # ln-core
2
2
 
3
3
  Shared helper module imported by all components — re-exported from
4
- `js/ln-core/index.js`. No DOM attribute, no constructor, no
5
- MutationObserver. Source: `js/ln-core/`.
4
+ `components/ln-core/index.js`. No DOM attribute, no constructor, no
5
+ MutationObserver. Source: `components/ln-core/`.
6
6
 
7
7
  ## Helpers
8
8
 
9
9
  ln-core exposes helpers in these categories:
10
10
 
11
11
  - **Templates** — clone and populate `<template>` fragments
12
- - **Events** — dispatch standard and cancelable CustomEvents; shared data-request cycle for collection components (`requestData`)
12
+ - **Events & Lifecycle** — dispatch standard/cancelable CustomEvents; shared data-request cycle (`requestData`); `registerComponent` for MutationObserver-backed auto-init
13
13
  - **DOM binding** — declarative data-attribute-driven rendering (`fill`, `renderList`, `fillTemplate`)
14
- - **Forms** — serialize/populate by field `name`
15
- - **Dictionaries** — extract i18n strings from hidden elements
16
- - **Discovery** — `registerComponent` for MutationObserver-backed auto-init
14
+ - **Forms & Inputs** — serialize/populate by field `name`, intercept input value getters/setters (`interceptValueProperty`)
15
+ - **Target & Event Predicates** — `shouldIgnoreClick`, `isTargetDisabled`, `isUsableTarget`, `isEditableTarget`
17
16
  - **Reactivity** — `reactiveState`, `deepReactive`, `createBatcher`
18
17
  - **Windowing** — sliding-window sparse cache for server-side virtualization (`createWindowCache`)
19
- - **Layout** — viewport-aware positioning, teleport, measurement
20
- - **Persistence** — localStorage wrappers with `ln:` prefix
21
- - **Hash codec** — namespaced URL fragment state (`hashParse`, `hashGet`, `hashSet`)
18
+ - **Layout & Positioning** — viewport-aware positioning (`computePlacement`), measurement (`measureHidden`)
19
+ - **Hash codec** — namespaced URL fragment state (`hashParse`, `hashGet`, `hashSet`, `hashSortEncode`, `hashSortDecode`, `hashFilterEncode`, `hashFilterDecode`, `resolveHashNamespace`)
22
20
  - **Cryptography** — high-performance Web Crypto helpers for encryption at rest (`crypto.js`)
23
-
24
- Source of truth: `js/ln-core/helpers.js`, `js/ln-core/reactive.js`, and `js/ln-core/crypto.js`. Import from `'../ln-core'` (barrel).
21
+ - **Searching & Filtering** — query tokenization, multi-field and filter matching (`matching.js`)
22
+ - **Comparing & Sorting** — type inference and localized/numeric comparison (`compare.js`)
23
+ - **Progress Math** — clamp and percentage calculations (`progress.js`)
24
+ - **Date Utilities** — flexible date input parsing and ISO formatting (`date.js`)
25
+ - **Number Utilities** — locale-aware separators, number sanitizing, parsing, and formatting (`number.js`)
26
+ - **Attribute Contracts & Parsers** — attribute specs, live getters, typed readers, and dev-time validation (`attrSpec`, `defineAttrs`, `attrStr`, `attrInt`, `attrFloat`, `attrBool`, `attrEnum`, `attrJson`, `validateAttrValue`, `isDevMode`) (`attrs.js`)
27
+
28
+ Source of truth: modular sub-files in `components/ln-core/`. Import from `'../../ln-core'` (barrel).
29
+
30
+ ## Core Helpers & Submodules (`helpers.js`)
31
+
32
+ `helpers.js` serves as the centralized, backward-compatible named barrel re-exporting the specialized domain submodules:
33
+ - `template.js` — template cloning, declarative DOM binding (`fill`, `lnFill`, `fillTemplate`, `renderList`, `buildDict`).
34
+ - `events.js` — CustomEvent dispatchers (`dispatch`, `dispatchCancelable`, `requestData`), debug sinks and dev mode checks.
35
+ - `dom.js` — DOM guard and interactive target predicates (`guardBody`, `isVisible`, `shouldIgnoreClick`, `isUsableTarget`, etc.).
36
+ - `form.js` — form method resolution, serialization, population, and `interceptValueProperty`.
37
+ - `http.js` — URL assembly, HTTP headers compilation/parsing, and data mapper registry.
38
+ - `locale.js` — locale detection (`getLocale`), observer, and locale fallback registry.
39
+ - `lifecycle.js` — component registration (`registerComponent`), element scanning (`findElements`), and shared MutationObservers.
25
40
 
26
41
  ---
27
42
 
28
- ## helpers.js
29
-
30
43
  ### cloneTemplate(name, componentTag)
31
44
 
32
45
  Clone a `<template data-ln-template="name">` element. Cached after first lookup.
@@ -114,12 +127,12 @@ window.lnCore.lnFill(modalEl, record); // record → fill; null → reset/clea
114
127
 
115
128
  - Also dispatches `ln-fill` at `container` itself when it matches
116
129
  `[data-ln-form]` or `[data-ln-fillable]` — so `lnFill(formEl, record)` works
117
- when called directly on a form element. Source: `js/ln-core/helpers.js` L164–165.
130
+ when called directly on a form element. Source: `components/ln-core/template.js` (`lnFill`).
118
131
  - `container` — any element; scans its entire subtree.
119
132
  - `record` — plain object or `null`.
120
133
  - Dispatches `ln-fill` (`bubbles: true`, `detail = record ?? null`) at each target.
121
134
  - Returns `container` for chaining.
122
- - Exported from `js/ln-core/index.js`; available at `window.lnCore.lnFill`.
135
+ - Exported from `components/ln-core/index.js`; available at `window.lnCore.lnFill`.
123
136
  - Back-compat: `ln-form` also still accepts `ln-form:fill` / `ln-form:reset` (aliases).
124
137
 
125
138
  ### fillTemplate(clone, data)
@@ -143,7 +156,7 @@ fillTemplate(frag, { text: 'Engineering' });
143
156
  root element itself when it is an `Element`, since `querySelectorAll` does not include
144
157
  root). For each attribute whose value contains `{{`, replaces tokens via
145
158
  `el.setAttribute(attr.name, resolved)`. Uses `setAttribute` — never `innerHTML` —
146
- so injection risk is the same as `data-ln-attr`. Source: `js/ln-core/helpers.js` L197–233.
159
+ so injection risk is the same as `data-ln-attr`. Source: `components/ln-core/template.js` (`fillTemplate`).
147
160
  - Missing keys produce empty string in both passes
148
161
  - No-op in either pass when no `{{` is present
149
162
  - Returns `clone` for chaining
@@ -153,7 +166,7 @@ fillTemplate(frag, { text: 'Engineering' });
153
166
 
154
167
  ### renderList(container, items, templateName, keyFn, fillFn, componentTag)
155
168
 
156
- Keyed list rendering with DOM reuse. Existing children matched by `data-ln-key` attribute.
169
+ Keyed list rendering with DOM reuse. Existing children matched by `data-ln-render-key` attribute.
157
170
 
158
171
  ```js
159
172
  renderList(
@@ -168,7 +181,7 @@ renderList(
168
181
 
169
182
  - `keyFn(item)` returns a unique string key for each item
170
183
  - `fillFn(el, item, index)` updates the DOM element with item data
171
- - Reuses existing elements with matching `data-ln-key` (avoids re-clone)
184
+ - Reuses existing elements with matching `data-ln-render-key` (avoids re-clone)
172
185
  - Replaces all container children atomically (`textContent = '' + appendChild(frag)`)
173
186
  - Calls `fillTemplate(clone, item)` automatically for newly-cloned elements — `{{ key }}` placeholders in the template resolve from the item data without extra wiring.
174
187
 
@@ -295,12 +308,12 @@ populated.forEach(function (el) { dispatch(el, 'input'); });
295
308
  - **Decoupled fill key (`data-ln-fill-as`)** — match key for the fill direction is
296
309
  `el.getAttribute('data-ln-fill-as') || el.name`. When `data-ln-fill-as` is set,
297
310
  the record key is the fill-as value and `name` stays as the form submission key.
298
- Source: `populateForm` in `js/ln-core/helpers.js`.
311
+ Source: `populateForm` in `components/ln-core/form.js`.
299
312
  - **Checkbox string coercion** — a single checkbox (one element with that `name` in
300
313
  the form) whose fill value is a string is coerced via `_coerceBool`: `"false"`,
301
314
  `"0"`, `""`, `"off"`, `"no"` (case-insensitive, trimmed) → unchecked; anything
302
315
  else → checked. This handles `data-ln-fill-*` values which always arrive as
303
- strings. Source: `_coerceBool` + `populateForm` in `js/ln-core/helpers.js`.
316
+ strings. Source: `_coerceBool` + `populateForm` in `components/ln-core/form.js`.
304
317
  - Checkbox + array → `checked` if `el.value` is in the array.
305
318
  - Checkbox group (same `name`, 2+ elements) + scalar → treated as comma-separated list (`"admin,editor"` → membership check).
306
319
  - Single checkbox + scalar → boolean coercion: `"false"/"0"/"off"/"no"/""` → unchecked; anything else → checked.
@@ -326,11 +339,13 @@ Read the raw machine value behind a formatted cell/item display. The single
326
339
  read path for value-based sort/filter across components.
327
340
 
328
341
  ```js
329
- const raw = readValue(td); // '1250.50' from data-ln-value, else trimmed text
342
+ const raw = readValue(td); // '1250.50' from data-ln-value, datetime on <time>, value on <data>, else trimmed text
330
343
  ```
331
344
 
332
345
  - Returns `data-ln-value` attribute value if the element has it.
333
- - Otherwise returns `el.textContent.trim()`.
346
+ - Returns `datetime` attribute if the element is `<time>` and has it.
347
+ - Returns `value` attribute if the element is `<data>` and has it.
348
+ - Otherwise falls back to `el.textContent.trim()`.
334
349
  - Used by `ln-table` for sort/filter.
335
350
 
336
351
  ### registerComponent(selector, attribute, ComponentFn, componentTag, options)
@@ -345,8 +360,9 @@ import { registerComponent } from '../ln-core';
345
360
  function _component(dom) { this.dom = dom; /* ... */ }
346
361
 
347
362
  registerComponent('data-ln-example', 'lnExample', _component, 'ln-example', {
348
- extraAttributes: ['data-ln-example-state'],
349
- onAttributeChange: function (target, name) { /* attribute → state bridge */ },
363
+ effects: {
364
+ 'data-ln-example-state': function (target, name, oldValue) { /* attribute → state bridge */ }
365
+ },
350
366
  onInit: function (root) { /* post-init hook, per added subtree */ }
351
367
  });
352
368
  ```
@@ -355,18 +371,172 @@ registerComponent('data-ln-example', 'lnExample', _component, 'ln-example', {
355
371
  if it contains `[`, `.`, or `#` (e.g. `'[data-ln-foo]:not([disabled])'`).
356
372
  - `attribute` — JS-side key used both as `window[attribute]` (the
357
373
  constructor function) and `el[attribute]` (the per-element instance).
358
- - `options.extraAttributes` — additional attribute names to include in
359
- the MutationObserver's `attributeFilter` (e.g. state attributes set
360
- by coordinator).
361
-
362
- > **Coverage rule:** `extraAttributes` must include every attribute your `onAttributeChange` handler — and any helper it calls synchronously — reads from the element as an input to what it renders or derives. The observed set is *primary attribute + `extraAttributes`*; an attribute read but not observed will not trigger a re-sync when it changes. Behaviour flags checked only at a transition (e.g. `data-ln-persist`, read at open/close to decide a side-effect) are exempt — a mid-life change to them is not meant to re-render.
363
- - `options.onAttributeChange(target, attrName)` — called when a
364
- filtered attribute changes on an already-initialized element. The
374
+ - `options.attributes` — the component's attribute table. One entry per host
375
+ attribute the component reads or reacts to. Derives the `effects` map, the
376
+ live-getter spec (via `attrSpec` at call site), and scopes `options.onAttrChange` —
377
+ only declared keys reach the catch-all. When present, `options.effects` is ignored.
378
+ - `options.effects` — `{ attributeName: handler(target, name, oldValue) }`.
379
+ A per-attribute reaction dispatched by the shared attribute observer's
380
+ registry. Fires when the mutated element already owns an instance
381
+ (`el[attribute]` is truthy) and the attribute name starts with `data-ln-`.
382
+ - `options.onAttrChange(target, name, oldValue)` — catch-all reaction for
383
+ any `data-ln-*` attribute on the host not covered by an `effects` key and
384
+ present in `options.attributes` when a table is supplied. Without a table, the
385
+ catch-all is unscoped and fires for every `data-ln-*` attribute on the host.
386
+ Same instance and prefix gate as `effects`.
387
+ - `options.onAttributeChange(target, attrName)` — called when an attribute
388
+ in this registration's observed set (the selector's own attribute name,
389
+ plus `extraAttributes`) changes on an already-initialized element. The
365
390
  attribute → state bridge hook.
391
+ - `options.extraAttributes` — additional attribute names, beyond the
392
+ selector's own, added to this registration's observed set in the shared
393
+ attribute registry (e.g. state attributes set by a coordinator). Read by
394
+ the `onAttributeChange` path.
366
395
  - `options.onInit(root)` — called after `findElements` per subtree
367
396
  (initial DOM, added childList nodes, attribute-mutated subtrees).
397
+ - `options.onSubtreeChange(host, mutation)` — called on every `childList`
398
+ mutation inside this registration's observed subtree, where `host` is the
399
+ nearest ancestor (or the mutated node itself) matching `selector`. Runs
400
+ alongside, not instead of, the added/removed-node instantiation and
401
+ teardown `registerComponent` already performs.
368
402
  - Returns the constructor function (also stored at `window[attribute]`).
369
403
 
404
+ ### observeAttributes(names, handler)
405
+
406
+ For components that own their own lifecycle (bespoke `childList` logic that
407
+ doesn't fit `registerComponent`'s shape) and need only the attribute half of
408
+ the shared attribute-observer invariant.
409
+
410
+ ```js
411
+ import { observeAttributes } from '../ln-core';
412
+
413
+ observeAttributes(['href'], function (el, name, oldValue) {
414
+ _processLink(el);
415
+ });
416
+ ```
417
+
418
+ - `names` — attribute names to watch. No `data-ln-` prefix gate, no instance
419
+ gate — every mutation of a watched name calls `handler`, on any element.
420
+ - `handler(el, attributeName, oldValue)`.
421
+ - Installs onto the same shared `MutationObserver` used by `registerComponent`
422
+ (one observer on `document.body`, no `attributeFilter`).
423
+ - A component using `observeAttributes` must **not** run its own attribute
424
+ `MutationObserver` alongside it — that duplicates delivery.
425
+ - Two standing exceptions keep their own private observer instead of using
426
+ this: `ln-progress` (observes an attribute on its **parent**, outside the
427
+ host-only boundary the shared observer covers) and `ln-icon` (the only
428
+ component in the library that imports nothing — pulling in `ln-core`'s
429
+ module-level side effects would roughly quadruple its standalone bundle
430
+ size). Neither is an oversight; do not migrate them.
431
+
432
+ ### setPersistSink(sink)
433
+
434
+ Nullable persist restore/save sink. Installed/removed only by `ln-persist`
435
+ (`components/ln-persist/src/ln-persist.js`) — mirrors `setDebugSink`'s shape
436
+ exactly. `findElements` calls `window.lnCore._persistSink(el, selector)`
437
+ for any element carrying `data-ln-persist`, immediately before constructing
438
+ its owning component's instance, when the sink is installed.
439
+
440
+ ```js
441
+ import { setPersistSink } from '../ln-core';
442
+
443
+ setPersistSink(function (el, selector) {
444
+ // restore/save logic keyed off the registry's persist: entries
445
+ });
446
+ ```
447
+
448
+ - `sink(el, selector)` — called synchronously inside `findElements`, before
449
+ the element's component instance is constructed.
450
+ - No sink installed (`ln-persist` not loaded) → the check is a no-op.
451
+ - See `components/ln-persist/README.md` for the full restore/save contract.
452
+
453
+ ### defineAttrs(instance, dom, spec), attrSpec(table), attrEffects(table)
454
+
455
+ Define live, getter-only properties on a component instance that read the DOM
456
+ on every access. One table per component is the single source of truth for its
457
+ host attributes, feeding `defineAttrs` via `attrSpec` and `registerComponent` via
458
+ `attributes`.
459
+
460
+ ```js
461
+ import { registerComponent, defineAttrs, attrSpec, attrInt, attrBool, attrList } from '../ln-core';
462
+
463
+ const ATTRIBUTES = {
464
+ 'data-ln-example-window': { prop: '_windowSize', read: attrInt, fallback: 1000, effect: _applyWindowSize },
465
+ 'data-ln-example-tags': { prop: '_tags', read: attrList },
466
+ 'data-ln-example-no-sync': { prop: 'noSync', read: attrBool }
467
+ };
468
+
469
+ const ATTR_SPEC = attrSpec(ATTRIBUTES);
470
+
471
+ function _component(dom) {
472
+ defineAttrs(this, dom, ATTR_SPEC);
473
+ }
474
+
475
+ registerComponent('data-ln-example', 'lnExample', _component, 'ln-example', {
476
+ attributes: ATTRIBUTES
477
+ });
478
+ ```
479
+
480
+ - `table` — `{ [attributeName]: { prop, read, fallback, effect } }`.
481
+ - `attrSpec(table)` — derives `{ propName: [reader, attributeName, fallback] }` for entries declaring `prop`.
482
+ - `attrEffects(table)` — derives `{ [attributeName]: effect }` for entries declaring `effect` (returns `null` when empty).
483
+ - `spec` — `{ propName: [reader, attributeName, fallback] }`.
484
+ - `attrStr` — returns `fallback` when the attribute is absent, else the raw
485
+ string.
486
+ - `attrInt` — `parseInt(..., 10)`; returns `fallback` on `NaN`. `"0"` stays
487
+ `0` — unlike the common `parseInt(attr, 10) || fallback` idiom, which maps
488
+ `"0"` to `fallback`. Check call sites relying on that idiom before swapping
489
+ them to `attrInt`.
490
+ - `attrBool` — `(el, name, fallback)`: returns `fallback` when the attribute is absent, `false` when `"false"` or `"0"`, and `true` otherwise (`""`, `"true"`, `"1"`).
491
+ - `attrList` — `(el.getAttribute(name) || '').split(',').map(trim).filter(Boolean)`.
492
+ Allocates a new array per read — do not call inside a per-row loop.
493
+ - `reader` can be any function of that `(el, name, fallback)` shape, so a
494
+ component can pass its own for special-cased parsing.
495
+ - **Getter-only — assignment throws** (`TypeError`, strict mode). Nothing is
496
+ copied into instance state, so there is no state to invalidate and no sync
497
+ step; a drifting copy is structurally impossible. Before migrating an
498
+ existing `this.prop = ...` assignment to a `defineAttrs` getter, grep for
499
+ every place the codebase assigns to that property after construction — a
500
+ hit means the property is not a pure attribute mirror and does not belong
501
+ in the spec. (`ln-data-store`'s `windowed` is excluded for exactly this
502
+ reason: `destroy()` assigns `this.windowed = false`.)
503
+ - **Attribute names must be inline string literals or a `const` bound to a
504
+ literal — never concatenated or templated.**
505
+ `scripts/sync-ln-schemas.mjs:22-23` is a raw regex over file text; a
506
+ constructed name is invisible to it and the CI docs gate breaks silently.
507
+
508
+ ### Frozen-attribute convention
509
+
510
+ A **frozen attribute** is a constructor-only attribute — one that physically
511
+ cannot be re-read at runtime (e.g. an IndexedDB schema fixed at open time).
512
+ When a frozen attribute is edited after init, the component writes
513
+ `data-ln-{component}-frozen` on its own host, with the attempted attribute
514
+ name as the value, and a co-located `*-dev.scss` rule surfaces it through the
515
+ `dev-dom-error` mixin under `[data-ln-debug]`. Never `console.warn` for this
516
+ case — it is a CSS-only dev affordance. See `ln-data-store` for the reference
517
+ implementation (`_markFrozen`, `ln-data-store-dev.scss`).
518
+
519
+ ### holdInit() / releaseInit() / pendingCount() / queueBoot(fn)
520
+
521
+ Boot gating primitives for asynchronous loading (such as external partial templates). Prevents components from initializing (sweeping the DOM) before all external resources have finished loading.
522
+
523
+ ```js
524
+ import { holdInit, releaseInit, pendingCount, queueBoot } from '../ln-core';
525
+
526
+ // In an async component's initialization:
527
+ holdInit();
528
+ fetch('/tpl.html').then(html => {
529
+ // populate DOM
530
+ releaseInit();
531
+ });
532
+ ```
533
+
534
+ - `holdInit()` — Increments the global boot holds counter (`window.lnCore._bootHolds`).
535
+ - `releaseInit()` — Decrements the global boot holds counter. If the counter reaches zero, it drains the queue (`window.lnCore._bootQueue`) in push order.
536
+ - `pendingCount()` — Returns the current number of active boot holds.
537
+ - `queueBoot(fn)` — If `pendingCount() > 0`, queues the function in `_bootQueue`. Otherwise, schedules it immediately using `setTimeout(fn, 0)` (to match standard event loop scheduling).
538
+ - Used internally by `registerComponent` and `ln-ui-coordinator` to prevent race conditions during async initialization.
539
+
370
540
  ---
371
541
 
372
542
  ## reactive.js
@@ -448,7 +618,7 @@ the DOM will thrash.
448
618
 
449
619
  For the full decision matrix (`reactiveState` vs `deepReactive`, when
450
620
  to use this pattern at all, anti-patterns), see
451
- [Reactive Rendering Pattern in js/COMPONENTS.md](../../js/COMPONENTS.md#reactive-rendering-pattern).
621
+ [Reactive Rendering Pattern in components/COMPONENTS.md](../../components/COMPONENTS.md#reactive-rendering-pattern).
452
622
 
453
623
  ---
454
624
 
@@ -549,26 +719,6 @@ panel.setAttribute('data-ln-placement', placement);
549
719
  pair → clamps to viewport edge if nothing fits.
550
720
  - Pure function. No DOM side effects.
551
721
 
552
- ### teleportToBody(el)
553
-
554
- Move an element into `<body>`, leaving a comment placeholder so it can
555
- be restored to its original parent.
556
-
557
- ```js
558
- const restore = teleportToBody(panel);
559
- // ... later
560
- restore();
561
- ```
562
-
563
- - Returns a cleanup function that restores the element to its origin.
564
- - No-op + no-op cleanup if the element is already in `<body>` or
565
- parent is missing.
566
- - Used for floating UI that needs to escape `overflow: hidden`
567
- ancestors (popovers, dropdowns).
568
- - Does NOT set inline styles — the caller's CSS rule (e.g.
569
- `[data-ln-popover] { position: fixed }`) is responsible for
570
- positioning context.
571
-
572
722
  ### measureHidden(el)
573
723
 
574
724
  Read `offsetWidth` / `offsetHeight` of an element that may currently
@@ -587,93 +737,8 @@ const { width, height } = measureHidden(panel);
587
737
 
588
738
  ---
589
739
 
590
- ## persist.js
591
-
592
- ### persistGet(component, el)
593
-
594
- Read persisted value for an element. Returns the parsed JSON value or `null` (not found, or storage unavailable).
595
-
596
- - `component` — component name string (e.g. `'toggle'`, `'tabs'`, `'filter'`)
597
- - `el` — DOM element with `data-ln-persist` attribute
598
-
599
- Storage key resolved from `el`: uses `data-ln-persist` value if non-empty, otherwise element's `id`. If neither exists, emits `console.warn` and returns `null`.
600
-
601
- ```js
602
- import { persistGet } from '../ln-core';
603
- const saved = persistGet('toggle', el); // null | 'open' | 'close'
604
- ```
605
-
606
- ### persistSet(component, el, value)
607
-
608
- Write a value to localStorage. Value is JSON-serialized. Silently no-ops if localStorage is unavailable or full.
609
-
610
- ```js
611
- import { persistSet } from '../ln-core';
612
- persistSet('toggle', el, 'open');
613
- persistSet('sort', el, { field: 'name', column: null, direction: 'desc' });
614
- persistSet('filter', el, null); // explicitly clears
615
- ```
616
-
617
- ### persistRemove(component, el)
618
-
619
- Remove a single persisted value for a specific element.
620
-
621
- ```js
622
- import { persistRemove } from '../ln-core';
623
- persistRemove('tabs', el);
624
- ```
625
-
626
- ### persistClear(component)
627
-
628
- Remove ALL persisted values for a given component type. Scans all localStorage keys matching `ln:{component}:*`.
629
-
630
- ```js
631
- import { persistClear } from '../ln-core';
632
- persistClear('toggle'); // removes all ln:toggle:* keys
633
- persistClear('sort'); // removes all ln:sort:* keys
634
- ```
635
-
636
- Useful for "reset to defaults" functionality.
637
-
638
- ### Storage key format
639
-
640
- ```
641
- ln:{component}:{pagePath}:{id}
642
- ```
643
-
644
- - `pagePath` — `location.pathname`, lowercase, trailing slash stripped, or `/` for root
645
- - `id` — element's `id` attribute, or the explicit `data-ln-persist="custom-key"` value
646
-
647
- Examples:
648
- ```
649
- ln:toggle:/admin/users:sidebar
650
- ln:tabs:/settings:settings-tabs
651
- ln:sort:/admin/orders:orders-table-name
652
- ln:filter:/admin/users:status-filter
653
- ```
654
-
655
- ### Opt-in HTML attribute
656
-
657
- ```html
658
- <!-- Uses element id as storage key -->
659
- <section id="sidebar" data-ln-toggle="close" data-ln-persist>
660
-
661
- <!-- Explicit key (no id needed) -->
662
- <section data-ln-toggle="close" data-ln-persist="sidebar-section">
663
- ```
664
-
665
- Persistence is always opt-in. Elements without `data-ln-persist` are never touched.
666
-
667
- Components that support `data-ln-persist` document the stored value
668
- shape in their own READMEs (ln-toggle, ln-accordion, ln-tabs,
669
- ln-sort, ln-filter).
670
-
671
- ### Graceful degradation
672
-
673
- - localStorage disabled (private browsing) → silent no-op, components work normally without persistence
674
- - localStorage full → silent no-op on write, existing data preserved
675
- - Stale data (panel removed from DOM, column index out of range) → orphan key ignored gracefully
676
- - Missing `id` + no explicit key → `console.warn` once, persistence skipped for that element
740
+ Persistence now lives in the standalone `ln-persist` component — see
741
+ `components/ln-persist/README.md`.
677
742
 
678
743
  ---
679
744
 
@@ -763,15 +828,17 @@ const key = getCryptoKey(); // null or CryptoKey
763
828
 
764
829
  - **Returns** `CryptoKey` or `null` if no key has been derived yet.
765
830
 
766
- ### encryptData(plainData, key = null)
831
+ ### encryptData(plainData, keyOrOptions = null)
767
832
 
768
- Encrypt any serializable value (objects/arrays are converted to JSON strings).
833
+ Encrypt any serializable value (objects/arrays are converted to JSON strings). Enforces fail-closed semantics (throws `Error` if no key is set or encryption fails).
769
834
 
770
835
  ```js
771
836
  import { encryptData } from '../ln-core';
772
837
 
773
838
  const result = await encryptData({ text: 'Sensitive message' });
774
839
  // {
840
+ // v: 1,
841
+ // alg: 'AES-GCM',
775
842
  // encrypted: true,
776
843
  // iv: 'base64-iv-string...',
777
844
  // data: 'base64-ciphertext...'
@@ -779,23 +846,134 @@ const result = await encryptData({ text: 'Sensitive message' });
779
846
  ```
780
847
 
781
848
  - **`plainData`** — any primitive value, array, or object.
782
- - **`key`** — optional custom `CryptoKey`. Defaults to the active global key.
783
- - **Returns** `Promise<Object>` containing `encrypted: true`, base64 `iv`, and base64 `data`. Returns raw `plainData` if no active key is available.
849
+ - **`keyOrOptions`** — optional custom `CryptoKey` or options object `{ key?: CryptoKey }`. Defaults to the active key.
850
+ - **Returns** `Promise<Object>` containing version 1 envelope (`v: 1`, `alg: 'AES-GCM'`, `encrypted: true`, `iv`, `data`). Throws `Error` on failure.
784
851
 
785
- ### decryptData(encryptedObject, key = null)
852
+ ### decryptData(encryptedObject, keyOrOptions = null)
786
853
 
787
- Decrypt an encrypted object back into its original form (automatically parsing JSON strings back to objects).
854
+ Decrypt an encrypted envelope back into its original form (automatically parsing JSON strings back to objects). Supports both v1 envelopes and legacy v0 unversioned envelopes. Enforces fail-closed semantics unless `{ silent: true }` is specified.
788
855
 
789
856
  ```js
790
857
  import { decryptData } from '../ln-core';
791
858
 
792
859
  const decrypted = await decryptData(result);
793
860
  console.log(decrypted.text); // 'Sensitive message'
861
+
862
+ // Resilient bulk-read mode:
863
+ const resilient = await decryptData(corruptObject, { silent: true });
864
+ // returns { ...corruptObject, decryptionError: true } instead of throwing
794
865
  ```
795
866
 
796
867
  - **`encryptedObject`** — an object generated by `encryptData`.
797
- - **`key`** — optional custom `CryptoKey`. Defaults to the active global key.
798
- - **Returns** `Promise<any>`. The original plain data, or `{ ...encryptedObject, decryptionError: true }` if decryption failed (e.g., incorrect key).
868
+ - **`keyOrOptions`** — optional custom `CryptoKey` or options object `{ key?: CryptoKey, silent?: boolean }`.
869
+ - **Returns** `Promise<any>`. The original plain data. Throws `Error` on decryption failure or missing key unless `{ silent: true }` is passed.
870
+
871
+ ---
872
+
873
+ ## matching.js
874
+
875
+ Pure string searching, tokenization, and multi-filter matching engine.
876
+
877
+ ### normalizeSearchTerm(raw)
878
+
879
+ Trims and lowercases user search queries.
880
+
881
+ ### tokenizeSearchQuery(raw)
882
+
883
+ Splits query on whitespace for multi-term AND matching. Returns an array of lowercase search tokens.
884
+
885
+ ### matchesSearchTokens(text, tokens)
886
+
887
+ Evaluates whether text contains all search tokens (case-insensitive AND match).
888
+
889
+ ### matchesFilterValues(text, values)
890
+
891
+ Evaluates whether text matches any of the active filter values (case-insensitive OR match).
892
+
893
+ ---
894
+
895
+ ## compare.js
896
+
897
+ Type inference and localized/numeric comparison engine.
898
+
899
+ ### detectValueType(values)
900
+
901
+ Iterates values in a dataset or column. If all non-empty values are finite numbers, returns `'number'`; otherwise returns `'string'`.
902
+
903
+ ### compareValues(a, b, type, locale)
904
+
905
+ Performs null-safe comparison (-1, 0, 1):
906
+ - `null`/`undefined` collation: sorts first or last deterministically.
907
+ - `type === 'number'`: numeric difference `a - b`.
908
+ - `type === 'string'`: localized comparison via `Intl.Collator(locale, { numeric: true, sensitivity: 'base' })`.
909
+
910
+ ---
911
+
912
+ ## progress.js
913
+
914
+ Pure mathematical clamping and progress calculations.
915
+
916
+ ### calculateProgress(rawValue, rawMax, min = 0)
917
+
918
+ Computes `{ value, min, max, clampedValue, percentage }` where percentage is bounded between 0% and 100%.
919
+
920
+ ---
921
+
922
+ ## date.js
923
+
924
+ Pure date input parsing and ISO formatting.
925
+
926
+ ### parseDateInput(raw)
927
+
928
+ Parses Date objects, ISO strings (`YYYY-MM-DD` or `YYYY-MM-DDTHH:mm:ssZ`), and Unix seconds/milliseconds timestamps into a valid `Date` instance (or `null` if invalid).
929
+
930
+ A date-only `YYYY-MM-DD` string is read as local midnight of that calendar day, not UTC midnight (the native `Date` reading), so it round-trips through `formatDateToISO` unchanged in every timezone. A non-existent calendar date (`2026-02-31`) returns `null`.
931
+
932
+ ### formatDateToISO(date)
933
+
934
+ Converts a `Date` instance to standard `YYYY-MM-DD` string representation.
935
+
936
+ ---
937
+
938
+ ## number.js
939
+
940
+ Locale-aware number sanitizing, parsing, and formatting with cached `Intl.NumberFormat` instances.
941
+
942
+ ### getSeparators(locale)
943
+
944
+ Extracts group and decimal separators dynamically from `Intl.NumberFormat` for any BCP 47 locale tag (e.g. `'en-US'`, `'mk-MK'`, `'de-DE'`, `'fr-FR'`).
945
+
946
+ ### cleanNumericString(raw, groupSep, decimalSep)
947
+
948
+ Strips currency symbols, whitespace, and thousands separators, normalizing the decimal separator to `.` for reliable `parseFloat`.
949
+
950
+ ### parseNumber(raw, locale)
951
+
952
+ Parses localized user input or raw strings into a valid `Number` or `NaN`.
953
+
954
+ ### formatNumber(num, locale, options)
955
+
956
+ Formats a numeric value using `Intl.NumberFormat` with cached formatters. Supports `maxDecimals` and `userDecimals` constraints.
957
+
958
+ ---
959
+
960
+ ## Target & Event Predicates (`dom.js`)
961
+
962
+ ### shouldIgnoreClick(event)
963
+
964
+ Returns `true` if the mouse click was triggered with modifier keys (`Ctrl`, `Meta`, `Shift`, `Alt`) or a non-primary mouse button (`button !== 0`).
965
+
966
+ ### isTargetDisabled(element)
967
+
968
+ Returns `true` if the element or any ancestor is disabled (`.disabled`), marked with `aria-disabled="true"`, or contained inside an `[inert]` element.
969
+
970
+ ### isUsableTarget(element, action)
971
+
972
+ Returns `true` if the target is connected to the DOM, visible (`isVisible`), enabled (`!isTargetDisabled`), and implements the target method (e.g. `el.click` or `el.focus`).
973
+
974
+ ### isEditableTarget(element)
975
+
976
+ Returns `true` if the element is an `<input>`, `<textarea>`, `<select>`, or `[contenteditable]` container where global shortcut keys should avoid triggering unless explicitly opted-in.
799
977
 
800
978
  ---
801
979
 
@@ -870,11 +1048,12 @@ Sort coerces with `parseFloat(raw) || 0`. Formatted display text breaks it:
870
1048
 
871
1049
  ### The split
872
1050
 
873
- - `data-ln-value` — the VALUE. Universal. Read by `ln-core.readValue`.
1051
+ - `data-ln-value` — the VALUE. Universal raw attribute. Read by `ln-core.readValue`.
1052
+ - `datetime` / `value` — native HTML5 machine values on `<time>` and `<data>` elements, also read directly by `ln-core.readValue`.
874
1053
  - **Type is inferred, not declared.** `ln-core.detectValueType` scans the current value set once
875
1054
  per sort operation — no `data-ln-*-sort` type attribute exists anymore. All non-empty values
876
1055
  finite numbers → numeric compare; otherwise `ln-core.compareValues` + `Intl.Collator` string
877
- compare. See `js/ln-sort/README.md`.
1056
+ compare. See `components/ln-sort/README.md`.
878
1057
 
879
1058
  ### The reader
880
1059
 
@@ -894,3 +1073,49 @@ the server's job: Blade/PHP in SSR mode, or the API payload in data-driven mode.
894
1073
  - **Data-driven** — the raw value lives in the record field (API payload); the
895
1074
  rendered element may also carry `data-ln-value`. Sorting runs on the record
896
1075
  field. Never sort formatted text.
1076
+
1077
+ ---
1078
+
1079
+ ## attrs.js
1080
+
1081
+ Standardized attribute readers, spec generators, live getter mappers, and dev-time type validation.
1082
+
1083
+ ### `attrStr(el, name, fallback)`
1084
+ Reads a string attribute. Returns `fallback` if attribute is null or empty.
1085
+
1086
+ ### `attrInt(el, name, fallback)`
1087
+ Parses integer from attribute (`parseInt(val, 10)`). Returns `fallback` if parsing results in `NaN`.
1088
+
1089
+ ### `attrFloat(el, name, fallback)`
1090
+ Parses floating point number from attribute (`parseFloat(val)`). Returns `fallback` if parsing results in `NaN`.
1091
+
1092
+ ### `attrBool(el, name, fallback = false)`
1093
+ Evaluates boolean attribute presence and values. Returns `fallback` if attribute is absent. If present, returns `false` when value is `'false'` or `'0'`, and `true` otherwise (`""`, `'true'`, `'1'`).
1094
+
1095
+ ### `attrEnum(values, fallback)`
1096
+ Higher-order function returning a cached reader function for a set of permitted string enum values. (Illustrative usage; authoritative contracts are defined in each component's documentation, e.g. [`ln-modal`](../../docs-mcp/components/ln-modal.md)):
1097
+ ```js
1098
+ const readState = attrEnum(['open', 'close'], 'close');
1099
+ const state = readState(el, 'data-ln-modal');
1100
+ ```
1101
+
1102
+ ### `attrJson(el, name, fallback)`
1103
+ Safely parses JSON string attribute. Returns `fallback` if attribute is missing or contains invalid JSON syntax.
1104
+
1105
+ ### `attrSpec(spec)`
1106
+ Generates an attribute definition spec for `defineAttrs`. Automatically infers the appropriate reader function from `entry.type` if `entry.read` is omitted. For full component specifications, see individual component docs and schemas:
1107
+ ```js
1108
+ // Illustrative attribute table combining host enum and typed options:
1109
+ const ATTRIBUTES = {
1110
+ 'data-ln-modal': { type: 'enum', values: ['open', 'close'], fallback: 'close', effect: _syncAttribute },
1111
+ 'data-ln-scroll-delay': { prop: 'delay', type: 'integer', fallback: 450 }
1112
+ };
1113
+ const ATTR_SPEC = attrSpec(ATTRIBUTES);
1114
+ ```
1115
+
1116
+ ### `defineAttrs(target, el, spec)`
1117
+ Installs live getters on `target` mapping property names to dynamic attribute reads on `el`.
1118
+
1119
+ ### `validateAttrValue(entry, value, attrName, componentTag)`
1120
+ Validates attribute string against entry constraints (`enum`, `integer`, `float`, `boolean`, `min`, `max`, etc.) and outputs descriptive `console.warn` in dev mode.
1121
+