@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
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Calculates virtual scrolling slicing indices and padding heights.
3
+ * @param {number} scrollTop
4
+ * @param {number} viewportHeight
5
+ * @param {number} rowHeight
6
+ * @param {number} totalCount
7
+ * @param {number} [bufferRows=15]
8
+ * @returns {{ start: number, end: number, topPadding: number, bottomPadding: number }}
9
+ */
10
+ export function calculateVirtualWindow(scrollTop, viewportHeight, rowHeight, totalCount, bufferRows = 15) {
11
+ if (totalCount <= 0 || rowHeight <= 0) {
12
+ return { start: 0, end: 0, topPadding: 0, bottomPadding: 0 };
13
+ }
14
+
15
+ const safeScrollTop = Math.max(0, scrollTop || 0);
16
+ const safeViewportHeight = Math.max(0, viewportHeight || 0);
17
+
18
+ const visibleStartIndex = Math.floor(safeScrollTop / rowHeight);
19
+ const visibleCount = Math.ceil(safeViewportHeight / rowHeight);
20
+
21
+ const start = Math.max(0, visibleStartIndex - bufferRows);
22
+ const end = Math.min(totalCount, visibleStartIndex + visibleCount + bufferRows);
23
+
24
+ const topPadding = start * rowHeight;
25
+ const bottomPadding = Math.max(0, (totalCount - end) * rowHeight);
26
+
27
+ return { start, end, topPadding, bottomPadding };
28
+ }
29
+
30
+ /**
31
+ * Computes selection state metadata across a dataset.
32
+ * @param {string[]|number[]} allIds
33
+ * @param {Set<string|number>|Array<string|number>} selectedIds
34
+ * @returns {{ totalCount: number, selectedCount: number, isAllSelected: boolean, isIndeterminate: boolean }}
35
+ */
36
+ export function calculateSelectionState(allIds, selectedIds) {
37
+ const totalCount = Array.isArray(allIds) ? allIds.length : 0;
38
+ const selectedSet = selectedIds instanceof Set ? selectedIds : new Set(selectedIds || []);
39
+ let selectedCount = 0;
40
+
41
+ if (Array.isArray(allIds)) {
42
+ for (let i = 0; i < allIds.length; i++) {
43
+ if (selectedSet.has(allIds[i])) {
44
+ selectedCount++;
45
+ }
46
+ }
47
+ } else {
48
+ selectedCount = selectedSet.size;
49
+ }
50
+
51
+ const isAllSelected = totalCount > 0 && selectedCount === totalCount;
52
+ const isIndeterminate = selectedCount > 0 && selectedCount < totalCount;
53
+
54
+ return { totalCount, selectedCount, isAllSelected, isIndeterminate };
55
+ }
56
+
57
+ /**
58
+ * Toggles a row ID within a Set of selected IDs.
59
+ * @param {Set<string|number>} selectedSet
60
+ * @param {string|number} rowId
61
+ * @param {boolean} [forceState]
62
+ * @returns {Set<string|number>}
63
+ */
64
+ export function toggleRowSelection(selectedSet, rowId, forceState) {
65
+ const next = new Set(selectedSet);
66
+ if (rowId == null) return next;
67
+
68
+ const shouldSelect = forceState !== undefined ? forceState : !next.has(rowId);
69
+ if (shouldSelect) {
70
+ next.add(rowId);
71
+ } else {
72
+ next.delete(rowId);
73
+ }
74
+ return next;
75
+ }
76
+
77
+ /**
78
+ * Selects or deselects all IDs.
79
+ * @param {Set<string|number>} selectedSet
80
+ * @param {Array<string|number>} allIds
81
+ * @param {boolean} selectAll
82
+ * @returns {Set<string|number>}
83
+ */
84
+ export function toggleSelectAll(selectedSet, allIds, selectAll) {
85
+ const next = new Set(selectedSet);
86
+ if (!Array.isArray(allIds)) return next;
87
+
88
+ if (selectAll) {
89
+ for (let i = 0; i < allIds.length; i++) {
90
+ if (allIds[i] != null) next.add(allIds[i]);
91
+ }
92
+ } else {
93
+ for (let i = 0; i < allIds.length; i++) {
94
+ next.delete(allIds[i]);
95
+ }
96
+ }
97
+ return next;
98
+ }
@@ -0,0 +1,61 @@
1
+ # ln-table-coordinator
2
+
3
+ > Applied to a wrapper element (`<div data-ln-table-coordinator>`) enclosing a table, search controls, and filter popovers. It listens for `ln-filter:change` within its host to toggle `.ln-filter-active` classes on header `<th>` buttons, intercepts `click` on `[data-ln-table-clear]` to reset search inputs and uncheck filter checkboxes, and captures page `'/'` `keydown` to focus the search input. It acts strictly as a Layer 2 mediator, never rendering table rows or holding query caches.
4
+
5
+ A zero-dependency, per-instance, host-scoped coordinator component that wires external search hosts (`ln-search`), column filter popovers (`ln-filter`), header filter indicators, and clear button triggers to a single `ln-table`.
6
+
7
+ `ln-table` itself is a Layer 1 primitive: it manages internal data state, virtual scroll rendering, pagination/windowing cache, row template cloning, cell formatting, and selection DOM state. `ln-table-coordinator` is the Layer 2 policy and mediator that wires external UI controls to `ln-table`.
8
+
9
+ ---
10
+
11
+ ## 🧭 Philosophy & Architecture
12
+
13
+ 1. **Layer Separation.** `ln-table` manages its own internal table state and DOM subtree only — no external DOM querying, no external input mirroring, and no visual indicator class toggles on header buttons. `ln-table-coordinator` handles all project UI wiring on top.
14
+ 2. **Host-Scoped, Per-Instance.** The coordinator is a real component: `[data-ln-table-coordinator]` is a **required wrapper**. Its handlers are bound to its own host element (`self.dom`), not `document`. The table, its `[data-ln-filter]` popovers, and its clear button(s) must all be descendants of the same coordinator host — outside its own subtree, the coordinator does not exist and does not look.
15
+ 3. **Event-Driven Communication.** Communication with `ln-table` occurs strictly via CustomEvents (`ln-table:set-search`, `ln-table:set-filter`, `ln-table:request-clear-filters`).
16
+
17
+ **Breaking change:** a data-driven table's search/filter/clear-all only works when wrapped in `[data-ln-table-coordinator]`. There is no page-wide fallback — the coordinator does not resolve a table by scanning the whole document.
18
+
19
+ ---
20
+
21
+ ## 🛠️ Declarative API & Wire Contract
22
+
23
+ | Target / Trigger | Event / Trigger | Behavior |
24
+ | :--- | :--- | :--- |
25
+ | `[data-ln-filter="tableId"]` (delivered by `ln-filter`) | `ln-filter:change` | Resolves target table in host, toggles `.ln-filter-active` indicator on header filter button. |
26
+ | `[data-ln-table-clear]`, `[data-ln-table-clear-all]` | `click` | Resolves the table structurally within this host, symmetrically clears the linked search input and `data-ln-search` source attribute, checks filter reset checkboxes, removes `.ln-filter-active` visual classes, and dispatches `ln-table:request-clear-filters` to SSR tables. |
27
+ | Document | `keydown` (`'/'`) | Focuses the search input inside the active coordinator wrapper on the page (or the first `[data-ln-search-for]` if none). Safely ignored when focus is in an input, textarea, or `isContentEditable` surface. |
28
+
29
+ ---
30
+
31
+ ## ⚡ Consumed & Dispatched Events
32
+
33
+ ### Consumed Events
34
+ - `ln-filter:change` (on `self.dom`)
35
+ - `click` (on `self.dom` — targets `[data-ln-table-clear]` and `[data-ln-table-clear-all]`)
36
+ - `keydown` (`document` — key `'/'`, the sole document-level exception, see below)
37
+
38
+ ### Dispatched Events
39
+ - `ln-table:request-clear-filters` (Target: the SSR table inside this host, Payload: `{ table }`)
40
+
41
+ ---
42
+
43
+ ## 🔧 Internals
44
+
45
+ Source: `components/ln-table-coordinator/src/ln-table-coordinator.js`. This is a real per-instance component: `_component(dom)` calls `_bindEvents(self)`, which attaches listeners to `self.dom` and cleans them up symmetrically on `destroy()`.
46
+
47
+ ### Column-filter indicator
48
+
49
+ `ln-filter` dispatches `ln-filter:change` on both its own `<ul data-ln-filter>` root and on the target element. The coordinator catches the event within its host, finds the `th[data-ln-table-filter-col="key"]`, and toggles `.ln-filter-active` on that header's `[data-ln-table-col-filter]` button based on `values.length > 0`. This header-indicator class is Layer-2 policy — which is why `ln-table` itself never sets it.
50
+
51
+ ### Clear
52
+
53
+ A `click` on `[data-ln-table-clear]` / `[data-ln-table-clear-all]` resolves the table structurally: `clearBtn.closest('[data-ln-table]')`, falling back to `dom.querySelector('[data-ln-table]')` — always scoped to this host. It then runs: (1) strip `.ln-filter-active` off every filter button in the table, (2) blank the linked search input and set `data-ln-search=""` on the source host, (3) check each linked filter popover's `[data-ln-filter-reset]` and fire a bubbling `change`, (4) dispatch `ln-table:request-clear-filters` if the table is an SSR table.
54
+
55
+ ### `/` focus shortcut
56
+
57
+ The sole deliberate exception to host-scoping. It remains a single `document`-level `keydown` listener registered once at module scope (outside `_component`/`_bindEvents`) — a page-level keyboard affordance, not coordination between a host and its children. Registering it per-instance would fire it once per coordinator on the page. It focuses the search input inside the active `[data-ln-table-coordinator]` wrapper, or the first `[data-ln-search]` on the page if none. Ignored when focus is already in an `INPUT`/`TEXTAREA`, `isContentEditable` surface (e.g. `ln-editor`), or when the event was already `defaultPrevented`.
58
+
59
+ ### Multi-coordinator isolation
60
+
61
+ Each `_component(dom)` instance closures its handlers over its own `dom`; `addEventListener` on a specific host node only fires for events whose bubble path passes through that node. Two coordinator hosts on the same page cannot see each other's descendants' events — no shared/global state exists to leak. The `/` shortcut is the sole intentional exception to this isolation — it is document-scoped by design.
@@ -0,0 +1 @@
1
+ (function(){"use strict";function L(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function S(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let r=null;for(let i=1;i<t.length;i++)if(t[i]&&t[i].nodeType===1){r=t[i];break}if(!S(r))return}e.apply(console,t)}}function I(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function _(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){_(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const $={};function V(e,t){$[e]=t}function H(e){return $[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=V,window.lnCore.getDataMapper=H);function U(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,_(function(){new MutationObserver(function(){I(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const D={};function K(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];D[n]=t}function P(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return D[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=K,window.lnCore.getLocaleFallback=P,window.lnCore.ensureLocaleObserver=U);const k={};function Q(e,t){k[e]||(k[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=k[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function N(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let s=0;s<n.length;s++){const a=n[s],o=a.getAttribute("data-ln-field");t[o]!=null&&(a.textContent=t[o])}const r=e.querySelectorAll("[data-ln-attr]");for(let s=0;s<r.length;s++){const a=r[s],o=a.getAttribute("data-ln-attr").split(",");for(let c=0;c<o.length;c++){const u=o[c].trim().split(":");if(u.length!==2)continue;const d=u[0].trim(),f=u[1].trim();t[f]!=null&&a.setAttribute(d,t[f])}}const i=e.querySelectorAll("[data-ln-show]");for(let s=0;s<i.length;s++){const a=i[s],o=a.getAttribute("data-ln-show");o in t&&a.classList.toggle("hidden",!t[o])}const l=e.querySelectorAll("[data-ln-class]");for(let s=0;s<l.length;s++){const a=l[s],o=a.getAttribute("data-ln-class").split(",");for(let c=0;c<o.length;c++){const u=o[c].trim().split(":");if(u.length!==2)continue;const d=u[0].trim(),f=u[1].trim();f in t&&a.classList.toggle(d,!!t[f])}}return e}function X(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)N(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function R(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const l=n.currentNode;l.textContent.indexOf("{{")!==-1&&(l.textContent=l.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(s,a){return t[a]!==void 0?t[a]:""}))}const r=function(l,s){return t[s]!==void 0?t[s]:""},i=Array.from(e.querySelectorAll("*"));e.nodeType===1&&i.push(e);for(let l=0;l<i.length;l++){const s=i[l],a=s.attributes;for(let o=0;o<a.length;o++){const c=a[o];c.value.indexOf("{{")!==-1&&s.setAttribute(c.name,c.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function G(e,t,n,r,i,l){const s={};for(let o=0;o<e.children.length;o++){const c=e.children[o],u=c.getAttribute("data-ln-render-key");u&&(s[u]=c)}const a=document.createDocumentFragment();for(let o=0;o<t.length;o++){const c=t[o],u=String(r(c));let d=s[u];if(d)i(d,c,o);else{const f=Q(n,l);if(!f||(R(f,c),d=f.firstElementChild,!d))continue;d.setAttribute("data-ln-render-key",u),i(d,c,o)}a.appendChild(d)}e.textContent="",e.appendChild(a)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=R,window.lnCore.fill=N,window.lnCore.lnFill=X,window.lnCore.renderList=G);function B(e,t,n,r,i){if(!e||t===null)return!0;const l=r||"ln-component",s=e.type;if(s==="trigger"||s==="marker"||s==="string"||s==="list"||!s||t===""&&e.fallback!==void 0)return!0;const a=o=>{i?console.warn(o,i):console.warn(o)};if(s==="boolean"){const o=t.trim().toLowerCase();return o!==""&&o!=="true"&&o!=="false"&&o!=="1"&&o!=="0"?(a(`[${l}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(s==="enum"){const o=e.values||[];return o.includes(t)?!0:(a(`[${l}] Invalid value "${t}" for attribute "${n}". Allowed: ${o.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(s==="integer"){if(!/^-?\d+$/.test(t))return a(`[${l}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const o=parseInt(t,10);return e.min!==void 0&&o<e.min?(a(`[${l}] Value ${o} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&o>e.max?(a(`[${l}] Value ${o} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(s==="float")return isNaN(Number(t))?(a(`[${l}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(s==="json")try{return JSON.parse(t),!0}catch(o){return a(`[${l}] Invalid JSON for attribute "${n}": ${o.message}. Fallback: ${e.fallback}.`),!1}return!0}function J(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function z(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function E(e,t,n,r){if(e.nodeType!==1)return;const l=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",s=Array.from(e.querySelectorAll(l));e.matches&&e.matches(l)&&s.push(e);for(const a of s)if(!a[n]){window.lnCore._persistSink&&a.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(a,t);try{a[n]=new r(a)}catch(o){console.error("["+n+"] init failed",a,o)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function Y(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function Z(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function F(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function ee(e){const t=F(),n=e.observed||[];for(let r=0;r<n.length;r++){const i=n[r];t.byAttr.has(i)||t.byAttr.set(i,[]),t.byAttr.get(i).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const i=t.byDeclaredAttr.get(r);i.some(l=>l.componentTag===e.componentTag)||i.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=z(e);if(r===null)t.reactiveWildcard.push(e);else for(const i of r)t.byReactive.has(i)||t.byReactive.set(i,[]),t.byReactive.get(i).push(e)}e.persist&&t.persist.push(e)}function M(e,t,n,r){for(let i=0;i<e.length;i++){const l=e[i];if(!t[l.attribute])continue;const s=l.effects&&l.effects[n];s?s(t,n,r):l.onAttrChange&&(!l.declared||l.declared.has(n))&&l.onAttrChange(t,n,r)}}function te(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=F(),i=r.byAttr.get(n);if(L()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&S(t)){const l=r.byDeclaredAttr.get(n),s=t.getAttribute(n);for(let a=0;a<l.length;a++)B(l[a].spec,s,n,l[a].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||i)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const l=r.byReactive.get(n);l&&M(l,t,n,e.oldValue),r.reactiveWildcard.length&&M(r.reactiveWildcard,t,n,e.oldValue)}if(i)for(let l=0;l<i.length;l++){const s=i[l];if(s.handler){s.handler(t,n,e.oldValue);continue}s.onAttributeChange&&t[s.attribute]?s.onAttributeChange(t,n):(E(t,s.selector,s.attribute,s.ComponentFn),s.onInit&&s.onInit(t))}}function ne(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,_(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{te(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function W(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function re(e){const t=W();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const i=r.query,l=e.target.nodeType===1?e.target.matches(i)?e.target:e.target.closest(i):e.target.parentElement?e.target.parentElement.closest(i):null;l&&r.onSubtreeChange(l,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let i=0;i<t.length;i++){const l=t[i];E(r,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let i=0;i<t.length;i++){const l=t[i],s=l.query,a=Array.from(r.querySelectorAll(s));r.matches&&r.matches(s)&&a.push(r);for(let o=0;o<a.length;o++){const c=a[o];if(!document.contains(c)){const u=c[l.attribute];if(u&&typeof u.destroy=="function")try{u.destroy()}catch(d){console.error("["+l.attribute+"] destroy failed",c,d)}delete c[l.attribute]}}}}}}function oe(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,_(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{re(r)}catch(i){console.error("[ln-core] lifecycle handler failed",r.target,i)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function le(e,t,n,r,i={}){const l=i.extraAttributes||[],s=i.onAttributeChange||null,a=i.onSubtreeChange||null,o=i.onInit||null,c=i.onAttrChange||null,u=i.effects||null,d=i.attributes||null,f=d?J(d):u,m=d?new Set(Object.keys(d)):null,h=i.persist||null;function w(A){const p=A||document.body;if(E(p,e,t,n),d&&L())for(const v in d){const be=d[v],T=Array.from(p.querySelectorAll("["+v+"]"));p.matches&&p.matches("["+v+"]")&&T.push(p);for(let O=0;O<T.length;O++){const x=T[O];S(x)&&B(be,x.getAttribute(v),v,r,x)}}o&&o(p)}const y=[];if(e.indexOf("[")!==-1){const A=/\[([\w-]+)/g;let p;for(;(p=A.exec(e))!==null;)y.push(p[1])}else y.push(e);ee({selector:e,attribute:t,componentTag:r,attributes:d,ComponentFn:n,onInit:o,observed:y.concat(l),onAttributeChange:s,onAttrChange:c,effects:f,declared:m,persist:h}),ne();const b=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";W().push({selector:e,attribute:t,ComponentFn:n,onInit:o,onSubtreeChange:a,query:b}),oe(),window[t]=w;function g(){Y()>0?Z(function(){w(document.body)}):w(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",g):g(),w}function j(e){return(e||"").replace(/^#/,"")}function q(e){const t=e===void 0?location.hash:e,n={},r=j(t);if(!r)return n;const i=r.split("&");for(let l=0;l<i.length;l++){const s=i[l];if(!s)continue;const a=s.indexOf(":"),o=a>-1?s.slice(0,a):s,c=a>-1?s.slice(a+1):"";if(o)try{n[o]=decodeURIComponent(c)}catch{n[o]=c}}return n}function ie(e){if(!e)return null;const t=q();return e in t?t[e]:null}function se(e,t){if(!e)return;const n=q();t==null?delete n[e]:n[e]=String(t);const i=Object.keys(n).map(function(l){const s=n[l];return s===""?l:l+":"+encodeURIComponent(s)}).join("&");j(location.hash)!==i&&(location.hash=i)}function ae(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function ce(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function ue(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function de(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function fe(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function we(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),i=r?r.split(",").map(function(l){try{return decodeURIComponent(l)}catch{return l}}).filter(Boolean):[];return{key:n,values:i}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=q,window.lnCore.hashGet=ie,window.lnCore.hashSet=se,window.lnCore.hashLinkClick=ae,window.lnCore.resolveHashNamespace=ce,window.lnCore.hashSortEncode=ue,window.lnCore.hashSortDecode=de,window.lnCore.hashFilterEncode=fe,window.lnCore.hashFilterDecode=we),(function(){const e="data-ln-table-coordinator",t="lnTableCoordinator";if(window[t]!==void 0)return;const n={"data-ln-table-coordinator":{type:"marker",description:"Mounts table coordinator mediating between table, search, filter, and pagination"}};document.addEventListener("keydown",function(a){if(a.key!=="/"||a.defaultPrevented||document.activeElement&&(document.activeElement.tagName==="INPUT"||document.activeElement.tagName==="TEXTAREA"||document.activeElement.isContentEditable))return;const o=document.querySelector("["+e+"] [data-ln-search-for]")||document.querySelector("[data-ln-search-for]");if(!o)return;const c=o.tagName==="INPUT"||o.tagName==="TEXTAREA"?o:o.querySelector('input[type="search"], input[type="text"], input');c&&(a.preventDefault(),c.focus())});function r(a){return this.dom=a,s(this),this}function i(a,o){const c=o?'[data-ln-search-for="'+o+'"]':"[data-ln-search-for]",u=a.querySelector(c)||document.querySelector(c);return u?u.tagName==="INPUT"||u.tagName==="TEXTAREA"?u:u.querySelector("input, textarea"):null}function l(a,o){if(o){const u=a.querySelectorAll('[data-ln-filter="'+o+'"]');if(u.length>0)return u;const d=document.querySelectorAll('[data-ln-filter="'+o+'"]');if(d.length>0)return d}const c=a.querySelectorAll("[data-ln-filter]");return c.length>0?c:document.querySelectorAll("[data-ln-filter]")}function s(a){const o=a.dom;function c(u){const d=u.target;if(d&&d.hasAttribute&&(d.hasAttribute("data-ln-table")||d.tagName==="TABLE"))return d;const f=u.detail&&u.detail.targetId||d&&d.id;return f?o.querySelector('[data-ln-table-source="'+f+'"]')||o.querySelector('[data-ln-table="'+f+'"]')||o.querySelector("#"+f)||(o.id===f?o:null)||document.getElementById(f):null}a._handlers={filter:function(u){if(!u.detail)return;const d=c(u);if(!d)return;const f=u.detail.key,m=u.detail.values||[],h=d.querySelectorAll("th");for(let w=0;w<h.length;w++)if((h[w].getAttribute("data-ln-table-filter-col")||h[w].getAttribute("data-ln-filter-col")||h[w].getAttribute("data-ln-filter-key")||h[w].getAttribute("data-ln-field"))===f){const C=h[w].querySelector("[data-ln-table-col-filter], .table-filter");C&&C.classList.toggle("ln-filter-active",m.length>0);break}},clear:function(u){const d=u.target.closest("[data-ln-table-clear], [data-ln-table-clear-all]");if(!d)return;const f=d.closest("[data-ln-table], table")||o.querySelector("[data-ln-table], table");if(!f)return;const m=f.lnTable&&f.lnTable.name||f.id,h=f.querySelectorAll("th");for(let b=0;b<h.length;b++){const g=h[b].querySelector("[data-ln-table-col-filter], .table-filter");g&&g.classList.remove("ln-filter-active")}const w=f.getAttribute("data-ln-table-source")||f.id,y=w?document.getElementById(w):null;if(y&&y.hasAttribute("data-ln-search"))y.setAttribute("data-ln-search","");else{const b=i(o,w);b&&b.value!==""&&(b.value="",b.dispatchEvent(new Event("input",{bubbles:!0})))}const C=l(o,w);for(let b=0;b<C.length;b++){const g=C[b].querySelector("[data-ln-filter-reset]");if(!g)continue;const A=C[b].querySelectorAll("input:not([data-ln-filter-reset]):checked").length>0;(!g.checked||A)&&(g.checked=!0,g.dispatchEvent(new Event("change",{bubbles:!0})))}f.lnTable&&!f.hasAttribute("data-ln-table-source")&&I(f,"ln-table:request-clear-filters",{table:m})}},o.addEventListener("ln-filter:change",a._handlers.filter),o.addEventListener("click",a._handlers.clear)}r.prototype.destroy=function(){this.dom[t]&&(this._handlers&&(this.dom.removeEventListener("ln-filter:change",this._handlers.filter),this.dom.removeEventListener("click",this._handlers.clear),this._handlers=null),delete this.dom[t])},le(e,t,r,"ln-table-coordinator",{attributes:n})})()})();
@@ -0,0 +1,93 @@
1
+ {
2
+ "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
+ "component": "ln-table-coordinator",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-field": {
7
+ "direction": "author",
8
+ "sources": [
9
+ "src/ln-table-coordinator.js"
10
+ ]
11
+ },
12
+ "data-ln-filter": {
13
+ "direction": "author",
14
+ "sources": [
15
+ "src/ln-table-coordinator.js"
16
+ ]
17
+ },
18
+ "data-ln-filter-col": {
19
+ "direction": "author",
20
+ "sources": [
21
+ "src/ln-table-coordinator.js"
22
+ ]
23
+ },
24
+ "data-ln-filter-key": {
25
+ "direction": "author",
26
+ "sources": [
27
+ "src/ln-table-coordinator.js"
28
+ ]
29
+ },
30
+ "data-ln-filter-reset": {
31
+ "direction": "author",
32
+ "sources": [
33
+ "src/ln-table-coordinator.js"
34
+ ]
35
+ },
36
+ "data-ln-search": {
37
+ "direction": "both",
38
+ "sources": [
39
+ "src/ln-table-coordinator.js"
40
+ ]
41
+ },
42
+ "data-ln-search-for": {
43
+ "direction": "author",
44
+ "sources": [
45
+ "src/ln-table-coordinator.js"
46
+ ]
47
+ },
48
+ "data-ln-table": {
49
+ "direction": "author",
50
+ "sources": [
51
+ "src/ln-table-coordinator.js"
52
+ ]
53
+ },
54
+ "data-ln-table-clear": {
55
+ "direction": "author",
56
+ "sources": [
57
+ "src/ln-table-coordinator.js"
58
+ ]
59
+ },
60
+ "data-ln-table-clear-all": {
61
+ "direction": "author",
62
+ "sources": [
63
+ "src/ln-table-coordinator.js"
64
+ ]
65
+ },
66
+ "data-ln-table-col-filter": {
67
+ "direction": "author",
68
+ "sources": [
69
+ "src/ln-table-coordinator.js"
70
+ ]
71
+ },
72
+ "data-ln-table-coordinator": {
73
+ "direction": "author",
74
+ "sources": [
75
+ "src/ln-table-coordinator.js"
76
+ ],
77
+ "type": "marker",
78
+ "description": "Mounts table coordinator mediating between table, search, filter, and pagination"
79
+ },
80
+ "data-ln-table-filter-col": {
81
+ "direction": "author",
82
+ "sources": [
83
+ "src/ln-table-coordinator.js"
84
+ ]
85
+ },
86
+ "data-ln-table-source": {
87
+ "direction": "author",
88
+ "sources": [
89
+ "src/ln-table-coordinator.js"
90
+ ]
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,193 @@
1
+ import { registerComponent, dispatch } from '../../ln-core';
2
+
3
+ (function () {
4
+ const DOM_SELECTOR = 'data-ln-table-coordinator';
5
+ const DOM_ATTRIBUTE = 'lnTableCoordinator';
6
+
7
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
8
+
9
+ // ─── Attribute Contract (SSOT) ──────────────────────────
10
+ const ATTRIBUTES = {
11
+ 'data-ln-table-coordinator': { type: 'marker', description: 'Mounts table coordinator mediating between table, search, filter, and pagination' }
12
+ };
13
+
14
+ // ─── Keyboard Navigation Search Focus ('/' shortcut) ──
15
+ // Deliberate, user-approved exception to host-scoping: this is a
16
+ // page-level keyboard affordance, not coordination between a host and
17
+ // its children, so it stays a single module-scope document listener
18
+ // exactly as it behaves today (unchanged resolution order). Registering
19
+ // it per-instance inside _bindEvents would fire it once per coordinator
20
+ // on the page — this is why it lives outside _component/_bindEvents.
21
+
22
+ document.addEventListener('keydown', function (e) {
23
+ if (e.key !== '/') return;
24
+ if (e.defaultPrevented) return;
25
+ if (document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.isContentEditable)) return;
26
+
27
+ // Resolve search input inside active coordinator wrapper or first visible search
28
+ const searchHost = document.querySelector('[' + DOM_SELECTOR + '] [data-ln-search-for]')
29
+ || document.querySelector('[data-ln-search-for]');
30
+ if (!searchHost) return;
31
+
32
+ const input = (searchHost.tagName === 'INPUT' || searchHost.tagName === 'TEXTAREA')
33
+ ? searchHost
34
+ : searchHost.querySelector('input[type="search"], input[type="text"], input');
35
+
36
+ if (input) {
37
+ e.preventDefault();
38
+ input.focus();
39
+ }
40
+ });
41
+
42
+ // ─── Component Constructor ─────────────────────────────
43
+
44
+ function _component(dom) {
45
+ this.dom = dom;
46
+ _bindEvents(this);
47
+ return this;
48
+ }
49
+
50
+ function _findSearchInput(dom, sourceId) {
51
+ const sel = sourceId ? '[data-ln-search-for="' + sourceId + '"]' : '[data-ln-search-for]';
52
+ const host = dom.querySelector(sel) || document.querySelector(sel);
53
+ if (!host) return null;
54
+ return (host.tagName === 'INPUT' || host.tagName === 'TEXTAREA')
55
+ ? host
56
+ : host.querySelector('input, textarea');
57
+ }
58
+
59
+ function _findFilterElements(dom, sourceId) {
60
+ if (sourceId) {
61
+ const scoped = dom.querySelectorAll('[data-ln-filter="' + sourceId + '"]');
62
+ if (scoped.length > 0) return scoped;
63
+ const global = document.querySelectorAll('[data-ln-filter="' + sourceId + '"]');
64
+ if (global.length > 0) return global;
65
+ }
66
+ const fallbackScoped = dom.querySelectorAll('[data-ln-filter]');
67
+ return fallbackScoped.length > 0 ? fallbackScoped : document.querySelectorAll('[data-ln-filter]');
68
+ }
69
+
70
+ // ─── Event Binding (host-scoped — own subtree only) ────
71
+
72
+ function _bindEvents(self) {
73
+ const dom = self.dom;
74
+
75
+ // Point-to-point only: the event either landed on the table itself, or it
76
+ // names the source that table is bound to. No "first table in the host"
77
+ // fallback — ln-filter fires ln-filter:change on its own <ul>
78
+ // root AND on the target, so a fallback makes the coordinator act twice
79
+ // for one user change and emit two identical fetches.
80
+ function _resolveTable(e) {
81
+ const el = e.target;
82
+ if (el && el.hasAttribute && (el.hasAttribute('data-ln-table') || el.tagName === 'TABLE')) return el;
83
+ const targetId = (e.detail && e.detail.targetId) || (el && el.id);
84
+ if (!targetId) return null;
85
+ return dom.querySelector('[data-ln-table-source="' + targetId + '"]') ||
86
+ dom.querySelector('[data-ln-table="' + targetId + '"]') ||
87
+ dom.querySelector('#' + targetId) ||
88
+ (dom.id === targetId ? dom : null) ||
89
+ document.getElementById(targetId);
90
+ }
91
+
92
+ self._handlers = {
93
+ // Query state is not forwarded here. The source owns search/filter/sort
94
+ // (docs/architecture/shared-query.md) and ln-data-coordinator re-serves
95
+ // every view bound to it — a second forwarder would fetch twice for one
96
+ // user change. What is left is the header indicator, which is Layer 2
97
+ // policy: ln-table never sets this class itself.
98
+ filter: function (e) {
99
+ if (!e.detail) return;
100
+ const table = _resolveTable(e);
101
+ if (!table) return;
102
+
103
+ const key = e.detail.key;
104
+ const values = e.detail.values || [];
105
+
106
+ const ths = table.querySelectorAll('th');
107
+ for (let i = 0; i < ths.length; i++) {
108
+ const colKey = ths[i].getAttribute('data-ln-table-filter-col') ||
109
+ ths[i].getAttribute('data-ln-filter-col') ||
110
+ ths[i].getAttribute('data-ln-filter-key') ||
111
+ ths[i].getAttribute('data-ln-field');
112
+ if (colKey === key) {
113
+ const btn = ths[i].querySelector('[data-ln-table-col-filter], .table-filter');
114
+ if (btn) btn.classList.toggle('ln-filter-active', values.length > 0);
115
+ break;
116
+ }
117
+ }
118
+ },
119
+
120
+ // Clear-all has no ID binding of its own — resolve structurally,
121
+ // scoped to this host only (never document-wide).
122
+ clear: function (e) {
123
+ const clearBtn = e.target.closest('[data-ln-table-clear], [data-ln-table-clear-all]');
124
+ if (!clearBtn) return;
125
+
126
+ const table = clearBtn.closest('[data-ln-table], table') || dom.querySelector('[data-ln-table], table');
127
+ if (!table) return;
128
+
129
+ const name = (table.lnTable && table.lnTable.name) || table.id;
130
+
131
+ const ths = table.querySelectorAll('th');
132
+ for (let i = 0; i < ths.length; i++) {
133
+ const filterBtn = ths[i].querySelector('[data-ln-table-col-filter], .table-filter');
134
+ if (filterBtn) filterBtn.classList.remove('ln-filter-active');
135
+ }
136
+
137
+ // Query controls address the source (docs/architecture/shared-query.md);
138
+ // only an SSR table is its own source.
139
+ const sourceId = table.getAttribute('data-ln-table-source') || table.id;
140
+ const sourceEl = sourceId ? document.getElementById(sourceId) : null;
141
+ if (sourceEl && sourceEl.hasAttribute('data-ln-search')) {
142
+ // State component syncs input controls and emits ln-search:change
143
+ sourceEl.setAttribute('data-ln-search', '');
144
+ } else {
145
+ const input = _findSearchInput(dom, sourceId);
146
+ if (input && input.value !== '') {
147
+ input.value = '';
148
+ input.dispatchEvent(new Event('input', { bubbles: true }));
149
+ }
150
+ }
151
+
152
+ const filterEls = _findFilterElements(dom, sourceId);
153
+ for (let i = 0; i < filterEls.length; i++) {
154
+ const resetInput = filterEls[i].querySelector('[data-ln-filter-reset]');
155
+ if (!resetInput) continue;
156
+ const hasActiveValues = filterEls[i].querySelectorAll('input:not([data-ln-filter-reset]):checked').length > 0;
157
+ if (!resetInput.checked || hasActiveValues) {
158
+ resetInput.checked = true;
159
+ resetInput.dispatchEvent(new Event('change', { bubbles: true }));
160
+ }
161
+ }
162
+
163
+ // A data-driven table is refreshed by its source: the control resets
164
+ // above already emit the query change. Only an SSR table, which holds
165
+ // its own rows, still needs to be told directly.
166
+ if (table.lnTable && !table.hasAttribute('data-ln-table-source')) {
167
+ dispatch(table, 'ln-table:request-clear-filters', { table: name });
168
+ }
169
+ }
170
+ };
171
+
172
+ dom.addEventListener('ln-filter:change', self._handlers.filter);
173
+ dom.addEventListener('click', self._handlers.clear);
174
+ }
175
+
176
+ // ─── Destroy ────────────────────────────────────────────
177
+
178
+ _component.prototype.destroy = function () {
179
+ if (!this.dom[DOM_ATTRIBUTE]) return;
180
+
181
+ if (this._handlers) {
182
+ this.dom.removeEventListener('ln-filter:change', this._handlers.filter);
183
+ this.dom.removeEventListener('click', this._handlers.clear);
184
+ this._handlers = null;
185
+ }
186
+
187
+ delete this.dom[DOM_ATTRIBUTE];
188
+ };
189
+
190
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-table-coordinator', {
191
+ attributes: ATTRIBUTES
192
+ });
193
+ })();
@@ -1,6 +1,9 @@
1
1
  # ln-tabs
2
2
 
3
- > N-way exclusive panel selection on a single container, managed reactively via the DOM.
3
+ > Applied to a wrapper `<section data-ln-tabs>` containing trigger buttons (`data-ln-tab="key"`) and panels (`data-ln-panel="key"`).
4
+ > On trigger `click`, it writes `data-ln-tabs-active="key"` on the wrapper. An attribute observer synchronizes the DOM:
5
+ > toggling `class="hidden"` and `aria-hidden` on panels, and updating `aria-selected` on triggers. When configured with an `id`
6
+ > and anchor triggers (`<a href="#id:key">`), it synchronizes the active tab with `window.location.hash` for deep-linking and browser history persistence.
4
7
 
5
8
  ---
6
9
 
@@ -48,9 +51,9 @@ Triggers and panels are bound via matching keys inside a wrapper. Inactive panel
48
51
 
49
52
  ## 3. The Declarative API & State Contract
50
53
 
51
- There are no imperative JavaScript methods (like `activate()` or `open()`) on the component instance. **The HTML attribute is the sole contract.**
54
+ State changes are driven declaratively via the `data-ln-tabs-active` attribute. **The HTML attribute is the sole source of truth.**
52
55
 
53
- Clicks, URL hash changes, localStorage restorations, and external scripts all change state by writing the active attribute on the wrapper:
56
+ Clicks, URL hash changes, localStorage restorations, and external scripts all change state by writing the active attribute on the wrapper, dispatching request events, or calling the instance helper method:
54
57
 
55
58
  ```js
56
59
  const tabs = document.getElementById('user-tabs');
@@ -58,6 +61,12 @@ const tabs = document.getElementById('user-tabs');
58
61
  // Canonical write — switches the active tab
59
62
  tabs.setAttribute('data-ln-tabs-active', 'settings');
60
63
 
64
+ // Or via command event
65
+ tabs.dispatchEvent(new CustomEvent('ln-tabs:request-select', { detail: { key: 'settings' }, bubbles: true }));
66
+
67
+ // Or via instance attribute-bridge method
68
+ tabs.lnTabs.select('settings');
69
+
61
70
  // Read-only state query
62
71
  tabs.getAttribute('data-ln-tabs-active'); // Returns currently active key
63
72
  ```
@@ -82,19 +91,28 @@ Mixing both trigger types in one group falls back to persist mode and logs a con
82
91
 
83
92
  ---
84
93
 
85
- ## 4. Transition Events
94
+ ## 4. Transition & Command Events
86
95
 
87
96
  All events bubble. The dispatch target is the wrapper element.
88
97
 
89
- | Event | Cancelable | `detail` | Dispatched When |
90
- |---|---|---|---|
91
- | **`ln-tabs:change`** | No | `{ key, tab, panel }` | After the active panel is swapped, ARIA synced, focus moved (if enabled), and localStorage updated. |
92
- | **`ln-tabs:destroyed`** | No | `{ target }` | Inside `destroy()`, after removing click and hashchange listeners. |
98
+ | Event | Direction | Cancelable | `detail` | Dispatched / Handled When |
99
+ |---|:---:|:---:|---|---|
100
+ | **`ln-tabs:request-select`** | Listens | No | `{ key }` | Command event sent by coordinators or external triggers to select a tab. |
101
+ | **`ln-tabs:before-change`** | Emits | **Yes** | `{ key, previousKey, tab, panel, target }` | Dispatched before active tab changes. Calling `event.preventDefault()` cancels the switch and reverts attribute/hash state. |
102
+ | **`ln-tabs:change`** | Emits | No | `{ key, previousKey, tab, panel, target }` | After the active panel is swapped, ARIA synced, focus moved (if enabled), and localStorage updated. |
93
103
 
94
104
  ```js
105
+ // Example: Prevent tab change if unsaved changes exist
106
+ document.addEventListener('ln-tabs:before-change', (e) => {
107
+ if (hasUnsavedChanges) {
108
+ e.preventDefault();
109
+ alert('Please save changes before leaving this tab.');
110
+ }
111
+ });
112
+
95
113
  // Example: Listen for tab changes
96
114
  document.addEventListener('ln-tabs:change', (e) => {
97
- console.log(`Active tab in ${e.target.id} changed to: ${e.detail.key}`);
115
+ console.log(`Active tab changed from ${e.detail.previousKey} to: ${e.detail.key}`);
98
116
  });
99
117
  ```
100
118
 
@@ -146,16 +164,16 @@ Use **button triggers** and add `data-ln-persist="key"` to remember the active t
146
164
  ```
147
165
  - **Standalone IIFE**: For lightweight pages, load the standalone, self-registering IIFE version:
148
166
  ```html
149
- <script src="js/ln-tabs/ln-tabs.js" defer></script>
167
+ <script src="components/ln-tabs/ln-tabs.js" defer></script>
150
168
  ```
151
- - **Active Source (ESM)**: Development source is located at [js/ln-tabs/src/ln-tabs.js](file:///c:/laragon/www/ln-ashlar/js/ln-tabs/src/ln-tabs.js).
169
+ - **Active Source (ESM)**: Development source is located at [components/ln-tabs/src/ln-tabs.js](file:///c:/laragon/www/ln-ashlar/components/ln-tabs/src/ln-tabs.js).
152
170
 
153
171
  ---
154
172
 
155
173
  ## Hash codec (ln-core shared)
156
174
 
157
175
  ln-tabs uses the shared ln-core hash codec (`hashGet` / `hashSet` /
158
- `hashParse` from `js/ln-core/hash.js`) instead of a private parser. The
176
+ `hashParse` from `components/ln-core/hash.js`) instead of a private parser. The
159
177
  grammar and behaviour are unchanged — anchor-trigger groups still produce
160
178
  `#nsKey:activeKey` fragments, still respond to `hashchange`, and still
161
179
  support back/forward navigation.
@@ -172,7 +190,7 @@ No markup or API change — this is an internal implementation improvement.
172
190
 
173
191
  ## 🔧 Internals
174
192
 
175
- Source: `js/ln-tabs/ln-tabs.js`. Zero cross-component imports — only `ln-core/helpers.js` (`registerComponent`, `dispatch`) and `ln-core/persist.js`.
193
+ Source: `components/ln-tabs/ln-tabs.js`. Zero cross-component imports — only `ln-core` (`registerComponent`, `dispatch`).
176
194
 
177
195
  ### State
178
196
 
@@ -202,7 +220,7 @@ A non-empty `data-ln-tab` value wins if present; otherwise the `href` is split o
202
220
 
203
221
  ### Destroy
204
222
 
205
- Idempotent. Detaches every click handler and clears their double-attach guards, detaches `hashchange` only if hash-enabled, dispatches `ln-tabs:destroyed`, deletes `dom.lnTabs`. Does NOT reset visual state (`data-active`/`aria-selected` survive), clear `localStorage`, or remove `data-ln-tabs-active` — a future re-init resumes from whatever is left.
223
+ Idempotent. Detaches every click handler and clears their double-attach guards, detaches `hashchange` only if hash-enabled, deletes `dom.lnTabs`. Does NOT reset visual state (`data-active`/`aria-selected` survive), clear `localStorage`, or remove `data-ln-tabs-active` — a future re-init resumes from whatever is left.
206
224
 
207
225
  ### Failure modes
208
226
 
@@ -0,0 +1,24 @@
1
+ @use '../../theme/config/mixins' as *;
2
+
3
+ [data-ln-debug] {
4
+ // 1. Anchor Tabs Namespace Check: URL hash mode (<a> triggers) requires an id or data-ln-tabs-key namespace
5
+ [data-ln-tabs]:has(a[data-ln-tab]):not([id]):not([data-ln-tabs-key]),
6
+ [data-ln-tabs]:has(a[href^="#"]):not([id]):not([data-ln-tabs-key]) {
7
+ @include dev-dom-error("Anchor tabs (URL hash mode) require an 'id' or 'data-ln-tabs-key' namespace on [data-ln-tabs].");
8
+ }
9
+
10
+ // 2. Tab Trigger Empty Key: button triggers must have a non-empty key
11
+ button[data-ln-tab=""] {
12
+ @include dev-inline-error("data-ln-tab key cannot be empty");
13
+ }
14
+
15
+ // 3. Tab Trigger Type: button triggers inside forms must be explicitly type="button" to prevent submit events
16
+ form button[data-ln-tab]:not([type="button"]) {
17
+ @include dev-inline-error("tab trigger within form must have type='button'");
18
+ }
19
+
20
+ // 4. Tab Trigger Nesting Check: [data-ln-tab] must be nested inside a [data-ln-tabs] parent container
21
+ [data-ln-tab]:not([data-ln-tabs] *) {
22
+ @include dev-inline-error("[data-ln-tab] is orphaned (not inside a [data-ln-tabs] container)");
23
+ }
24
+ }