@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,1216 +1,1301 @@
1
- import { cloneTemplateScoped, dispatch, dispatchCancelable, requestData, fill, fillTemplate, registerComponent, createWindowCache, createBatcher, getLocale, detectValueType, compareValues } from '../../ln-core';
2
-
3
- (function () {
4
- const DOM_SELECTOR = 'data-ln-list';
5
- const DOM_ATTRIBUTE = 'lnList';
6
- const EMPTY_TEMPLATE = 'data-ln-list-empty';
7
- const VIRTUAL_THRESHOLD = 200;
8
- const BUFFER_ROWS = 15;
9
- const WINDOW_DEFAULT = 1000;
10
- const WINDOW_PAGE = 200;
11
- const FETCH_DEBOUNCE = 120;
12
-
13
- if (window[DOM_ATTRIBUTE] !== undefined) return;
14
-
15
- function _formatNum(n, dom) {
16
- if (n == null || isNaN(n)) return '';
17
- try {
18
- return new Intl.NumberFormat(getLocale(dom)).format(n);
19
- } catch (e) {
20
- return String(n);
21
- }
22
- }
23
-
24
- function _findScrollContainer(el) {
25
- let p = el;
26
- while (p && p !== document.body && p !== document.documentElement) {
27
- const cs = getComputedStyle(p);
28
- const oy = cs.overflowY;
29
- if (oy === 'auto' || oy === 'scroll') return p;
30
- p = p.parentElement;
31
- }
32
- return null;
33
- }
34
-
35
- function _saveScroll(self) {
36
- const sc = self._scrollContainer || _findScrollContainer(self.dom);
37
- return {
38
- container: sc,
39
- top: sc ? sc.scrollTop : window.scrollY
40
- };
41
- }
42
-
43
- function _restoreScroll(state) {
44
- if (state.container) {
45
- state.container.scrollTop = state.top;
46
- } else {
47
- window.scrollTo(window.scrollX, state.top);
48
- }
49
- }
50
-
51
-
52
- function _getOuterHeight(el) {
53
- if (!el) return 0;
54
- const cs = getComputedStyle(el);
55
- const mt = parseFloat(cs.marginTop) || 0;
56
- const mb = parseFloat(cs.marginBottom) || 0;
57
- return el.offsetHeight + mt + mb;
58
- }
59
-
60
- // ─── Component ─────────────────────────────────────────────
61
-
62
- function _component(dom) {
63
- this.dom = dom;
64
- this.tbody = dom.querySelector('[data-ln-list-body]') || dom;
65
- this.isDataDriven = dom.hasAttribute('data-ln-list-source');
66
- this.name = dom.getAttribute(DOM_SELECTOR) || '';
67
- this.source = dom.getAttribute('data-ln-list-source') || '';
68
-
69
- // Footer elements — both modes
70
- this._totalSpan = dom.querySelector('[data-ln-list-total]');
71
- this._filteredSpan = dom.querySelector('[data-ln-list-filtered]');
72
- if (this._filteredSpan) {
73
- this._filteredWrap = this._filteredSpan.parentElement !== dom
74
- ? this._filteredSpan.parentElement
75
- : null;
76
- }
77
- this._selectedSpan = dom.querySelector('[data-ln-list-selected]');
78
- if (this._selectedSpan) {
79
- this._selectedWrap = this._selectedSpan.parentElement !== dom
80
- ? this._selectedSpan.parentElement
81
- : null;
82
- }
83
-
84
- this._data = [];
85
- this._filteredData = [];
86
- this._searchTerm = '';
87
- this._columnFilters = {};
88
- this.selectedIds = new Set();
89
-
90
- // Virtual scroll state
91
- this._virtual = false;
92
- this._itemHeight = 0;
93
- this._vStart = -1;
94
- this._vEnd = -1;
95
- this._rafId = null;
96
- this._scrollHandler = null;
97
- this._resizeHandler = null;
98
- this._scrollContainer = null;
99
- this.isUl = this.tbody.tagName === 'UL' || this.tbody.tagName === 'OL';
100
-
101
- const self = this;
102
-
103
- // --- Selection (both modes) ---
104
- this._selectable = dom.hasAttribute('data-ln-list-selectable');
105
- this._selectableActive = false;
106
- if (this._selectable) {
107
- this._enableSelection();
108
- }
109
-
110
- if (this.isDataDriven) {
111
- this.isLoaded = false;
112
- this.totalCount = 0;
113
- this.visibleCount = 0;
114
- this.currentSort = null;
115
- this.currentFilters = {};
116
- this.currentSearch = '';
117
-
118
- this._windowed = false;
119
- this._cache = null;
120
- if (this.isDataDriven && dom.hasAttribute('data-ln-list-window')) this._enterWindowedMode();
121
-
122
- this._lastTotal = 0;
123
- this._lastFiltered = 0;
124
-
125
- // --- Event listeners ---
126
- this._onSetData = function (e) {
127
- const detail = e.detail || {};
128
- if (self._windowed) {
129
- dom.classList.remove('ln-list--loading');
130
- self._cache.ingest(detail);
131
- return;
132
- }
133
- self._data = detail.data || [];
134
- self._lastTotal = detail.total != null ? detail.total : self._data.length;
135
- self._lastFiltered = detail.filtered != null ? detail.filtered : self._data.length;
136
-
137
- self.totalCount = self._lastTotal;
138
- self.visibleCount = self._lastFiltered;
139
- self.isLoaded = true;
140
-
141
- dom.classList.remove('ln-list--loading');
142
-
143
- self._vStart = -1;
144
- self._vEnd = -1;
145
-
146
- self._applyFilterAndSort();
147
- self._render();
148
- self._updateFooter();
149
-
150
- dispatch(dom, 'ln-list:rendered', {
151
- list: self.name,
152
- total: self.totalCount,
153
- visible: self.visibleCount
154
- });
155
- };
156
- dom.addEventListener('ln-list:set-data', this._onSetData);
157
-
158
- this._onSetLoading = function (e) {
159
- const loading = e.detail && e.detail.loading;
160
- dom.classList.toggle('ln-list--loading', !!loading);
161
- if (loading) {
162
- self.isLoaded = false;
163
- }
164
- };
165
- dom.addEventListener('ln-list:set-loading', this._onSetLoading);
166
-
167
- this._onPageFailed = function (e) {
168
- if (!self._windowed || !self._cache) return;
169
- self._cache.release(e.detail && e.detail.offset);
170
- };
171
- dom.addEventListener('ln-list:page-failed', this._onPageFailed);
172
-
173
- this._onRequestRevalidate = function () {
174
- if (!self._windowed || !self._cache) return;
175
- self._cache.revalidate();
176
- };
177
- dom.addEventListener('ln-list:request-revalidate', this._onRequestRevalidate);
178
-
179
- // Clear all
180
- this._onClearAll = function (e) {
181
- const btn = e.target.closest('[data-ln-list-clear-all]');
182
- if (!btn) return;
183
- self.currentFilters = {};
184
- dispatch(dom, 'ln-list:clear-filters', { list: self.name });
185
- self._requestData();
186
- };
187
- dom.addEventListener('click', this._onClearAll);
188
-
189
- // --- Sort ---
190
- // Mirrors ln-table's own data-driven ln-sort:change handler: windowed lists
191
- // invalidate the sliding-window cache and re-fetch (via the existing
192
- // _requestData(), which already branches on this._windowed — see its
193
- // definition further down this file); non-windowed lists re-sort the
194
- // already-fetched in-memory data locally, no server round-trip.
195
- this._onSort = function (e) {
196
- // Index-only event (field === null) has nothing to key a record by — ignore.
197
- if (e.detail.field == null) return;
198
- e.preventDefault();
199
- self.currentSort = e.detail.direction === 'none' ? null : { field: e.detail.field, direction: e.detail.direction };
200
- if (self._windowed) {
201
- self._requestData();
202
- } else {
203
- self._applyFilterAndSort();
204
- self._vStart = -1;
205
- self._vEnd = -1;
206
- self._render();
207
- }
208
- };
209
- dom.addEventListener('ln-sort:change', this._onSort);
210
-
211
- // D4 — windowed lists cannot select rows they have never fetched
212
- if (this._windowed && this._selectable && this._selectAllCheckbox) {
213
- this._selectAllCheckbox.classList.add('hidden');
214
- }
215
-
216
- // --- Item Click & Actions ---
217
- this._onItemClick = function (e) {
218
- if (e.target.closest('[data-ln-item-select]')) return;
219
- if (e.target.closest('[data-ln-item-action]')) return;
220
- if (e.target.closest('a') || e.target.closest('button')) return;
221
- if (e.ctrlKey || e.metaKey || e.button === 1) return;
222
-
223
- const itemEl = e.target.closest('[data-ln-item]');
224
- if (!itemEl) return;
225
-
226
- const id = itemEl.getAttribute('data-ln-item-id');
227
- const record = itemEl._lnRecord || {};
228
-
229
- dispatch(dom, 'ln-list:item-click', {
230
- list: self.name,
231
- id: id,
232
- record: record
233
- });
234
- };
235
- if (this.tbody) this.tbody.addEventListener('click', this._onItemClick);
236
-
237
- this._onItemAction = function (e) {
238
- const btn = e.target.closest('[data-ln-item-action]');
239
- if (!btn) return;
240
-
241
- e.stopPropagation();
242
- const itemEl = btn.closest('[data-ln-item]');
243
- if (!itemEl) return;
244
-
245
- const action = btn.getAttribute('data-ln-item-action');
246
- const id = itemEl.getAttribute('data-ln-item-id');
247
- const record = itemEl._lnRecord || {};
248
-
249
- dispatch(dom, 'ln-list:item-action', {
250
- list: self.name,
251
- id: id,
252
- action: action,
253
- record: record
254
- });
255
- };
256
- if (this.tbody) this.tbody.addEventListener('click', this._onItemAction);
257
-
258
- // --- Search Change ---
259
- this._onSearchChange = function (e) {
260
- e.preventDefault();
261
- self.currentSearch = (e.detail && e.detail.term) || '';
262
- dispatch(dom, 'ln-list:search', {
263
- list: self.name,
264
- query: self.currentSearch
265
- });
266
- self._requestData();
267
- };
268
- dom.addEventListener('ln-search:change', this._onSearchChange);
269
-
270
- // Local hydration of initial items
271
- if (this.tbody && this.tbody.children.length > 0) {
272
- this._parseChildren();
273
- }
274
-
275
- // Initial request-data
276
- if (this._windowed) {
277
- this._kickWindowInitial();
278
- } else {
279
- dispatch(dom, 'ln-list:request-data', {
280
- list: this.name,
281
- sort: this.currentSort,
282
- filters: this.currentFilters,
283
- search: this.currentSearch
284
- });
285
- }
286
-
287
- } else {
288
- // SSR Mode
289
- this._emptyObserver = null;
290
-
291
- if (this.tbody && this.tbody.children.length > 0) {
292
- this._parseChildren();
293
- } else if (this.tbody) {
294
- this._emptyObserver = new MutationObserver(function () {
295
- if (self.tbody.children.length > 0) {
296
- self._emptyObserver.disconnect();
297
- self._emptyObserver = null;
298
- self._parseChildren();
299
- }
300
- });
301
- this._emptyObserver.observe(this.tbody, { childList: true });
302
- }
303
-
304
- this._onSearch = function (e) {
305
- e.preventDefault();
306
- self._searchTerm = (e.detail && e.detail.term) || '';
307
- self._applyFilterAndSort();
308
- self._vStart = -1;
309
- self._vEnd = -1;
310
- self._render();
311
- self._updateFooter();
312
- dispatch(dom, 'ln-list:filter', {
313
- term: self._searchTerm,
314
- matched: self._filteredData.length,
315
- total: self._data.length
316
- });
317
- };
318
- dom.addEventListener('ln-search:change', this._onSearch);
319
- }
320
-
321
- this._onClear = function (e) {
322
- const btn = e.target.closest('[data-ln-list-clear]');
323
- if (!btn) return;
324
-
325
- const before = dispatchCancelable(dom, 'ln-list:before-clear-search', { list: self.name });
326
- if (before.defaultPrevented) return;
327
-
328
- if (self.isDataDriven) {
329
- self.currentSearch = '';
330
- } else {
331
- self._searchTerm = '';
332
- }
333
-
334
- const searchEl = document.querySelector('[data-ln-search="' + dom.id + '"]');
335
- if (searchEl) {
336
- const input = searchEl.tagName === 'INPUT' ? searchEl : searchEl.querySelector('input');
337
- if (input) {
338
- input.value = '';
339
- input.dispatchEvent(new Event('input', { bubbles: true }));
340
- }
341
- }
342
-
343
- if (self.isDataDriven) {
344
- dispatch(dom, 'ln-list:search', {
345
- list: self.name,
346
- query: ''
347
- });
348
- self._requestData();
349
- } else {
350
- self._applyFilterAndSort();
351
- self._vStart = -1;
352
- self._vEnd = -1;
353
- self._render();
354
- self._updateFooter();
355
- dispatch(dom, 'ln-list:filter', {
356
- term: '',
357
- matched: self._filteredData.length,
358
- total: self._data.length
359
- });
360
- }
361
- };
362
- dom.addEventListener('click', this._onClear);
363
-
364
- return this;
365
- }
366
-
367
- // ─── Parse Children into in-memory array ───────────────────
368
-
369
- _component.prototype._parseChildren = function () {
370
- const children = Array.from(this.tbody.children).filter(el => !el.classList.contains('ln-list__spacer'));
371
- this._data = [];
372
-
373
- if (children.length > 0) this._itemHeight = _getOuterHeight(children[0]) || 50;
374
-
375
- for (let i = 0; i < children.length; i++) {
376
- const el = children[i];
377
- const id = el.getAttribute('data-ln-item-id') || el.getAttribute('id');
378
- const text = el.textContent.trim().toLowerCase();
379
-
380
- let record = null;
381
- if (this.isDataDriven) {
382
- record = {};
383
- if (id != null) record.id = id;
384
-
385
- const fields = el.querySelectorAll('[data-ln-list-field]');
386
- for (let j = 0; j < fields.length; j++) {
387
- const f = fields[j];
388
- const prop = f.getAttribute('data-ln-list-field');
389
- if (prop) {
390
- record[prop] = f.textContent.trim();
391
- }
392
- }
393
- }
394
-
395
- this._data.push({
396
- html: el.outerHTML,
397
- searchText: text,
398
- id: id,
399
- ...record
400
- });
401
- }
402
-
403
- this._filteredData = this._data.slice();
404
-
405
- if (this.isDataDriven) {
406
- this._lastTotal = this._data.length;
407
- this._lastFiltered = this._data.length;
408
- this.totalCount = this._data.length;
409
- this.visibleCount = this._data.length;
410
- this._updateFooter();
411
- }
412
-
413
- this._render();
414
-
415
- dispatch(this.dom, 'ln-list:ready', {
416
- total: this._data.length
417
- });
418
- };
419
-
420
- // ─── Filter + Sort ─────────────────────────────────────────
421
-
422
- _component.prototype._applyFilterAndSort = function () {
423
- if (this.isDataDriven) {
424
- const term = (this.currentSearch || '').trim().toLowerCase();
425
- const filters = this.currentFilters || {};
426
- const hasFilters = Object.keys(filters).length > 0;
427
-
428
- // 1. Filter
429
- this._filteredData = this._data.filter(function (row) {
430
- if (term) {
431
- let match = false;
432
- for (const key in row) {
433
- if (row.hasOwnProperty(key) && typeof row[key] === 'string' && key !== 'html' && key !== 'searchText') {
434
- if (row[key].toLowerCase().indexOf(term) !== -1) {
435
- match = true;
436
- break;
437
- }
438
- }
439
- }
440
- if (!match) return false;
441
- }
442
-
443
- if (hasFilters) {
444
- for (const field in filters) {
445
- const activeVals = filters[field];
446
- if (activeVals && activeVals.length > 0) {
447
- const val = row[field];
448
- const sVal = val != null ? String(val) : '';
449
- if (activeVals.indexOf(sVal) === -1) return false;
450
- }
451
- }
452
- }
453
- return true;
454
- });
455
-
456
- this.visibleCount = this._filteredData.length;
457
-
458
- // 2. Sort
459
- if (!this.currentSort || !this.currentSort.field || !this.currentSort.direction) return;
460
-
461
- const field = this.currentSort.field;
462
- const multiplier = this.currentSort.direction === 'desc' ? -1 : 1;
463
-
464
- const values = this._filteredData.map(function (row) { return row[field]; });
465
- const type = detectValueType(values);
466
- const collator = typeof Intl !== 'undefined'
467
- ? new Intl.Collator(getLocale(this.dom), { sensitivity: 'base' })
468
- : null;
469
-
470
- this._filteredData.sort(function (a, b) {
471
- return compareValues(a[field], b[field], type, collator) * multiplier;
472
- });
473
-
474
- } else {
475
- const term = this._searchTerm;
476
- if (!term) {
477
- this._filteredData = this._data.slice();
478
- } else {
479
- this._filteredData = this._data.filter(function (row) {
480
- return row.searchText.indexOf(term) !== -1;
481
- });
482
- }
483
- }
484
- };
485
-
486
- // ─── Render ────────────────────────────────────────────────
487
-
488
- _component.prototype._render = function () {
489
- if (!this.tbody) return;
490
-
491
- if (this.isDataDriven) {
492
- if (this._windowed) { this._renderWindowed(); return; }
493
- const total = this._lastTotal;
494
- const filtered = this.visibleCount;
495
-
496
- if (total === 0 || this._filteredData.length === 0 || filtered === 0) {
497
- this._disableVirtualScroll();
498
- this._showEmptyState();
499
- return;
500
- }
501
-
502
- if (this._filteredData.length > VIRTUAL_THRESHOLD) {
503
- this._enableVirtualScroll();
504
- this._renderVirtual();
505
- } else {
506
- this._disableVirtualScroll();
507
- this._renderAll();
508
- }
509
- } else {
510
- const count = this._filteredData.length;
511
-
512
- if (count === 0 && this._searchTerm) {
513
- this._disableVirtualScroll();
514
- this._showEmptyState();
515
- } else if (count > VIRTUAL_THRESHOLD) {
516
- this._enableVirtualScroll();
517
- this._renderVirtual();
518
- } else {
519
- this._disableVirtualScroll();
520
- this._renderAll();
521
- }
522
- }
523
- };
524
-
525
- _component.prototype._renderAll = function () {
526
- if (this.isDataDriven) {
527
- const data = this._filteredData;
528
- const frag = document.createDocumentFragment();
529
-
530
- for (let i = 0; i < data.length; i++) {
531
- const el = this._buildItem(data[i]);
532
- if (!el) break;
533
- frag.appendChild(el);
534
- }
535
-
536
- const scrollState = _saveScroll(this);
537
- this.tbody.textContent = '';
538
- this.tbody.appendChild(frag);
539
- _restoreScroll(scrollState);
540
-
541
- if (this._selectable) this._updateSelectAll();
542
- } else {
543
- const html = [];
544
- const data = this._filteredData;
545
- for (let i = 0; i < data.length; i++) html.push(data[i].html);
546
-
547
- const scrollState = _saveScroll(this);
548
- this.tbody.innerHTML = html.join('');
549
- _restoreScroll(scrollState);
550
-
551
- if (this._selectable) this._restoreSelection();
552
- }
553
- };
554
-
555
- // ─── Virtual scroll ────────────────────────────────────────
556
-
557
- _component.prototype._readGridLayout = function () {
558
- const cs = getComputedStyle(this.tbody);
559
- const tracks = cs.gridTemplateColumns;
560
- let columns = 1;
561
- if (tracks && tracks !== 'none') {
562
- const parts = tracks.trim().split(/\s+/).filter(Boolean);
563
- if (parts.length > 0) columns = parts.length;
564
- }
565
- const rowGap = parseFloat(cs.rowGap);
566
- return { columns: columns, rowGap: isNaN(rowGap) ? 0 : rowGap };
567
- };
568
-
569
- _component.prototype._measureItemHeight = function () {
570
- if (this._windowed) {
571
- const sample = this._cache.peek();
572
- const el = sample ? this._buildItem(sample) : this._buildPlaceholderItem();
573
- if (el) {
574
- this.tbody.textContent = '';
575
- this.tbody.appendChild(el);
576
- this._itemHeight = _getOuterHeight(el) || 50;
577
- this.tbody.textContent = '';
578
- }
579
- } else if (this.isDataDriven) {
580
- if (this._data.length > 0) {
581
- const el = this._buildItem(this._data[0]);
582
- if (el) {
583
- this.tbody.textContent = '';
584
- this.tbody.appendChild(el);
585
- this._itemHeight = _getOuterHeight(el) || 50;
586
- this.tbody.textContent = '';
587
- }
588
- }
589
- } else {
590
- const children = this.tbody.children;
591
- if (children.length > 0) {
592
- this._itemHeight = _getOuterHeight(children[0]) || 50;
593
- }
594
- }
595
- };
596
-
597
- _component.prototype._enableVirtualScroll = function () {
598
- if (this._virtual) return;
599
- this._virtual = true;
600
- this._vStart = -1;
601
- this._vEnd = -1;
602
- const self = this;
603
-
604
- if (!this._itemHeight) {
605
- this._measureItemHeight();
606
- }
607
-
608
- this._scrollContainer = _findScrollContainer(this.dom);
609
- const container = this._scrollContainer || window;
610
-
611
- this._scrollHandler = function () {
612
- if (!self._rafId) {
613
- self._rafId = requestAnimationFrame(function () {
614
- self._rafId = null;
615
- self._windowed ? self._renderWindowed() : self._renderVirtual();
616
- });
617
- }
618
- };
619
-
620
- this._resizeHandler = function () {
621
- self._itemHeight = 0;
622
- self._measureItemHeight();
623
- self._vStart = -1;
624
- self._vEnd = -1;
625
- self._windowed ? self._renderWindowed() : self._renderVirtual();
626
- };
627
-
628
- container.addEventListener('scroll', this._scrollHandler, { passive: true });
629
- window.addEventListener('resize', this._resizeHandler, { passive: true });
630
- };
631
-
632
- _component.prototype._disableVirtualScroll = function () {
633
- if (!this._virtual) return;
634
- this._virtual = false;
635
-
636
- if (this._scrollHandler) {
637
- const container = this._scrollContainer || window;
638
- container.removeEventListener('scroll', this._scrollHandler);
639
- this._scrollHandler = null;
640
- }
641
- if (this._resizeHandler) {
642
- window.removeEventListener('resize', this._resizeHandler);
643
- this._resizeHandler = null;
644
- }
645
- this._scrollContainer = null;
646
-
647
- if (this._rafId) {
648
- cancelAnimationFrame(this._rafId);
649
- this._rafId = null;
650
- }
651
- this._vStart = -1;
652
- this._vEnd = -1;
653
- };
654
-
655
- _component.prototype._renderVirtual = function () {
656
- const data = this._filteredData;
657
- const count = data.length;
658
- const itemHeight = this._itemHeight;
659
- if (!itemHeight || !count) return;
660
-
661
- const container = this._scrollContainer;
662
- let scrollTop, viewportHeight;
663
-
664
- if (container) {
665
- const rect = this.tbody.getBoundingClientRect();
666
- const containerRect = container.getBoundingClientRect();
667
- const relativeTop = (container === this.tbody) ? 0 : (rect.top - containerRect.top + container.scrollTop);
668
- scrollTop = container.scrollTop - relativeTop;
669
- viewportHeight = container.clientHeight;
670
- } else {
671
- const rect = this.tbody.getBoundingClientRect();
672
- const absoluteTop = rect.top + window.scrollY;
673
- scrollTop = window.scrollY - absoluteTop;
674
- viewportHeight = window.innerHeight;
675
- }
676
-
677
- const layout = this._readGridLayout();
678
- const columns = layout.columns;
679
- const rowGap = layout.rowGap;
680
- const rowHeight = itemHeight + rowGap;
681
- const totalRows = Math.ceil(count / columns);
682
-
683
- let firstRow = Math.max(0, Math.floor(scrollTop / rowHeight) - BUFFER_ROWS);
684
- firstRow = Math.min(firstRow, totalRows);
685
- const visibleRows = Math.ceil(viewportHeight / rowHeight) + BUFFER_ROWS * 2;
686
- const lastRow = Math.min(firstRow + visibleRows, totalRows);
687
-
688
- const start = Math.min(firstRow * columns, count);
689
- const end = Math.min(lastRow * columns, count);
690
-
691
- if (start === this._vStart && end === this._vEnd) return;
692
-
693
- this._vStart = start;
694
- this._vEnd = end;
695
-
696
- const topSpacerHeight = firstRow * rowHeight;
697
- const bottomSpacerHeight = (totalRows - lastRow) * rowHeight;
698
-
699
- if (this.isDataDriven) {
700
- const frag = document.createDocumentFragment();
701
-
702
- if (topSpacerHeight > 0) {
703
- const topSpacer = document.createElement(this.isUl ? 'li' : 'div');
704
- topSpacer.className = 'ln-list__spacer';
705
- topSpacer.style.height = topSpacerHeight + 'px';
706
- frag.appendChild(topSpacer);
707
- }
708
-
709
- for (let i = start; i < end; i++) {
710
- const el = this._buildItem(data[i]);
711
- if (el) frag.appendChild(el);
712
- }
713
-
714
- if (bottomSpacerHeight > 0) {
715
- const bottomSpacer = document.createElement(this.isUl ? 'li' : 'div');
716
- bottomSpacer.className = 'ln-list__spacer';
717
- bottomSpacer.style.height = bottomSpacerHeight + 'px';
718
- frag.appendChild(bottomSpacer);
719
- }
720
-
721
- const scrollState = _saveScroll(this);
722
- this.tbody.textContent = '';
723
- this.tbody.appendChild(frag);
724
- _restoreScroll(scrollState);
725
-
726
- if (this._selectable) this._updateSelectAll();
727
- } else {
728
- let html = '';
729
- if (topSpacerHeight > 0) {
730
- html += `<${this.isUl ? 'li' : 'div'} class="ln-list__spacer" style="height:${topSpacerHeight}px;padding:0;border:none"></${this.isUl ? 'li' : 'div'}>`;
731
- }
732
- for (let i = start; i < end; i++) {
733
- html += data[i].html;
734
- }
735
- if (bottomSpacerHeight > 0) {
736
- html += `<${this.isUl ? 'li' : 'div'} class="ln-list__spacer" style="height:${bottomSpacerHeight}px;padding:0;border:none"></${this.isUl ? 'li' : 'div'}>`;
737
- }
738
-
739
- const scrollState = _saveScroll(this);
740
- this.tbody.innerHTML = html;
741
- _restoreScroll(scrollState);
742
-
743
- if (this._selectable) this._restoreSelection();
744
- }
745
- };
746
-
747
- _component.prototype._buildPlaceholderItem = function () {
748
- const el = document.createElement(this.isUl ? 'li' : 'div');
749
- el.className = 'ln-list__placeholder';
750
- el.setAttribute('aria-hidden', 'true');
751
- el.style.height = this._itemHeight + 'px';
752
- return el;
753
- };
754
-
755
- _component.prototype._renderWindowed = function () {
756
- if (this.isLoaded && this._cache.logicalTotal === 0) {
757
- this._disableVirtualScroll();
758
- this._showEmptyState();
759
- return;
760
- }
761
- if (!this._virtual) this._enableVirtualScroll();
762
-
763
- const itemHeight = this._itemHeight;
764
- if (!itemHeight) return;
765
-
766
- const container = this._scrollContainer;
767
- let scrollTop, viewportHeight;
768
- if (container) {
769
- const rect = this.tbody.getBoundingClientRect();
770
- const containerRect = container.getBoundingClientRect();
771
- const relativeTop = (container === this.tbody) ? 0 : (rect.top - containerRect.top + container.scrollTop);
772
- scrollTop = container.scrollTop - relativeTop;
773
- viewportHeight = container.clientHeight;
774
- } else {
775
- const rect = this.tbody.getBoundingClientRect();
776
- const absoluteTop = rect.top + window.scrollY;
777
- scrollTop = window.scrollY - absoluteTop;
778
- viewportHeight = window.innerHeight;
779
- }
780
-
781
- const layout = this._readGridLayout();
782
- const columns = layout.columns;
783
- const rowGap = layout.rowGap;
784
- const rowHeight = itemHeight + rowGap;
785
- const total = this._cache.logicalTotal;
786
- const totalRows = Math.ceil(total / columns);
787
-
788
- let firstRow = Math.max(0, Math.floor(scrollTop / rowHeight) - BUFFER_ROWS);
789
- firstRow = Math.min(firstRow, totalRows);
790
- const visibleRows = Math.ceil(viewportHeight / rowHeight) + BUFFER_ROWS * 2;
791
- const lastRow = Math.min(firstRow + visibleRows, totalRows);
792
-
793
- const start = Math.min(firstRow * columns, total);
794
- const end = Math.min(lastRow * columns, total);
795
-
796
- // No start===_vStart early-return: windowed must re-render when a page
797
- // splices in even if the range is unchanged.
798
-
799
- const topSpacerHeight = firstRow * rowHeight;
800
- const bottomSpacerHeight = (totalRows - lastRow) * rowHeight;
801
-
802
- const frag = document.createDocumentFragment();
803
- if (topSpacerHeight > 0) {
804
- const topSpacer = document.createElement(this.isUl ? 'li' : 'div');
805
- topSpacer.className = 'ln-list__spacer';
806
- topSpacer.style.height = topSpacerHeight + 'px';
807
- frag.appendChild(topSpacer);
808
- }
809
- for (let i = start; i < end; i++) {
810
- if (this._cache.has(i)) {
811
- const el = this._buildItem(this._cache.get(i));
812
- if (el) frag.appendChild(el);
813
- } else {
814
- frag.appendChild(this._buildPlaceholderItem());
815
- }
816
- }
817
- if (bottomSpacerHeight > 0) {
818
- const bottomSpacer = document.createElement(this.isUl ? 'li' : 'div');
819
- bottomSpacer.className = 'ln-list__spacer';
820
- bottomSpacer.style.height = bottomSpacerHeight + 'px';
821
- frag.appendChild(bottomSpacer);
822
- }
823
-
824
- const scrollState = _saveScroll(this);
825
- this.tbody.textContent = '';
826
- this.tbody.appendChild(frag);
827
- _restoreScroll(scrollState);
828
-
829
- // Select-all disabled in windowed mode — no _updateSelectAll().
830
- this._vStart = start;
831
- this._vEnd = end;
832
-
833
- this._cache.ensure(start, end);
834
- };
835
-
836
- _component.prototype._showEmptyState = function () {
837
- this.tbody.textContent = '';
838
-
839
- let el = null;
840
- if (this.isDataDriven) {
841
- const total = this._lastTotal != null ? this._lastTotal : this._data.length;
842
- const visible = this.visibleCount;
843
- const isFiltered = this.currentSearch && (visible < total || visible === 0);
844
- const templateName = isFiltered ? this.name + '-empty-filtered' : this.name + '-empty';
845
-
846
- el = cloneTemplateScoped(this.dom, templateName, 'ln-list');
847
- if (!el) {
848
- const fallback = this.dom.querySelector('template[data-ln-empty]');
849
- if (fallback) {
850
- const condition = isFiltered ? 'search' : 'initial';
851
- const matched = fallback.content.querySelector(`[data-ln-empty-when="${condition}"]`) || fallback.content.firstElementChild;
852
- if (matched) el = document.importNode(matched, true);
853
- }
854
- }
855
- } else {
856
- const fallback = this.dom.querySelector(`template[${EMPTY_TEMPLATE}]`);
857
- if (fallback) {
858
- el = document.importNode(fallback.content, true);
859
- }
860
- }
861
-
862
- if (el) {
863
- if (el.tagName === 'LI' || el.tagName === 'TR') {
864
- this.tbody.appendChild(el);
865
- } else {
866
- const item = document.createElement(this.isUl ? 'li' : 'div');
867
- item.appendChild(el);
868
- this.tbody.appendChild(item);
869
- }
870
- }
871
-
872
- dispatch(this.dom, 'ln-list:empty', {
873
- term: this.isDataDriven ? this.currentSearch : this._searchTerm,
874
- total: this.isDataDriven ? (this._lastTotal != null ? this._lastTotal : this._data.length) : this._data.length
875
- });
876
- };
877
-
878
- _component.prototype._buildItem = function (item) {
879
- const clone = cloneTemplateScoped(this.dom, this.name + '-row', 'ln-list');
880
- if (!clone) return null;
881
-
882
- const el = clone.querySelector('[data-ln-item]') || clone.firstElementChild;
883
- if (!el) return null;
884
-
885
- fillTemplate(el, item);
886
- fill(el, item);
887
-
888
- el._lnRecord = item;
889
- if (item.id != null) {
890
- el.setAttribute('data-ln-item-id', item.id);
891
- if (this._selectable && this.selectedIds.has(String(item.id))) {
892
- el.classList.add('ln-item-selected');
893
- const cb = el.querySelector('[data-ln-item-select]');
894
- if (cb) cb.checked = true;
895
- }
896
- }
897
-
898
- return el;
899
- };
900
-
901
- // ─── Selection Helpers ─────────────────────────────────────
902
-
903
- _component.prototype._enableSelection = function () {
904
- if (this._selectableActive) return;
905
- this._selectableActive = true;
906
-
907
- const self = this;
908
-
909
- this._onSelectionChange = function (e) {
910
- const cb = e.target.closest('[data-ln-item-select]');
911
- if (!cb) return;
912
-
913
- const itemEl = cb.closest('[data-ln-item]');
914
- if (!itemEl) return;
915
-
916
- const id = itemEl.getAttribute('data-ln-item-id');
917
- if (id == null) return;
918
-
919
- if (cb.checked) {
920
- self.selectedIds.add(String(id));
921
- itemEl.classList.add('ln-item-selected');
922
- } else {
923
- self.selectedIds.delete(String(id));
924
- itemEl.classList.remove('ln-item-selected');
925
- }
926
-
927
- self._updateSelectAll();
928
- self._updateFooter();
929
-
930
- dispatch(self.dom, 'ln-list:select', {
931
- list: self.name,
932
- selectedIds: self.selectedIds,
933
- count: self.selectedIds.size
934
- });
935
- };
936
- this.tbody.addEventListener('change', this._onSelectionChange);
937
-
938
- this._selectAllCheckbox = this.dom.querySelector('[data-ln-list-select-all]');
939
- if (this._selectAllCheckbox) {
940
- this._onSelectAll = function () {
941
- const checked = self._selectAllCheckbox.checked;
942
- const items = self.tbody.querySelectorAll('[data-ln-item]');
943
- for (let i = 0; i < items.length; i++) {
944
- const itemEl = items[i];
945
- const id = itemEl.getAttribute('data-ln-item-id');
946
- const cb = itemEl.querySelector('[data-ln-item-select]');
947
- if (id != null) {
948
- if (checked) {
949
- self.selectedIds.add(String(id));
950
- itemEl.classList.add('ln-item-selected');
951
- } else {
952
- self.selectedIds.delete(String(id));
953
- itemEl.classList.remove('ln-item-selected');
954
- }
955
- if (cb) cb.checked = checked;
956
- }
957
- }
958
-
959
- dispatch(self.dom, 'ln-list:select-all', { list: self.name, selected: checked });
960
- dispatch(self.dom, 'ln-list:select', {
961
- list: self.name,
962
- selectedIds: self.selectedIds,
963
- count: self.selectedIds.size
964
- });
965
- self._updateFooter();
966
- };
967
- this._selectAllCheckbox.addEventListener('change', this._onSelectAll);
968
- }
969
- };
970
-
971
- _component.prototype._updateSelectAll = function () {
972
- if (!this._selectAllCheckbox) return;
973
- const items = this.tbody.querySelectorAll('[data-ln-item]');
974
- let allSelected = items.length > 0;
975
- for (let i = 0; i < items.length; i++) {
976
- const id = items[i].getAttribute('data-ln-item-id');
977
- if (id != null && !this.selectedIds.has(String(id))) {
978
- allSelected = false;
979
- break;
980
- }
981
- }
982
- this._selectAllCheckbox.checked = allSelected;
983
- };
984
-
985
- // SSR re-render replays cached item HTML — reapply live selection state.
986
- _component.prototype._restoreSelection = function () {
987
- if (!this.tbody) return;
988
- const items = this.tbody.querySelectorAll('[data-ln-item]');
989
- for (let i = 0; i < items.length; i++) {
990
- const id = items[i].getAttribute('data-ln-item-id');
991
- const selected = id != null && this.selectedIds.has(String(id));
992
- items[i].classList.toggle('ln-item-selected', selected);
993
- const cb = items[i].querySelector('[data-ln-item-select]');
994
- if (cb) cb.checked = selected;
995
- }
996
- this._updateSelectAll();
997
- };
998
-
999
- _component.prototype._requestData = function () {
1000
- if (this._windowed) {
1001
- this.dom.classList.add('ln-list--loading');
1002
- this._cache.invalidate({
1003
- sort: this.currentSort,
1004
- filters: this.currentFilters,
1005
- search: this.currentSearch
1006
- });
1007
- return;
1008
- }
1009
- requestData(this, 'ln-list:request-data', 'list');
1010
- };
1011
-
1012
- // ─── Windowed Mode — enter/exit/seed (live toggle) ──────────
1013
-
1014
- _component.prototype._enterWindowedMode = function () {
1015
- const self = this;
1016
- const dom = this.dom;
1017
- const winAttr = parseInt(dom.getAttribute('data-ln-list-window'), 10);
1018
- const pageAttr = parseInt(dom.getAttribute('data-ln-list-window-page'), 10);
1019
- const threshAttr = parseInt(dom.getAttribute('data-ln-list-window-threshold'), 10);
1020
-
1021
- this._onCacheChange = function () {
1022
- if (!self._windowed || !self._cache) return;
1023
- self.totalCount = self._cache.grandTotal;
1024
- self.visibleCount = self._cache.logicalTotal;
1025
- self._lastTotal = self._cache.grandTotal;
1026
- self.isLoaded = true;
1027
- self._vStart = -1;
1028
- self._vEnd = -1;
1029
- self._render();
1030
- self._updateFooter();
1031
- dispatch(dom, 'ln-list:rendered', {
1032
- list: self.name,
1033
- total: self.totalCount,
1034
- visible: self.visibleCount
1035
- });
1036
- };
1037
- this._renderBatch = createBatcher(this._onCacheChange);
1038
-
1039
- this._cache = createWindowCache({
1040
- windowSize: winAttr > 0 ? winAttr : WINDOW_DEFAULT,
1041
- pageSize: pageAttr > 0 ? pageAttr : WINDOW_PAGE,
1042
- threshold: threshAttr >= 0 ? threshAttr : 25,
1043
- fetchDebounce: FETCH_DEBOUNCE,
1044
- requestPage: function (query, offset, limit) {
1045
- dispatch(dom, 'ln-list:request-data', {
1046
- list: self.name,
1047
- sort: query.sort,
1048
- filters: query.filters,
1049
- search: query.search,
1050
- offset: offset,
1051
- limit: limit,
1052
- queryGen: self._cache.queryGen
1053
- });
1054
- },
1055
- onChange: this._renderBatch
1056
- });
1057
-
1058
- this._windowed = true;
1059
-
1060
- if (this._selectable && this._selectAllCheckbox) {
1061
- this._selectAllCheckbox.classList.add('hidden');
1062
- }
1063
- };
1064
-
1065
- _component.prototype._kickWindowInitial = function () {
1066
- if (this._data.length > 0) {
1067
- // SSR-seeded / warm-seeded: page 0 is already resident, the grand
1068
- // total is declared in markup — no initial fetch needed. No
1069
- // queryGen on the seed — it must never be dropped as stale.
1070
- const declaredTotal = parseInt(this.dom.getAttribute('data-ln-list-count'), 10);
1071
- const seedTotal = declaredTotal > 0 ? declaredTotal : this._data.length;
1072
- this._cache.ingest({
1073
- data: this._data,
1074
- offset: 0,
1075
- total: seedTotal,
1076
- filtered: seedTotal
1077
- });
1078
- } else {
1079
- this.dom.classList.add('ln-list--loading');
1080
- this._cache.requestInitial({
1081
- sort: this.currentSort,
1082
- filters: this.currentFilters,
1083
- search: this.currentSearch
1084
- });
1085
- }
1086
- };
1087
-
1088
- _component.prototype._exitWindowedMode = function () {
1089
- this._disableVirtualScroll();
1090
- if (this._cache) this._cache.destroy();
1091
- this._cache = null;
1092
- this._windowed = false;
1093
- this._renderBatch = null;
1094
- this._onCacheChange = null;
1095
- if (this._selectAllCheckbox) {
1096
- this._selectAllCheckbox.classList.remove('hidden');
1097
- }
1098
- this._itemHeight = 0;
1099
- this._vStart = -1;
1100
- this._vEnd = -1;
1101
- this._data = [];
1102
- this._filteredData = [];
1103
- this.dom.classList.add('ln-list--loading');
1104
- this._requestData();
1105
- };
1106
-
1107
- _component.prototype._updateFooter = function () {
1108
- let total = 0;
1109
- let filtered = 0;
1110
-
1111
- if (this.isDataDriven) {
1112
- total = this._lastTotal != null ? this._lastTotal : this._data.length;
1113
- filtered = this.visibleCount;
1114
- } else {
1115
- total = this._data.length;
1116
- filtered = this._filteredData.length;
1117
- }
1118
-
1119
- const hasActiveFilter = filtered < total;
1120
-
1121
- if (this._totalSpan) this._totalSpan.textContent = _formatNum(total, this.dom);
1122
- if (this._filteredSpan) this._filteredSpan.textContent = hasActiveFilter ? _formatNum(filtered, this.dom) : '';
1123
- if (this._filteredWrap) this._filteredWrap.classList.toggle('hidden', !hasActiveFilter);
1124
-
1125
- if (this._selectedSpan) {
1126
- const selected = this.selectedIds ? this.selectedIds.size : 0;
1127
- this._selectedSpan.textContent = selected > 0 ? _formatNum(selected, this.dom) : '';
1128
- if (this._selectedWrap) {
1129
- this._selectedWrap.classList.toggle('hidden', selected === 0);
1130
- }
1131
- }
1132
- };
1133
-
1134
- _component.prototype.destroy = function () {
1135
- if (!this.dom[DOM_ATTRIBUTE]) return;
1136
-
1137
- this._disableVirtualScroll();
1138
-
1139
- if (this.isDataDriven) {
1140
- if (this._cache) this._cache.destroy();
1141
- this.dom.removeEventListener('ln-list:set-data', this._onSetData);
1142
- this.dom.removeEventListener('ln-list:set-loading', this._onSetLoading);
1143
- this.dom.removeEventListener('ln-list:page-failed', this._onPageFailed);
1144
- this.dom.removeEventListener('ln-list:request-revalidate', this._onRequestRevalidate);
1145
- this.dom.removeEventListener('ln-sort:change', this._onSort);
1146
- this.dom.removeEventListener('click', this._onClearAll);
1147
-
1148
- if (this.tbody) {
1149
- this.tbody.removeEventListener('click', this._onItemClick);
1150
- this.tbody.removeEventListener('click', this._onItemAction);
1151
- }
1152
-
1153
- this.dom.removeEventListener('ln-search:change', this._onSearchChange);
1154
- } else {
1155
- if (this._emptyObserver) {
1156
- this._emptyObserver.disconnect();
1157
- this._emptyObserver = null;
1158
- }
1159
- this.dom.removeEventListener('ln-search:change', this._onSearch);
1160
- }
1161
-
1162
- if (this._onSelectionChange && this.tbody) {
1163
- this.tbody.removeEventListener('change', this._onSelectionChange);
1164
- }
1165
- if (this._selectAllCheckbox && this._onSelectAll) {
1166
- this._selectAllCheckbox.removeEventListener('change', this._onSelectAll);
1167
- }
1168
-
1169
- if (this._onClear) {
1170
- this.dom.removeEventListener('click', this._onClear);
1171
- }
1172
-
1173
- this._data = [];
1174
- this._filteredData = [];
1175
- delete this.dom[DOM_ATTRIBUTE];
1176
- };
1177
-
1178
- registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-list', {
1179
- extraAttributes: [
1180
- 'data-ln-list-window',
1181
- 'data-ln-list-window-page',
1182
- 'data-ln-list-window-threshold',
1183
- 'data-ln-list-count'
1184
- ],
1185
- onAttributeChange: function (el, attrName) {
1186
- const inst = el[DOM_ATTRIBUTE];
1187
- if (!inst || !inst.isDataDriven) return;
1188
-
1189
- if (attrName === 'data-ln-list-window') {
1190
- const present = el.hasAttribute('data-ln-list-window');
1191
- if (present && !inst._windowed) {
1192
- inst._enterWindowedMode();
1193
- inst._kickWindowInitial();
1194
- } else if (!present && inst._windowed) {
1195
- inst._exitWindowedMode();
1196
- } else if (present && inst._windowed) {
1197
- const v = parseInt(el.getAttribute('data-ln-list-window'), 10);
1198
- if (v > 0) inst._cache.configure({ windowSize: v });
1199
- }
1200
- return;
1201
- }
1202
-
1203
- if (!inst._windowed || !inst._cache) return;
1204
- if (attrName === 'data-ln-list-window-page') {
1205
- const v = parseInt(el.getAttribute('data-ln-list-window-page'), 10);
1206
- if (v > 0) inst._cache.configure({ pageSize: v });
1207
- } else if (attrName === 'data-ln-list-window-threshold') {
1208
- const v = parseInt(el.getAttribute('data-ln-list-window-threshold'), 10);
1209
- if (v >= 0) inst._cache.configure({ threshold: v });
1210
- } else if (attrName === 'data-ln-list-count') {
1211
- const v = parseInt(el.getAttribute('data-ln-list-count'), 10);
1212
- if (v >= 0) inst._cache.setGrandTotal(v);
1213
- }
1214
- }
1215
- });
1216
- })();
1
+ import { cloneTemplateScoped, dispatch, requestData, fill, fillTemplate, registerComponent, readValue, createWindowCache, createBatcher, getLocale, detectValueType, compareValues, attrBool, attrStr, defineAttrs, attrSpec } from '../../ln-core';
2
+
3
+ (function () {
4
+ const DOM_SELECTOR = 'data-ln-list';
5
+ const DOM_ATTRIBUTE = 'lnList';
6
+ const EMPTY_TEMPLATE = 'data-ln-list-empty';
7
+ const VIRTUAL_THRESHOLD = 200;
8
+ const BUFFER_ROWS = 15;
9
+ const WINDOW_DEFAULT = 1000;
10
+ const WINDOW_PAGE = 200;
11
+ const FETCH_DEBOUNCE = 120;
12
+
13
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
14
+
15
+ // ─── Attribute Contract (SSOT) ──────────────────────────
16
+ function _applyWindow(inst, val) {
17
+ if (!inst || !inst.isDataDriven) return;
18
+ const present = inst.dom.hasAttribute('data-ln-list-window');
19
+ if (present && !inst._windowed) {
20
+ inst._enterWindowedMode();
21
+ inst._kickWindowInitial();
22
+ } else if (!present && inst._windowed) {
23
+ inst._exitWindowedMode();
24
+ } else if (present && inst._windowed) {
25
+ const v = parseInt(val, 10);
26
+ if (v > 0) inst._cache.configure({ windowSize: v });
27
+ }
28
+ }
29
+
30
+ function _applyWindowPage(inst, val) {
31
+ if (!inst || !inst.isDataDriven || !inst._windowed || !inst._cache) return;
32
+ const v = parseInt(val, 10);
33
+ if (v > 0) inst._cache.configure({ pageSize: v });
34
+ }
35
+
36
+ function _applyWindowThreshold(inst, val) {
37
+ if (!inst || !inst.isDataDriven || !inst._windowed || !inst._cache) return;
38
+ const v = parseInt(val, 10);
39
+ if (v >= 0) inst._cache.configure({ threshold: v });
40
+ }
41
+
42
+ function _applyCount(inst, val) {
43
+ if (!inst || !inst.isDataDriven || !inst._windowed || !inst._cache) return;
44
+ const v = parseInt(val, 10);
45
+ if (v >= 0) inst._cache.setGrandTotal(v);
46
+ }
47
+
48
+ const ATTRIBUTES = {
49
+ 'data-ln-list': { prop: 'name', type: 'string', read: attrStr, fallback: '', description: 'List instance name or identifier' },
50
+ 'data-ln-list-source': { prop: 'source', type: 'string', read: attrStr, fallback: '', description: 'Source store or coordinator addressing' },
51
+ 'data-ln-list-selectable': { prop: '_selectable', type: 'boolean', read: attrBool, description: 'Enables item selection controls' },
52
+ 'data-ln-list-window': { type: 'integer', fallback: WINDOW_DEFAULT, min: 10, effect: _applyWindow, description: 'Virtual scrolling window size in items' },
53
+ 'data-ln-list-window-page': { type: 'integer', fallback: WINDOW_PAGE, min: 5, effect: _applyWindowPage, description: 'Virtual scrolling slice page size' },
54
+ 'data-ln-list-window-threshold': { type: 'integer', fallback: 50, min: 0, effect: _applyWindowThreshold, description: 'Scroll threshold margin in pixels to trigger page fetch' },
55
+ 'data-ln-list-count': { type: 'integer', min: 0, effect: _applyCount, description: 'Total item count override for virtual scrollbar calculation' },
56
+ 'data-ln-list-empty': { type: 'marker', description: 'Container for list empty state' },
57
+ 'data-ln-list-field': { type: 'string', description: 'Field name mapping for list item binding' }
58
+ };
59
+
60
+ const ATTR_SPEC = attrSpec(ATTRIBUTES);
61
+
62
+ function _formatNum(n, dom) {
63
+ if (n == null || isNaN(n)) return '';
64
+ try {
65
+ return new Intl.NumberFormat(getLocale(dom)).format(n);
66
+ } catch (e) {
67
+ return String(n);
68
+ }
69
+ }
70
+
71
+ function _findScrollContainer(el) {
72
+ let p = el;
73
+ while (p && p !== document.body && p !== document.documentElement) {
74
+ const cs = getComputedStyle(p);
75
+ const oy = cs.overflowY;
76
+ if (oy === 'auto' || oy === 'scroll') return p;
77
+ p = p.parentElement;
78
+ }
79
+ return null;
80
+ }
81
+
82
+ function _saveScroll(self) {
83
+ const sc = self._scrollContainer || _findScrollContainer(self.dom);
84
+ return {
85
+ container: sc,
86
+ top: sc ? sc.scrollTop : window.scrollY
87
+ };
88
+ }
89
+
90
+ function _restoreScroll(state) {
91
+ if (state.container) {
92
+ state.container.scrollTop = state.top;
93
+ } else {
94
+ window.scrollTo(window.scrollX, state.top);
95
+ }
96
+ }
97
+
98
+
99
+ function _getOuterHeight(el) {
100
+ if (!el) return 0;
101
+ const cs = getComputedStyle(el);
102
+ const mt = parseFloat(cs.marginTop) || 0;
103
+ const mb = parseFloat(cs.marginBottom) || 0;
104
+ return el.offsetHeight + mt + mb;
105
+ }
106
+
107
+ // ─── Component ─────────────────────────────────────────────
108
+
109
+ function _component(dom) {
110
+ this.dom = dom;
111
+ defineAttrs(this, dom, ATTR_SPEC);
112
+ this.tbody = dom.querySelector('[data-ln-list-body]') || dom;
113
+ this.isDataDriven = dom.hasAttribute('data-ln-list-source');
114
+
115
+ // Footer elements — both modes
116
+ this._totalSpan = dom.querySelector('[data-ln-list-total]');
117
+ this._filteredSpan = dom.querySelector('[data-ln-list-filtered]');
118
+ if (this._filteredSpan) {
119
+ this._filteredWrap = this._filteredSpan.parentElement !== dom
120
+ ? this._filteredSpan.parentElement
121
+ : null;
122
+ }
123
+ this._selectedSpan = dom.querySelector('[data-ln-list-selected]');
124
+ if (this._selectedSpan) {
125
+ this._selectedWrap = this._selectedSpan.parentElement !== dom
126
+ ? this._selectedSpan.parentElement
127
+ : null;
128
+ }
129
+
130
+ this._data = [];
131
+ this._filteredData = [];
132
+ this.selectedIds = new Set();
133
+
134
+ // SSR filtering / sorting state
135
+ this._searchTerm = '';
136
+ this._filters = {};
137
+ this._sortField = null;
138
+ this._sortDir = null;
139
+
140
+ // Virtual scroll state
141
+ this._virtual = false;
142
+ this._itemHeight = 0;
143
+ this._vStart = -1;
144
+ this._vEnd = -1;
145
+ this._rafId = null;
146
+ this._scrollHandler = null;
147
+ this._resizeHandler = null;
148
+ this._scrollContainer = null;
149
+ this.isUl = this.tbody.tagName === 'UL' || this.tbody.tagName === 'OL';
150
+
151
+ const self = this;
152
+
153
+ // --- Layer 1 Command Handlers ---
154
+ this._onSetSearch = function (e) {
155
+ const term = (e.detail && e.detail.query != null ? e.detail.query : (e.detail && e.detail.term != null ? e.detail.term : '')).trim();
156
+ if (self.isDataDriven) {
157
+ self.currentSearch = term;
158
+ dispatch(dom, 'ln-list:search', {
159
+ list: self.name,
160
+ query: self.currentSearch
161
+ });
162
+ self._requestData();
163
+ } else {
164
+ self._searchTerm = term.toLowerCase();
165
+ self._applyFilterAndSort();
166
+ self._vStart = -1;
167
+ self._vEnd = -1;
168
+ self._render();
169
+ self._updateFooter();
170
+ dispatch(dom, 'ln-list:filter', {
171
+ term: self._searchTerm,
172
+ matched: self._filteredData.length,
173
+ total: self._data.length
174
+ });
175
+ }
176
+ };
177
+ dom.addEventListener('ln-list:set-search', this._onSetSearch);
178
+
179
+ this._onSearchChange = function (e) {
180
+ e.preventDefault();
181
+ self._onSetSearch(e);
182
+ };
183
+ dom.addEventListener('ln-search:change', this._onSearchChange);
184
+
185
+ this._onRequestClearFilters = function () {
186
+ if (self.isDataDriven) {
187
+ self.currentFilters = {};
188
+ self.currentSearch = '';
189
+ dispatch(dom, 'ln-list:clear-filters', { list: self.name });
190
+ self._requestData();
191
+ } else {
192
+ self._searchTerm = '';
193
+ self._filters = {};
194
+ self._sortField = null;
195
+ self._sortDir = null;
196
+ self._applyFilterAndSort();
197
+ self._vStart = -1;
198
+ self._vEnd = -1;
199
+ self._render();
200
+ self._updateFooter();
201
+ dispatch(dom, 'ln-list:filter', {
202
+ term: '',
203
+ matched: self._filteredData.length,
204
+ total: self._data.length
205
+ });
206
+ }
207
+ };
208
+ dom.addEventListener('ln-list:request-clear-filters', this._onRequestClearFilters);
209
+
210
+ // --- Selection (both modes) ---
211
+ this._selectableActive = false;
212
+ if (this._selectable) {
213
+ this._enableSelection();
214
+ }
215
+
216
+ if (this.isDataDriven) {
217
+ this.isLoaded = false;
218
+ this.totalCount = 0;
219
+ this.visibleCount = 0;
220
+ this.currentSort = null;
221
+ this.currentFilters = {};
222
+ this.currentSearch = '';
223
+
224
+ this._lastTotal = 0;
225
+ this._lastFiltered = 0;
226
+ this._hasInitialSeed = false;
227
+
228
+ this._windowed = false;
229
+ this._cache = null;
230
+ if (dom.hasAttribute('data-ln-list-window')) this._enterWindowedMode();
231
+
232
+ // --- Event listeners ---
233
+ this._onSetData = function (e) {
234
+ const detail = e.detail || {};
235
+ const newData = detail.data || [];
236
+ const newTotal = detail.total != null ? detail.total : newData.length;
237
+
238
+ // Protect initial SSR seed items: do not overwrite pre-rendered HTML items with empty un-synced store payloads before initial API response arrives
239
+ if (self._hasInitialSeed && !self.isLoaded && newData.length === 0 && newTotal === 0) {
240
+ return;
241
+ }
242
+
243
+ if (self._windowed) {
244
+ // Only an accepted, authoritative page ends the refresh. A declined
245
+ // one means the source has not resolved the new query yet; a
246
+ // provisional one is the store's own answer, rendered while the
247
+ // server query is still out.
248
+ if (self._cache.ingest(detail) && !detail.provisional) dom.classList.remove('ln-list--loading');
249
+ return;
250
+ }
251
+ self._data = newData;
252
+ self._lastTotal = newTotal;
253
+ self._lastFiltered = detail.filtered != null ? detail.filtered : self._data.length;
254
+
255
+ self.totalCount = self._lastTotal;
256
+ self.visibleCount = self._lastFiltered;
257
+ self.isLoaded = true;
258
+ self._hasInitialSeed = false;
259
+
260
+ dom.classList.remove('ln-list--loading');
261
+
262
+ self._vStart = -1;
263
+ self._vEnd = -1;
264
+
265
+ self._applyFilterAndSort();
266
+ self._render();
267
+ self._updateFooter();
268
+
269
+ dispatch(dom, 'ln-list:rendered', {
270
+ list: self.name,
271
+ total: self.totalCount,
272
+ visible: self.visibleCount
273
+ });
274
+ };
275
+ dom.addEventListener('ln-list:set-data', this._onSetData);
276
+
277
+ this._onSetLoading = function (e) {
278
+ const loading = e.detail && e.detail.loading;
279
+ dom.classList.toggle('ln-list--loading', !!loading);
280
+ if (loading) {
281
+ self.isLoaded = false;
282
+ }
283
+ };
284
+ dom.addEventListener('ln-list:set-loading', this._onSetLoading);
285
+
286
+ this._onPageFailed = function (e) {
287
+ if (!self._windowed || !self._cache) return;
288
+ self._cache.release(e.detail && e.detail.offset);
289
+ };
290
+ dom.addEventListener('ln-list:page-failed', this._onPageFailed);
291
+
292
+ this._onRequestRevalidate = function () {
293
+ if (!self._windowed || !self._cache) return;
294
+ self._cache.revalidate();
295
+ };
296
+ dom.addEventListener('ln-list:request-revalidate', this._onRequestRevalidate);
297
+
298
+ // The source owns the query, so a query change reaches the view as a
299
+ // restart order, not as query state — the window drops to page 0 and
300
+ // the composed query is resolved at serve time.
301
+ this._onRequestInvalidate = function () {
302
+ if (!self._windowed || !self._cache) return;
303
+ self._requestData();
304
+ };
305
+ dom.addEventListener('ln-list:request-invalidate', this._onRequestInvalidate);
306
+
307
+ // --- Sort ---
308
+ this._onSort = function (e) {
309
+ if (e.detail.field == null) return;
310
+ e.preventDefault();
311
+ self.currentSort = e.detail.direction === 'none' ? null : { field: e.detail.field, direction: e.detail.direction };
312
+ self._requestData();
313
+ };
314
+ dom.addEventListener('ln-sort:change', this._onSort);
315
+
316
+ // --- Item Click & Actions ---
317
+ this._onItemClick = function (e) {
318
+ if (e.target.closest('[data-ln-item-select]')) return;
319
+ if (e.target.closest('[data-ln-item-action]')) return;
320
+ if (e.target.closest('a') || e.target.closest('button')) return;
321
+ if (e.ctrlKey || e.metaKey || e.button === 1) return;
322
+
323
+ const itemEl = e.target.closest('[data-ln-item]');
324
+ if (!itemEl) return;
325
+
326
+ const id = itemEl.getAttribute('data-ln-item-id');
327
+ const record = itemEl._lnRecord || {};
328
+
329
+ dispatch(dom, 'ln-list:item-click', {
330
+ list: self.name,
331
+ id: id,
332
+ record: record
333
+ });
334
+ };
335
+ if (this.tbody) this.tbody.addEventListener('click', this._onItemClick);
336
+
337
+ this._onItemAction = function (e) {
338
+ const btn = e.target.closest('[data-ln-item-action]');
339
+ if (!btn) return;
340
+
341
+ const itemEl = btn.closest('[data-ln-item]');
342
+ if (!itemEl) return;
343
+
344
+ const action = btn.getAttribute('data-ln-item-action');
345
+ const id = itemEl.getAttribute('data-ln-item-id');
346
+ const record = itemEl._lnRecord || {};
347
+
348
+ dispatch(dom, 'ln-list:item-action', {
349
+ list: self.name,
350
+ id: id,
351
+ action: action,
352
+ record: record
353
+ });
354
+ };
355
+ if (this.tbody) this.tbody.addEventListener('click', this._onItemAction);
356
+
357
+ // Local hydration of initial items
358
+ if (this.tbody && this.tbody.children.length > 0) {
359
+ this._parseChildren();
360
+ }
361
+
362
+ // Initial request-data
363
+ if (this._windowed) {
364
+ this._kickWindowInitial();
365
+ } else {
366
+ dispatch(dom, 'ln-list:request-data', {
367
+ list: this.name,
368
+ sort: this.currentSort,
369
+ filters: this.currentFilters,
370
+ search: this.currentSearch
371
+ });
372
+ }
373
+
374
+ } else {
375
+ // SSR Mode
376
+ this._emptyObserver = null;
377
+
378
+ if (this.tbody && this.tbody.children.length > 0) {
379
+ this._parseChildren();
380
+ } else if (this.tbody) {
381
+ this._emptyObserver = new MutationObserver(function () {
382
+ if (self.tbody.children.length > 0) {
383
+ self._emptyObserver.disconnect();
384
+ self._emptyObserver = null;
385
+ self._parseChildren();
386
+ }
387
+ });
388
+ this._emptyObserver.observe(this.tbody, { childList: true });
389
+ }
390
+
391
+ this._onFilterChange = function (e) {
392
+ e.preventDefault();
393
+ if (!e.detail) return;
394
+ const key = e.detail.key;
395
+ const values = e.detail.values || [];
396
+ if (!key) return;
397
+
398
+ if (values.length === 0) {
399
+ delete self._filters[key];
400
+ } else {
401
+ const lower = [];
402
+ for (let i = 0; i < values.length; i++) {
403
+ lower.push(values[i].toLowerCase());
404
+ }
405
+ self._filters[key] = lower;
406
+ }
407
+ self._applyFilterAndSort();
408
+ self._vStart = -1;
409
+ self._vEnd = -1;
410
+ self._render();
411
+ self._updateFooter();
412
+ dispatch(dom, 'ln-list:filter', {
413
+ term: self._searchTerm,
414
+ matched: self._filteredData.length,
415
+ total: self._data.length
416
+ });
417
+ };
418
+ dom.addEventListener('ln-filter:change', this._onFilterChange);
419
+
420
+ this._onSort = function (e) {
421
+ if (e.detail && e.detail.field == null) return;
422
+ e.preventDefault();
423
+ const direction = e.detail && e.detail.direction === 'none' ? null : (e.detail && e.detail.direction);
424
+ self._sortField = direction === null ? null : (e.detail && e.detail.field);
425
+ self._sortDir = direction;
426
+ self._applyFilterAndSort();
427
+ self._vStart = -1;
428
+ self._vEnd = -1;
429
+ self._render();
430
+ self._updateFooter();
431
+ dispatch(dom, 'ln-list:sorted', {
432
+ field: self._sortField,
433
+ direction: e.detail && e.detail.direction,
434
+ matched: self._filteredData.length,
435
+ total: self._data.length
436
+ });
437
+ };
438
+ dom.addEventListener('ln-sort:change', this._onSort);
439
+ }
440
+
441
+ return this;
442
+ }
443
+
444
+ // ─── Parse Children into in-memory array ───────────────────
445
+
446
+ _component.prototype._parseChildren = function () {
447
+ const children = Array.from(this.tbody.children).filter(el => !el.classList.contains('ln-list__spacer'));
448
+ this._data = [];
449
+
450
+ if (children.length > 0) this._itemHeight = _getOuterHeight(children[0]) || 50;
451
+
452
+ for (let i = 0; i < children.length; i++) {
453
+ const el = children[i];
454
+ const id = el.getAttribute('data-ln-item-id') || el.getAttribute('id');
455
+ const text = el.textContent.trim().toLowerCase();
456
+
457
+ let record = null;
458
+ if (this.isDataDriven) {
459
+ record = {};
460
+ if (id != null) record.id = id;
461
+
462
+ const fields = el.querySelectorAll('[data-ln-list-field]');
463
+ for (let j = 0; j < fields.length; j++) {
464
+ const f = fields[j];
465
+ const prop = f.getAttribute('data-ln-list-field');
466
+ if (prop) {
467
+ record[prop] = readValue(f);
468
+ }
469
+ }
470
+ }
471
+
472
+ // SSR and fallback field extraction via readValue
473
+ const fields = {};
474
+ const fieldNodes = el.querySelectorAll('[data-ln-list-field], [data-ln-field]');
475
+ for (let j = 0; j < fieldNodes.length; j++) {
476
+ const f = fieldNodes[j];
477
+ const prop = f.getAttribute('data-ln-list-field') || f.getAttribute('data-ln-field');
478
+ if (prop) fields[prop] = readValue(f);
479
+ }
480
+ for (let j = 0; j < el.attributes.length; j++) {
481
+ const attr = el.attributes[j];
482
+ if (attr.name.startsWith('data-') && !attr.name.startsWith('data-ln-')) {
483
+ const key = attr.name.slice(5);
484
+ if (key) fields[key] = attr.value;
485
+ }
486
+ }
487
+
488
+ this._data.push({
489
+ html: el.outerHTML,
490
+ id: id,
491
+ searchText: text,
492
+ fields: fields,
493
+ ...(record || {})
494
+ });
495
+ }
496
+
497
+ this._filteredData = this._data.slice();
498
+ if (this._data.length > 0) {
499
+ this._hasInitialSeed = true;
500
+ }
501
+
502
+ if (this.isDataDriven) {
503
+ this._lastTotal = this._data.length;
504
+ this._lastFiltered = this._data.length;
505
+ this.totalCount = this._data.length;
506
+ this.visibleCount = this._data.length;
507
+ this._updateFooter();
508
+ }
509
+
510
+ this._render();
511
+
512
+ dispatch(this.dom, 'ln-list:ready', {
513
+ total: this._data.length
514
+ });
515
+ };
516
+
517
+ // ─── Filter + Sort ─────────────────────────────────────────
518
+
519
+ _component.prototype._applyFilterAndSort = function () {
520
+ if (this.isDataDriven) {
521
+ this._filteredData = this._data ? this._data.slice() : [];
522
+ this.visibleCount = (this.isDataDriven && this._lastFiltered != null) ? this._lastFiltered : this._filteredData.length;
523
+ } else {
524
+ const term = this._searchTerm;
525
+ const tokens = term ? term.split(/\s+/).filter(Boolean) : [];
526
+ const filters = this._filters || {};
527
+ const hasFilters = Object.keys(filters).length > 0;
528
+
529
+ if (tokens.length === 0 && !hasFilters) {
530
+ this._filteredData = this._data.slice();
531
+ } else {
532
+ this._filteredData = this._data.filter(function (row) {
533
+ if (tokens.length > 0) {
534
+ const match = tokens.every(function (token) {
535
+ return row.searchText && row.searchText.indexOf(token) !== -1;
536
+ });
537
+ if (!match) return false;
538
+ }
539
+ if (hasFilters) {
540
+ for (const key in filters) {
541
+ const activeVals = filters[key];
542
+ if (activeVals && activeVals.length > 0) {
543
+ const val = (row.fields && row.fields[key] !== undefined)
544
+ ? row.fields[key]
545
+ : (row[key] !== undefined ? row[key] : null);
546
+ const sVal = val != null ? String(val).toLowerCase() : '';
547
+ if (activeVals.indexOf(sVal) === -1) return false;
548
+ }
549
+ }
550
+ }
551
+ return true;
552
+ });
553
+ }
554
+
555
+ // SSR Sort
556
+ if (this._sortField && this._sortDir) {
557
+ const field = this._sortField;
558
+ const multiplier = this._sortDir === 'desc' ? -1 : 1;
559
+ const collator = typeof Intl !== 'undefined'
560
+ ? new Intl.Collator(getLocale(this.dom), { sensitivity: 'base' })
561
+ : null;
562
+
563
+ const values = this._filteredData.map(function (row) {
564
+ return (row.fields && row.fields[field] !== undefined) ? row.fields[field] : row[field];
565
+ });
566
+ const type = detectValueType(values);
567
+
568
+ this._filteredData.sort(function (a, b) {
569
+ const valA = (a.fields && a.fields[field] !== undefined) ? a.fields[field] : a[field];
570
+ const valB = (b.fields && b.fields[field] !== undefined) ? b.fields[field] : b[field];
571
+ return compareValues(valA, valB, type, collator) * multiplier;
572
+ });
573
+ }
574
+ }
575
+ };
576
+
577
+ // ─── Render ────────────────────────────────────────────────
578
+
579
+ _component.prototype._render = function () {
580
+ if (!this.tbody) return;
581
+
582
+ if (this.isDataDriven) {
583
+ if (this._windowed) { this._renderWindowed(); return; }
584
+ const total = this._lastTotal;
585
+ const filtered = this.visibleCount;
586
+
587
+ if (total === 0 || this._filteredData.length === 0 || filtered === 0) {
588
+ this._disableVirtualScroll();
589
+ this._showEmptyState();
590
+ return;
591
+ }
592
+
593
+ if (this._filteredData.length > VIRTUAL_THRESHOLD) {
594
+ this._enableVirtualScroll();
595
+ this._renderVirtual();
596
+ } else {
597
+ this._disableVirtualScroll();
598
+ this._renderAll();
599
+ }
600
+ } else {
601
+ const count = this._filteredData.length;
602
+ const isFiltered = count === 0 && (this._searchTerm || Object.keys(this._filters || {}).length > 0);
603
+
604
+ if (isFiltered) {
605
+ this._disableVirtualScroll();
606
+ this._showEmptyState();
607
+ } else if (count > VIRTUAL_THRESHOLD) {
608
+ this._enableVirtualScroll();
609
+ this._renderVirtual();
610
+ } else {
611
+ this._disableVirtualScroll();
612
+ this._renderAll();
613
+ }
614
+ }
615
+ };
616
+
617
+ _component.prototype._renderAll = function () {
618
+ if (this.isDataDriven) {
619
+ const data = this._filteredData;
620
+ const frag = document.createDocumentFragment();
621
+
622
+ for (let i = 0; i < data.length; i++) {
623
+ const el = this._buildItem(data[i]);
624
+ if (el) frag.appendChild(el);
625
+ }
626
+
627
+ const scrollState = _saveScroll(this);
628
+ this.tbody.replaceChildren(frag);
629
+ _restoreScroll(scrollState);
630
+
631
+ if (this._selectable) this._updateSelectAll();
632
+ } else {
633
+ const html = [];
634
+ const data = this._filteredData;
635
+ for (let i = 0; i < data.length; i++) html.push(data[i].html);
636
+
637
+ const scrollState = _saveScroll(this);
638
+ this.tbody.innerHTML = html.join('');
639
+ _restoreScroll(scrollState);
640
+
641
+ if (this._selectable) this._restoreSelection();
642
+ }
643
+ };
644
+
645
+ // ─── Virtual scroll ────────────────────────────────────────
646
+
647
+ _component.prototype._readGridLayout = function () {
648
+ const cs = getComputedStyle(this.tbody);
649
+ const tracks = cs.gridTemplateColumns;
650
+ let columns = 1;
651
+ if (tracks && tracks !== 'none') {
652
+ const parts = tracks.trim().split(/\s+/).filter(Boolean);
653
+ if (parts.length > 0) columns = parts.length;
654
+ }
655
+ const rowGap = parseFloat(cs.rowGap);
656
+ return { columns: columns, rowGap: isNaN(rowGap) ? 0 : rowGap };
657
+ };
658
+
659
+ _component.prototype._measureItemHeight = function () {
660
+ if (this._windowed) {
661
+ const sample = this._cache.peek();
662
+ const el = sample ? this._buildItem(sample) : this._buildPlaceholderItem();
663
+ if (el) {
664
+ this.tbody.textContent = '';
665
+ this.tbody.appendChild(el);
666
+ this._itemHeight = _getOuterHeight(el) || 50;
667
+ this.tbody.textContent = '';
668
+ }
669
+ } else if (this.isDataDriven) {
670
+ if (this._data.length > 0) {
671
+ const el = this._buildItem(this._data[0]);
672
+ if (el) {
673
+ this.tbody.textContent = '';
674
+ this.tbody.appendChild(el);
675
+ this._itemHeight = _getOuterHeight(el) || 50;
676
+ this.tbody.textContent = '';
677
+ }
678
+ }
679
+ } else {
680
+ const children = this.tbody.children;
681
+ if (children.length > 0) {
682
+ this._itemHeight = _getOuterHeight(children[0]) || 50;
683
+ }
684
+ }
685
+ };
686
+
687
+ _component.prototype._enableVirtualScroll = function () {
688
+ if (this._virtual) return;
689
+ this._virtual = true;
690
+ this._vStart = -1;
691
+ this._vEnd = -1;
692
+ const self = this;
693
+
694
+ if (!this._itemHeight) {
695
+ this._measureItemHeight();
696
+ }
697
+
698
+ this._scrollContainer = _findScrollContainer(this.dom);
699
+ const container = this._scrollContainer || window;
700
+
701
+ this._scrollHandler = function () {
702
+ if (!self._rafId) {
703
+ self._rafId = requestAnimationFrame(function () {
704
+ self._rafId = null;
705
+ self._windowed ? self._renderWindowed() : self._renderVirtual();
706
+ });
707
+ }
708
+ };
709
+
710
+ this._resizeHandler = function () {
711
+ self._itemHeight = 0;
712
+ self._measureItemHeight();
713
+ self._vStart = -1;
714
+ self._vEnd = -1;
715
+ self._windowed ? self._renderWindowed() : self._renderVirtual();
716
+ };
717
+
718
+ container.addEventListener('scroll', this._scrollHandler, { passive: true });
719
+ window.addEventListener('resize', this._resizeHandler, { passive: true });
720
+ };
721
+
722
+ _component.prototype._disableVirtualScroll = function () {
723
+ if (!this._virtual) return;
724
+ this._virtual = false;
725
+
726
+ if (this._scrollHandler) {
727
+ const container = this._scrollContainer || window;
728
+ container.removeEventListener('scroll', this._scrollHandler);
729
+ this._scrollHandler = null;
730
+ }
731
+ if (this._resizeHandler) {
732
+ window.removeEventListener('resize', this._resizeHandler);
733
+ this._resizeHandler = null;
734
+ }
735
+ this._scrollContainer = null;
736
+
737
+ if (this._rafId) {
738
+ cancelAnimationFrame(this._rafId);
739
+ this._rafId = null;
740
+ }
741
+ this._vStart = -1;
742
+ this._vEnd = -1;
743
+ };
744
+
745
+ _component.prototype._renderVirtual = function () {
746
+ const data = this._filteredData;
747
+ const count = data.length;
748
+ const itemHeight = this._itemHeight;
749
+ if (!itemHeight || !count) return;
750
+
751
+ const container = this._scrollContainer;
752
+ let scrollTop, viewportHeight;
753
+
754
+ if (container) {
755
+ const rect = this.tbody.getBoundingClientRect();
756
+ const containerRect = container.getBoundingClientRect();
757
+ const relativeTop = (container === this.tbody) ? 0 : (rect.top - containerRect.top + container.scrollTop);
758
+ scrollTop = container.scrollTop - relativeTop;
759
+ viewportHeight = container.clientHeight;
760
+ } else {
761
+ const rect = this.tbody.getBoundingClientRect();
762
+ const absoluteTop = rect.top + window.scrollY;
763
+ scrollTop = window.scrollY - absoluteTop;
764
+ viewportHeight = window.innerHeight;
765
+ }
766
+
767
+ const layout = this._readGridLayout();
768
+ const columns = layout.columns;
769
+ const rowGap = layout.rowGap;
770
+ const rowHeight = itemHeight + rowGap;
771
+ const totalRows = Math.ceil(count / columns);
772
+
773
+ let firstRow = Math.max(0, Math.floor(scrollTop / rowHeight) - BUFFER_ROWS);
774
+ firstRow = Math.min(firstRow, totalRows);
775
+ const visibleRows = Math.ceil(viewportHeight / rowHeight) + BUFFER_ROWS * 2;
776
+ const lastRow = Math.min(firstRow + visibleRows, totalRows);
777
+
778
+ const start = Math.min(firstRow * columns, count);
779
+ const end = Math.min(lastRow * columns, count);
780
+
781
+ if (start === this._vStart && end === this._vEnd) return;
782
+
783
+ this._vStart = start;
784
+ this._vEnd = end;
785
+
786
+ const topSpacerHeight = firstRow * rowHeight;
787
+ const bottomSpacerHeight = (totalRows - lastRow) * rowHeight;
788
+
789
+ if (this.isDataDriven) {
790
+ const frag = document.createDocumentFragment();
791
+
792
+ if (topSpacerHeight > 0) {
793
+ const topSpacer = document.createElement(this.isUl ? 'li' : 'div');
794
+ topSpacer.className = 'ln-list__spacer';
795
+ topSpacer.setAttribute('aria-hidden', 'true');
796
+ topSpacer.style.height = topSpacerHeight + 'px';
797
+ frag.appendChild(topSpacer);
798
+ }
799
+
800
+ for (let i = start; i < end; i++) {
801
+ const el = this._buildItem(data[i]);
802
+ if (el) frag.appendChild(el);
803
+ }
804
+
805
+ if (bottomSpacerHeight > 0) {
806
+ const bottomSpacer = document.createElement(this.isUl ? 'li' : 'div');
807
+ bottomSpacer.className = 'ln-list__spacer';
808
+ bottomSpacer.setAttribute('aria-hidden', 'true');
809
+ bottomSpacer.style.height = bottomSpacerHeight + 'px';
810
+ frag.appendChild(bottomSpacer);
811
+ }
812
+
813
+ const scrollState = _saveScroll(this);
814
+ this.tbody.replaceChildren(frag);
815
+ _restoreScroll(scrollState);
816
+
817
+ if (this._selectable) this._updateSelectAll();
818
+ } else {
819
+ let html = '';
820
+ if (topSpacerHeight > 0) {
821
+ html += `<${this.isUl ? 'li' : 'div'} class="ln-list__spacer" aria-hidden="true" style="height:${topSpacerHeight}px"></${this.isUl ? 'li' : 'div'}>`;
822
+ }
823
+ for (let i = start; i < end; i++) {
824
+ html += data[i].html;
825
+ }
826
+ if (bottomSpacerHeight > 0) {
827
+ html += `<${this.isUl ? 'li' : 'div'} class="ln-list__spacer" aria-hidden="true" style="height:${bottomSpacerHeight}px"></${this.isUl ? 'li' : 'div'}>`;
828
+ }
829
+
830
+ const scrollState = _saveScroll(this);
831
+ this.tbody.innerHTML = html;
832
+ _restoreScroll(scrollState);
833
+
834
+ if (this._selectable) this._restoreSelection();
835
+ }
836
+ };
837
+
838
+ _component.prototype._buildPlaceholderItem = function () {
839
+ const el = document.createElement(this.isUl ? 'li' : 'div');
840
+ el.className = 'ln-list__placeholder';
841
+ el.setAttribute('aria-hidden', 'true');
842
+ el.style.height = this._itemHeight + 'px';
843
+ return el;
844
+ };
845
+
846
+ _component.prototype._renderWindowed = function () {
847
+ if (this.isLoaded && this._cache.logicalTotal === 0) {
848
+ this._disableVirtualScroll();
849
+ this._showEmptyState();
850
+ return;
851
+ }
852
+ if (!this._virtual) this._enableVirtualScroll();
853
+
854
+ const itemHeight = this._itemHeight;
855
+ if (!itemHeight) return;
856
+
857
+ const container = this._scrollContainer;
858
+ let scrollTop, viewportHeight;
859
+ if (container) {
860
+ const rect = this.tbody.getBoundingClientRect();
861
+ const containerRect = container.getBoundingClientRect();
862
+ const relativeTop = (container === this.tbody) ? 0 : (rect.top - containerRect.top + container.scrollTop);
863
+ scrollTop = container.scrollTop - relativeTop;
864
+ viewportHeight = container.clientHeight;
865
+ } else {
866
+ const rect = this.tbody.getBoundingClientRect();
867
+ const absoluteTop = rect.top + window.scrollY;
868
+ scrollTop = window.scrollY - absoluteTop;
869
+ viewportHeight = window.innerHeight;
870
+ }
871
+
872
+ const layout = this._readGridLayout();
873
+ const columns = layout.columns;
874
+ const rowGap = layout.rowGap;
875
+ const rowHeight = itemHeight + rowGap;
876
+ const total = this._cache.logicalTotal;
877
+ const totalRows = Math.ceil(total / columns);
878
+
879
+ let firstRow = Math.max(0, Math.floor(scrollTop / rowHeight) - BUFFER_ROWS);
880
+ firstRow = Math.min(firstRow, totalRows);
881
+ const visibleRows = Math.ceil(viewportHeight / rowHeight) + BUFFER_ROWS * 2;
882
+ const lastRow = Math.min(firstRow + visibleRows, totalRows);
883
+
884
+ const start = Math.min(firstRow * columns, total);
885
+ const end = Math.min(lastRow * columns, total);
886
+
887
+ // No start===_vStart early-return: windowed must re-render when a page
888
+ // splices in even if the range is unchanged.
889
+
890
+ const topSpacerHeight = firstRow * rowHeight;
891
+ const bottomSpacerHeight = (totalRows - lastRow) * rowHeight;
892
+
893
+ const frag = document.createDocumentFragment();
894
+ if (topSpacerHeight > 0) {
895
+ const topSpacer = document.createElement(this.isUl ? 'li' : 'div');
896
+ topSpacer.className = 'ln-list__spacer';
897
+ topSpacer.setAttribute('aria-hidden', 'true');
898
+ topSpacer.style.height = topSpacerHeight + 'px';
899
+ frag.appendChild(topSpacer);
900
+ }
901
+ for (let i = start; i < end; i++) {
902
+ if (this._cache.has(i)) {
903
+ const el = this._buildItem(this._cache.get(i));
904
+ if (el) frag.appendChild(el);
905
+ } else {
906
+ frag.appendChild(this._buildPlaceholderItem());
907
+ }
908
+ }
909
+ if (bottomSpacerHeight > 0) {
910
+ const bottomSpacer = document.createElement(this.isUl ? 'li' : 'div');
911
+ bottomSpacer.className = 'ln-list__spacer';
912
+ bottomSpacer.setAttribute('aria-hidden', 'true');
913
+ bottomSpacer.style.height = bottomSpacerHeight + 'px';
914
+ frag.appendChild(bottomSpacer);
915
+ }
916
+
917
+ const scrollState = _saveScroll(this);
918
+ this.tbody.replaceChildren(frag);
919
+ _restoreScroll(scrollState);
920
+
921
+ // Select-all disabled in windowed mode — no _updateSelectAll().
922
+ this._vStart = start;
923
+ this._vEnd = end;
924
+
925
+ this._cache.ensure(start, end);
926
+ };
927
+
928
+ _component.prototype._showEmptyState = function () {
929
+ let el = null;
930
+ if (this.isDataDriven) {
931
+ const total = this._lastTotal != null ? this._lastTotal : this._data.length;
932
+ const visible = this.visibleCount;
933
+ // The source owns the query (docs/architecture/shared-query.md) — narrowed to
934
+ // zero while records exist is the only signal the view gets.
935
+ const isFiltered = visible === 0 && total > 0;
936
+ const templateName = isFiltered ? this.name + '-empty-filtered' : this.name + '-empty';
937
+
938
+ el = cloneTemplateScoped(this.dom, templateName, 'ln-list');
939
+ if (!el) {
940
+ const fallback = this.dom.querySelector('template[data-ln-empty], template[data-ln-list-empty]');
941
+ if (fallback) {
942
+ const condition = isFiltered ? 'search' : 'initial';
943
+ const matched = fallback.content.querySelector(`[data-ln-empty-when="${condition}"]`) || fallback.content.firstElementChild;
944
+ if (matched) el = document.importNode(matched, true);
945
+ }
946
+ }
947
+ } else {
948
+ const fallback = this.dom.querySelector(`template[${EMPTY_TEMPLATE}]`);
949
+ if (fallback) {
950
+ const matched = fallback.content.firstElementChild;
951
+ if (matched) {
952
+ el = document.importNode(matched, true);
953
+ }
954
+ }
955
+ }
956
+
957
+ if (el) {
958
+ if (el.tagName === 'LI' || el.tagName === 'TR') {
959
+ this.tbody.replaceChildren(el);
960
+ } else {
961
+ const item = document.createElement(this.isUl ? 'li' : 'div');
962
+ item.appendChild(el);
963
+ this.tbody.replaceChildren(item);
964
+ }
965
+ } else {
966
+ this.tbody.replaceChildren();
967
+ }
968
+
969
+ dispatch(this.dom, 'ln-list:empty', {
970
+ term: this.isDataDriven ? (this.currentSearch || '') : this._searchTerm,
971
+ total: this.isDataDriven ? (this._lastTotal != null ? this._lastTotal : this._data.length) : this._data.length
972
+ });
973
+ };
974
+
975
+ _component.prototype._buildItem = function (item) {
976
+ let clone = cloneTemplateScoped(this.dom, this.name + '-row', 'ln-list');
977
+ if (!clone) {
978
+ const genericTpl = this.dom.querySelector('template[data-ln-item]');
979
+ if (genericTpl) {
980
+ clone = document.importNode(genericTpl.content, true);
981
+ }
982
+ }
983
+
984
+ let el = clone ? (clone.querySelector('[data-ln-item]') || clone.firstElementChild) : null;
985
+
986
+ if (!el) {
987
+ if (item && item.html) {
988
+ const temp = document.createElement(this.isUl ? 'ul' : 'div');
989
+ temp.innerHTML = item.html;
990
+ el = temp.firstElementChild;
991
+ } else {
992
+ el = document.createElement(this.isUl ? 'li' : 'div');
993
+ el.setAttribute('data-ln-item', '');
994
+ if (item && typeof item === 'object') {
995
+ for (const prop in item) {
996
+ if (prop !== 'html' && item[prop] != null) {
997
+ const span = document.createElement('span');
998
+ span.setAttribute('data-ln-field', prop);
999
+ span.textContent = String(item[prop]);
1000
+ el.appendChild(span);
1001
+ }
1002
+ }
1003
+ }
1004
+ }
1005
+ } else {
1006
+ fillTemplate(el, item);
1007
+ fill(el, item);
1008
+ }
1009
+
1010
+ el._lnRecord = item;
1011
+ if (item && item.id != null) {
1012
+ el.setAttribute('data-ln-item-id', item.id);
1013
+ if (this._selectable && this.selectedIds.has(String(item.id))) {
1014
+ el.classList.add('ln-item-selected');
1015
+ const cb = el.querySelector('[data-ln-item-select]');
1016
+ if (cb) cb.checked = true;
1017
+ }
1018
+ }
1019
+
1020
+ return el;
1021
+ };
1022
+
1023
+ // ─── Selection Helpers ─────────────────────────────────────
1024
+
1025
+ _component.prototype._restoreSelection = function () {
1026
+ if (!this.tbody) return;
1027
+ const items = this.tbody.querySelectorAll('[data-ln-item]');
1028
+ for (let i = 0; i < items.length; i++) {
1029
+ const id = items[i].getAttribute('data-ln-item-id');
1030
+ const selected = id != null && this.selectedIds.has(String(id));
1031
+ items[i].classList.toggle('ln-item-selected', selected);
1032
+ const cb = items[i].querySelector('[data-ln-item-select]');
1033
+ if (cb) cb.checked = selected;
1034
+ }
1035
+ this._updateSelectAll();
1036
+ };
1037
+
1038
+ _component.prototype._enableSelection = function () {
1039
+ if (this._selectableActive) return;
1040
+ this._selectableActive = true;
1041
+
1042
+ const self = this;
1043
+
1044
+ this._onSelectionChange = function (e) {
1045
+ const cb = e.target.closest('[data-ln-item-select]');
1046
+ if (!cb) return;
1047
+
1048
+ const itemEl = cb.closest('[data-ln-item]');
1049
+ if (!itemEl) return;
1050
+
1051
+ const id = itemEl.getAttribute('data-ln-item-id');
1052
+ if (id == null) return;
1053
+
1054
+ if (cb.checked) {
1055
+ self.selectedIds.add(String(id));
1056
+ itemEl.classList.add('ln-item-selected');
1057
+ } else {
1058
+ self.selectedIds.delete(String(id));
1059
+ itemEl.classList.remove('ln-item-selected');
1060
+ }
1061
+
1062
+ self._updateSelectAll();
1063
+ self._updateFooter();
1064
+
1065
+ dispatch(self.dom, 'ln-list:select', {
1066
+ list: self.name,
1067
+ selectedIds: self.selectedIds,
1068
+ count: self.selectedIds.size
1069
+ });
1070
+ };
1071
+ this.tbody.addEventListener('change', this._onSelectionChange);
1072
+
1073
+ this._selectAllCheckbox = this.dom.querySelector('[data-ln-list-select-all]');
1074
+ if (this._selectAllCheckbox) {
1075
+ this._onSelectAll = function () {
1076
+ const checked = self._selectAllCheckbox.checked;
1077
+ const items = self.tbody.querySelectorAll('[data-ln-item]');
1078
+ for (let i = 0; i < items.length; i++) {
1079
+ const itemEl = items[i];
1080
+ const id = itemEl.getAttribute('data-ln-item-id');
1081
+ const cb = itemEl.querySelector('[data-ln-item-select]');
1082
+ if (id != null) {
1083
+ if (checked) {
1084
+ self.selectedIds.add(String(id));
1085
+ itemEl.classList.add('ln-item-selected');
1086
+ } else {
1087
+ self.selectedIds.delete(String(id));
1088
+ itemEl.classList.remove('ln-item-selected');
1089
+ }
1090
+ if (cb) cb.checked = checked;
1091
+ }
1092
+ }
1093
+
1094
+ dispatch(self.dom, 'ln-list:select-all', { list: self.name, selected: checked });
1095
+ dispatch(self.dom, 'ln-list:select', {
1096
+ list: self.name,
1097
+ selectedIds: self.selectedIds,
1098
+ count: self.selectedIds.size
1099
+ });
1100
+ self._updateFooter();
1101
+ };
1102
+ this._selectAllCheckbox.addEventListener('change', this._onSelectAll);
1103
+ }
1104
+ };
1105
+
1106
+ _component.prototype._updateSelectAll = function () {
1107
+ if (!this._selectAllCheckbox) return;
1108
+ const items = this.tbody.querySelectorAll('[data-ln-item]');
1109
+ let allSelected = items.length > 0;
1110
+ for (let i = 0; i < items.length; i++) {
1111
+ const id = items[i].getAttribute('data-ln-item-id');
1112
+ if (id != null && !this.selectedIds.has(String(id))) {
1113
+ allSelected = false;
1114
+ break;
1115
+ }
1116
+ }
1117
+ this._selectAllCheckbox.checked = allSelected;
1118
+ };
1119
+
1120
+ _component.prototype._requestData = function () {
1121
+ if (this._windowed) {
1122
+ this.dom.classList.add('ln-list--loading');
1123
+ this._cache.invalidate({
1124
+ sort: this.currentSort,
1125
+ filters: this.currentFilters,
1126
+ search: this.currentSearch
1127
+ });
1128
+ return;
1129
+ }
1130
+ requestData(this, 'ln-list:request-data', 'list');
1131
+ };
1132
+
1133
+ // ─── Windowed Mode — enter/exit/seed (live toggle) ──────────
1134
+
1135
+ _component.prototype._enterWindowedMode = function () {
1136
+ const self = this;
1137
+ const dom = this.dom;
1138
+ const winAttr = parseInt(dom.getAttribute('data-ln-list-window'), 10);
1139
+ const pageAttr = parseInt(dom.getAttribute('data-ln-list-window-page'), 10);
1140
+ const threshAttr = parseInt(dom.getAttribute('data-ln-list-window-threshold'), 10);
1141
+
1142
+ this._onCacheChange = function () {
1143
+ if (!self._windowed || !self._cache) return;
1144
+ self.totalCount = self._cache.grandTotal;
1145
+ self.visibleCount = self._cache.logicalTotal;
1146
+ self._lastTotal = self._cache.grandTotal;
1147
+ self.isLoaded = true;
1148
+ self._vStart = -1;
1149
+ self._vEnd = -1;
1150
+ self._render();
1151
+ self._updateFooter();
1152
+ dispatch(dom, 'ln-list:rendered', {
1153
+ list: self.name,
1154
+ total: self.totalCount,
1155
+ visible: self.visibleCount
1156
+ });
1157
+ };
1158
+ this._renderBatch = createBatcher(this._onCacheChange);
1159
+
1160
+ this._cache = createWindowCache({
1161
+ windowSize: winAttr > 0 ? winAttr : WINDOW_DEFAULT,
1162
+ pageSize: pageAttr > 0 ? pageAttr : WINDOW_PAGE,
1163
+ threshold: threshAttr >= 0 ? threshAttr : 25,
1164
+ fetchDebounce: FETCH_DEBOUNCE,
1165
+ requestPage: function (query, offset, limit) {
1166
+ dispatch(dom, 'ln-list:request-data', {
1167
+ list: self.name,
1168
+ sort: query.sort,
1169
+ filters: query.filters,
1170
+ search: query.search,
1171
+ offset: offset,
1172
+ limit: limit,
1173
+ queryGen: self._cache.queryGen
1174
+ });
1175
+ },
1176
+ onChange: this._renderBatch
1177
+ });
1178
+
1179
+ this._windowed = true;
1180
+
1181
+ if (this._selectable && this._selectAllCheckbox) {
1182
+ this._selectAllCheckbox.classList.add('hidden');
1183
+ }
1184
+ };
1185
+
1186
+ _component.prototype._kickWindowInitial = function () {
1187
+ if (this._data.length > 0) {
1188
+ // SSR-seeded / warm-seeded: page 0 is already resident, the grand
1189
+ // total is declared in markup — no initial fetch needed. No
1190
+ // queryGen on the seed — it must never be dropped as stale.
1191
+ const declaredTotal = parseInt(this.dom.getAttribute('data-ln-list-count'), 10);
1192
+ const seedTotal = declaredTotal > 0 ? declaredTotal : this._data.length;
1193
+ this._cache.ingest({
1194
+ data: this._data,
1195
+ offset: 0,
1196
+ total: seedTotal,
1197
+ filtered: seedTotal
1198
+ });
1199
+ } else {
1200
+ this.dom.classList.add('ln-list--loading');
1201
+ this._cache.requestInitial({
1202
+ sort: this.currentSort,
1203
+ filters: this.currentFilters,
1204
+ search: this.currentSearch
1205
+ });
1206
+ }
1207
+ };
1208
+
1209
+ _component.prototype._exitWindowedMode = function () {
1210
+ this._disableVirtualScroll();
1211
+ if (this._cache) this._cache.destroy();
1212
+ this._cache = null;
1213
+ this._windowed = false;
1214
+ this._renderBatch = null;
1215
+ this._onCacheChange = null;
1216
+ if (this._selectAllCheckbox) {
1217
+ this._selectAllCheckbox.classList.remove('hidden');
1218
+ }
1219
+ this._itemHeight = 0;
1220
+ this._vStart = -1;
1221
+ this._vEnd = -1;
1222
+ this._data = [];
1223
+ this._filteredData = [];
1224
+ this.dom.classList.add('ln-list--loading');
1225
+ this._requestData();
1226
+ };
1227
+
1228
+ _component.prototype._updateFooter = function () {
1229
+ let total = 0;
1230
+ let filtered = 0;
1231
+
1232
+ if (this.isDataDriven) {
1233
+ total = this._lastTotal != null ? this._lastTotal : this._data.length;
1234
+ filtered = this.visibleCount;
1235
+ } else {
1236
+ total = this._data.length;
1237
+ filtered = this._filteredData.length;
1238
+ }
1239
+
1240
+ const hasActiveFilter = filtered < total;
1241
+
1242
+ if (this._totalSpan) this._totalSpan.textContent = _formatNum(total, this.dom);
1243
+ if (this._filteredSpan) this._filteredSpan.textContent = hasActiveFilter ? _formatNum(filtered, this.dom) : '';
1244
+ if (this._filteredWrap) this._filteredWrap.classList.toggle('hidden', !hasActiveFilter);
1245
+
1246
+ if (this._selectedSpan) {
1247
+ const selected = this.selectedIds ? this.selectedIds.size : 0;
1248
+ this._selectedSpan.textContent = selected > 0 ? _formatNum(selected, this.dom) : '';
1249
+ if (this._selectedWrap) {
1250
+ this._selectedWrap.classList.toggle('hidden', selected === 0);
1251
+ }
1252
+ }
1253
+ };
1254
+
1255
+ _component.prototype.destroy = function () {
1256
+ if (!this.dom[DOM_ATTRIBUTE]) return;
1257
+
1258
+ this._disableVirtualScroll();
1259
+ this.dom.removeEventListener('ln-list:set-search', this._onSetSearch);
1260
+ this.dom.removeEventListener('ln-search:change', this._onSearchChange);
1261
+ this.dom.removeEventListener('ln-list:request-clear-filters', this._onRequestClearFilters);
1262
+
1263
+ if (this.isDataDriven) {
1264
+ if (this._cache) this._cache.destroy();
1265
+ this.dom.removeEventListener('ln-list:set-data', this._onSetData);
1266
+ this.dom.removeEventListener('ln-list:set-loading', this._onSetLoading);
1267
+ this.dom.removeEventListener('ln-list:page-failed', this._onPageFailed);
1268
+ this.dom.removeEventListener('ln-list:request-revalidate', this._onRequestRevalidate);
1269
+ this.dom.removeEventListener('ln-list:request-invalidate', this._onRequestInvalidate);
1270
+ this.dom.removeEventListener('ln-sort:change', this._onSort);
1271
+
1272
+ if (this.tbody) {
1273
+ this.tbody.removeEventListener('click', this._onItemClick);
1274
+ this.tbody.removeEventListener('click', this._onItemAction);
1275
+ }
1276
+ } else {
1277
+ if (this._emptyObserver) {
1278
+ this._emptyObserver.disconnect();
1279
+ this._emptyObserver = null;
1280
+ }
1281
+ if (this._onFilterChange) this.dom.removeEventListener('ln-filter:change', this._onFilterChange);
1282
+ if (this._onSort) this.dom.removeEventListener('ln-sort:change', this._onSort);
1283
+ }
1284
+
1285
+ if (this._onSelectionChange && this.tbody) {
1286
+ this.tbody.removeEventListener('change', this._onSelectionChange);
1287
+ }
1288
+ if (this._selectAllCheckbox && this._onSelectAll) {
1289
+ this._selectAllCheckbox.removeEventListener('change', this._onSelectAll);
1290
+ }
1291
+
1292
+ this._data = [];
1293
+ this._filteredData = [];
1294
+ delete this.dom[DOM_ATTRIBUTE];
1295
+ };
1296
+
1297
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-list', {
1298
+ attributes: ATTRIBUTES
1299
+ });
1300
+ })();
1301
+