@livenetworks/ashlar 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (560) hide show
  1. package/LICENSE +641 -0
  2. package/README.md +202 -86
  3. package/{js → components}/COMPONENTS.md +1116 -1108
  4. package/{js → components}/index.js +81 -58
  5. package/{js → components}/ln-accordion/README.md +7 -6
  6. package/components/ln-accordion/ln-accordion-dev.scss +8 -0
  7. package/components/ln-accordion/ln-accordion.js +1 -0
  8. package/components/ln-accordion/ln-accordion.schema.json +30 -0
  9. package/components/ln-accordion/ln-accordion.scss +8 -0
  10. package/{js → components}/ln-accordion/src/ln-accordion.js +8 -2
  11. package/{js → components}/ln-ajax/README.md +171 -160
  12. package/components/ln-ajax/ln-ajax-dev.scss +8 -0
  13. package/components/ln-ajax/ln-ajax.js +1 -0
  14. package/components/ln-ajax/ln-ajax.schema.json +30 -0
  15. package/{js → components}/ln-ajax/src/ln-ajax.js +65 -30
  16. package/{js → components}/ln-api-connector/README.md +27 -5
  17. package/components/ln-api-connector/ln-api-connector.js +1 -0
  18. package/components/ln-api-connector/ln-api-connector.schema.json +96 -0
  19. package/components/ln-api-connector/src/connector-core.js +97 -0
  20. package/components/ln-api-connector/src/ln-api-connector.js +447 -0
  21. package/{js → components}/ln-api-queue/README.md +12 -9
  22. package/components/ln-api-queue/ln-api-queue.js +1 -0
  23. package/components/ln-api-queue/ln-api-queue.schema.json +34 -0
  24. package/{js → components}/ln-api-queue/src/ln-api-queue.js +33 -14
  25. package/{js → components}/ln-api-queue/src/queue-storage.js +7 -3
  26. package/{js → components}/ln-autoresize/README.md +129 -127
  27. package/components/ln-autoresize/ln-autoresize-dev.scss +8 -0
  28. package/components/ln-autoresize/ln-autoresize.js +1 -0
  29. package/components/ln-autoresize/ln-autoresize.schema.json +22 -0
  30. package/{js → components}/ln-autoresize/src/ln-autoresize.js +8 -1
  31. package/{js → components}/ln-autosave/README.md +127 -124
  32. package/components/ln-autosave/ln-autosave-dev.scss +13 -0
  33. package/components/ln-autosave/ln-autosave.js +1 -0
  34. package/components/ln-autosave/ln-autosave.schema.json +48 -0
  35. package/components/ln-autosave/src/autosave-model.js +34 -0
  36. package/{js → components}/ln-autosave/src/ln-autosave.js +65 -58
  37. package/components/ln-chart/README.md +145 -0
  38. package/components/ln-chart/ln-chart-dev.scss +20 -0
  39. package/components/ln-chart/ln-chart.js +1 -0
  40. package/components/ln-chart/ln-chart.schema.json +146 -0
  41. package/components/ln-chart/ln-chart.scss +8 -0
  42. package/components/ln-chart/src/chart-model.js +154 -0
  43. package/components/ln-chart/src/ln-chart.js +176 -0
  44. package/{js → components}/ln-circular-progress/README.md +15 -14
  45. package/components/ln-circular-progress/ln-circular-progress-dev.scss +8 -0
  46. package/components/ln-circular-progress/ln-circular-progress.js +1 -0
  47. package/components/ln-circular-progress/ln-circular-progress.schema.json +42 -0
  48. package/{js → components}/ln-circular-progress/src/ln-circular-progress.js +32 -50
  49. package/components/ln-confirm/README.md +216 -0
  50. package/components/ln-confirm/ln-confirm-dev.scss +24 -0
  51. package/components/ln-confirm/ln-confirm.js +1 -0
  52. package/components/ln-confirm/ln-confirm.schema.json +66 -0
  53. package/components/ln-confirm/ln-confirm.scss +24 -0
  54. package/{js → components}/ln-confirm/src/ln-confirm.js +180 -167
  55. package/{js → components}/ln-core/README.md +374 -149
  56. package/components/ln-core/attrs.js +273 -0
  57. package/components/ln-core/compare.js +40 -0
  58. package/components/ln-core/crypto.js +265 -0
  59. package/components/ln-core/date.js +50 -0
  60. package/components/ln-core/dom.js +115 -0
  61. package/components/ln-core/events.js +122 -0
  62. package/components/ln-core/form.js +136 -0
  63. package/{js → components}/ln-core/hash.js +149 -92
  64. package/components/ln-core/helpers.js +33 -0
  65. package/components/ln-core/http.js +48 -0
  66. package/components/ln-core/index.js +11 -0
  67. package/components/ln-core/lifecycle.js +403 -0
  68. package/components/ln-core/ln-core.schema.json +109 -0
  69. package/components/ln-core/locale.js +55 -0
  70. package/components/ln-core/matching.js +78 -0
  71. package/components/ln-core/number.js +125 -0
  72. package/components/ln-core/progress.js +30 -0
  73. package/components/ln-core/template.js +206 -0
  74. package/{js → components}/ln-core/window-cache.js +247 -213
  75. package/{js → components}/ln-couchdb-connector/README.md +7 -5
  76. package/components/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
  77. package/components/ln-couchdb-connector/ln-couchdb-connector.schema.json +50 -0
  78. package/{js → components}/ln-couchdb-connector/src/ln-couchdb-connector.js +29 -43
  79. package/{js → components}/ln-data-coordinator/README.md +109 -73
  80. package/components/ln-data-coordinator/ln-data-coordinator-dev.scss +8 -0
  81. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -0
  82. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +196 -0
  83. package/components/ln-data-coordinator/src/data-read-policy.js +41 -0
  84. package/{js → components}/ln-data-coordinator/src/ln-data-coordinator.js +411 -154
  85. package/{js → components}/ln-data-coordinator/src/mutation-receipts.js +0 -5
  86. package/{js → components}/ln-data-store/README.md +42 -17
  87. package/components/ln-data-store/ln-data-store-dev.scss +13 -0
  88. package/components/ln-data-store/ln-data-store.js +1 -0
  89. package/components/ln-data-store/ln-data-store.schema.json +84 -0
  90. package/components/ln-data-store/src/data-store-model.js +155 -0
  91. package/{js → components}/ln-data-store/src/ln-data-store.js +407 -105
  92. package/components/ln-data-store/src/window-index.js +119 -0
  93. package/{js → components}/ln-date/README.md +17 -7
  94. package/{js → components}/ln-date/ln-date-dev.scss +14 -14
  95. package/components/ln-date/ln-date.js +1 -0
  96. package/components/ln-date/ln-date.schema.json +92 -0
  97. package/components/ln-date/src/date-model.js +186 -0
  98. package/components/ln-date/src/ln-date.js +486 -0
  99. package/components/ln-debug/README.md +132 -0
  100. package/components/ln-debug/ln-debug.js +1 -0
  101. package/components/ln-debug/ln-debug.schema.json +28 -0
  102. package/components/ln-debug/src/console-sink.js +19 -0
  103. package/components/ln-debug/src/debug-verifier.js +331 -0
  104. package/components/ln-debug/src/gate.js +95 -0
  105. package/components/ln-debug/src/generated-attributes.js +299 -0
  106. package/components/ln-debug/src/ln-debug.js +60 -0
  107. package/{js → components}/ln-dropdown/README.md +150 -140
  108. package/components/ln-dropdown/ln-dropdown-dev.scss +13 -0
  109. package/components/ln-dropdown/ln-dropdown.js +1 -0
  110. package/components/ln-dropdown/ln-dropdown.schema.json +90 -0
  111. package/components/ln-dropdown/ln-dropdown.scss +18 -0
  112. package/{js → components}/ln-dropdown/src/ln-dropdown.js +114 -11
  113. package/{js → components}/ln-editor/README.md +11 -9
  114. package/components/ln-editor/ln-editor-dev.scss +18 -0
  115. package/components/ln-editor/ln-editor.js +1 -0
  116. package/components/ln-editor/ln-editor.schema.json +58 -0
  117. package/components/ln-editor/ln-editor.scss +13 -0
  118. package/{js → components}/ln-editor/src/ln-editor.js +59 -11
  119. package/{js → components}/ln-external-links/README.md +10 -8
  120. package/components/ln-external-links/ln-external-links.js +1 -0
  121. package/components/ln-external-links/ln-external-links.schema.json +18 -0
  122. package/{js → components}/ln-external-links/src/ln-external-links.js +13 -11
  123. package/{js → components}/ln-fill/README.md +6 -8
  124. package/components/ln-fill/ln-fill.js +1 -0
  125. package/components/ln-fill/ln-fill.schema.json +23 -0
  126. package/{js → components}/ln-fill/src/ln-fill.js +7 -1
  127. package/components/ln-filter/README.md +131 -0
  128. package/components/ln-filter/ln-filter-dev.scss +13 -0
  129. package/components/ln-filter/ln-filter.js +1 -0
  130. package/components/ln-filter/ln-filter.schema.json +153 -0
  131. package/components/ln-filter/src/filter-model.js +185 -0
  132. package/components/ln-filter/src/ln-filter.js +435 -0
  133. package/{js → components}/ln-form/README.md +260 -264
  134. package/components/ln-form/ln-form-dev.scss +8 -0
  135. package/components/ln-form/ln-form.js +1 -0
  136. package/components/ln-form/ln-form.schema.json +45 -0
  137. package/{js → components}/ln-form/src/ln-form.js +107 -99
  138. package/{js → components}/ln-http/README.md +11 -6
  139. package/components/ln-http/ln-http.js +1 -0
  140. package/components/ln-http/ln-http.schema.json +6 -0
  141. package/components/ln-http/src/http-core.js +51 -0
  142. package/{js → components}/ln-http/src/ln-http.js +28 -33
  143. package/{js → components}/ln-icon/README.md +16 -9
  144. package/components/ln-icon/ln-icon.schema.json +6 -0
  145. package/components/ln-include/README.md +75 -0
  146. package/components/ln-include/ln-include-dev.scss +13 -0
  147. package/components/ln-include/ln-include.js +1 -0
  148. package/components/ln-include/ln-include.schema.json +23 -0
  149. package/components/ln-include/src/ln-include.js +111 -0
  150. package/components/ln-key/README.md +130 -0
  151. package/components/ln-key/ln-key-dev.scss +18 -0
  152. package/components/ln-key/ln-key.js +1 -0
  153. package/components/ln-key/ln-key.schema.json +56 -0
  154. package/components/ln-key/src/key-model.js +146 -0
  155. package/components/ln-key/src/ln-key.js +201 -0
  156. package/{js → components}/ln-link/README.md +7 -9
  157. package/components/ln-link/ln-link-dev.scss +8 -0
  158. package/components/ln-link/ln-link.js +1 -0
  159. package/components/ln-link/ln-link.schema.json +22 -0
  160. package/{js → components}/ln-link/src/ln-link.js +16 -11
  161. package/components/ln-list/README.md +152 -0
  162. package/components/ln-list/ln-list-dev.scss +8 -0
  163. package/components/ln-list/ln-list.js +1 -0
  164. package/components/ln-list/ln-list.schema.json +166 -0
  165. package/components/ln-list/ln-list.scss +25 -0
  166. package/{js → components}/ln-list/src/ln-list.js +1301 -1216
  167. package/{js → components}/ln-modal/README.md +13 -10
  168. package/components/ln-modal/ln-modal-dev.scss +18 -0
  169. package/components/ln-modal/ln-modal.js +1 -0
  170. package/components/ln-modal/ln-modal.schema.json +54 -0
  171. package/components/ln-modal/ln-modal.scss +51 -0
  172. package/{js → components}/ln-modal/src/ln-modal.js +164 -137
  173. package/{js → components}/ln-nav/README.md +11 -11
  174. package/components/ln-nav/ln-nav.js +1 -0
  175. package/components/ln-nav/ln-nav.schema.json +25 -0
  176. package/{js → components}/ln-nav/src/ln-nav.js +25 -14
  177. package/components/ln-number/README.md +205 -0
  178. package/{js → components}/ln-number/ln-number-dev.scss +13 -13
  179. package/components/ln-number/ln-number.js +1 -0
  180. package/components/ln-number/ln-number.schema.json +63 -0
  181. package/components/ln-number/src/ln-number.js +400 -0
  182. package/components/ln-number/src/number-model.js +31 -0
  183. package/components/ln-obfuscator/README.md +60 -0
  184. package/components/ln-obfuscator/ln-obfuscator.js +1 -0
  185. package/components/ln-obfuscator/ln-obfuscator.schema.json +49 -0
  186. package/components/ln-obfuscator/src/ln-obfuscator.js +159 -0
  187. package/components/ln-obfuscator/src/obfuscator-model.js +250 -0
  188. package/{js → components}/ln-options/README.md +3 -1
  189. package/components/ln-options/ln-options-dev.scss +13 -0
  190. package/components/ln-options/ln-options.js +1 -0
  191. package/components/ln-options/ln-options.schema.json +41 -0
  192. package/{js → components}/ln-options/src/ln-options.js +13 -5
  193. package/components/ln-persist/README.md +151 -0
  194. package/components/ln-persist/ln-persist.js +1 -0
  195. package/components/ln-persist/ln-persist.schema.json +28 -0
  196. package/components/ln-persist/src/ln-persist.js +83 -0
  197. package/components/ln-picklist/README.md +230 -0
  198. package/components/ln-picklist/ln-picklist-dev.scss +22 -0
  199. package/components/ln-picklist/ln-picklist.js +1 -0
  200. package/components/ln-picklist/ln-picklist.schema.json +49 -0
  201. package/components/ln-picklist/src/ln-picklist.js +228 -0
  202. package/{js → components}/ln-popover/README.md +164 -162
  203. package/components/ln-popover/ln-popover-dev.scss +13 -0
  204. package/components/ln-popover/ln-popover.js +1 -0
  205. package/components/ln-popover/ln-popover.schema.json +82 -0
  206. package/components/ln-popover/ln-popover.scss +18 -0
  207. package/{js → components}/ln-popover/src/ln-popover.js +42 -36
  208. package/{js → components}/ln-progress/README.md +410 -413
  209. package/components/ln-progress/ln-progress-dev.scss +8 -0
  210. package/components/ln-progress/ln-progress.js +1 -0
  211. package/components/ln-progress/ln-progress.schema.json +40 -0
  212. package/components/ln-progress/src/ln-progress.js +91 -0
  213. package/components/ln-progress/src/progress-model.js +20 -0
  214. package/{js → components}/ln-router/README.md +443 -432
  215. package/components/ln-router/ln-router-dev.scss +13 -0
  216. package/components/ln-router/ln-router.js +1 -0
  217. package/components/ln-router/ln-router.schema.json +68 -0
  218. package/{js → components}/ln-router/src/ln-router.js +585 -539
  219. package/components/ln-router/src/router-model.js +50 -0
  220. package/components/ln-scroll/README.md +45 -0
  221. package/components/ln-scroll/ln-scroll.js +1 -0
  222. package/components/ln-scroll/ln-scroll.schema.json +85 -0
  223. package/components/ln-scroll/src/ln-scroll.js +150 -0
  224. package/components/ln-search/README.md +188 -0
  225. package/components/ln-search/ln-search-dev.scss +13 -0
  226. package/components/ln-search/ln-search.js +1 -0
  227. package/components/ln-search/ln-search.schema.json +103 -0
  228. package/components/ln-search/src/ln-search.js +330 -0
  229. package/{js → components}/ln-slug/README.md +6 -3
  230. package/components/ln-slug/ln-slug-dev.scss +18 -0
  231. package/components/ln-slug/ln-slug.js +1 -0
  232. package/components/ln-slug/ln-slug.schema.json +23 -0
  233. package/{js → components}/ln-slug/src/ln-slug.js +20 -13
  234. package/components/ln-slug/src/slug-model.js +21 -0
  235. package/{js → components}/ln-sort/README.md +178 -170
  236. package/{js → components}/ln-sort/ln-sort-dev.scss +8 -8
  237. package/components/ln-sort/ln-sort.js +1 -0
  238. package/components/ln-sort/ln-sort.schema.json +104 -0
  239. package/components/ln-sort/src/ln-sort.js +321 -0
  240. package/components/ln-sort/src/sort-model.js +117 -0
  241. package/{js → components}/ln-sortable/README.md +7 -11
  242. package/components/ln-sortable/ln-sortable-dev.scss +13 -0
  243. package/components/ln-sortable/ln-sortable.js +1 -0
  244. package/components/ln-sortable/ln-sortable.schema.json +36 -0
  245. package/{js → components}/ln-sortable/src/ln-sortable.js +8 -13
  246. package/{js → components}/ln-stat/README.md +3 -1
  247. package/components/ln-stat/ln-stat-dev.scss +8 -0
  248. package/components/ln-stat/ln-stat.js +1 -0
  249. package/components/ln-stat/ln-stat.schema.json +32 -0
  250. package/{js → components}/ln-stat/src/ln-stat.js +14 -18
  251. package/components/ln-stat/src/stat-model.js +28 -0
  252. package/{js → components}/ln-table/README.md +37 -24
  253. package/{js → components}/ln-table/ln-table-dev.scss +17 -17
  254. package/components/ln-table/ln-table.js +1 -0
  255. package/components/ln-table/ln-table.schema.json +211 -0
  256. package/components/ln-table/ln-table.scss +15 -0
  257. package/{js → components}/ln-table/src/ln-table.js +1365 -1493
  258. package/components/ln-table/src/table-model.js +98 -0
  259. package/components/ln-table-coordinator/README.md +61 -0
  260. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -0
  261. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +93 -0
  262. package/components/ln-table-coordinator/src/ln-table-coordinator.js +193 -0
  263. package/{js → components}/ln-tabs/README.md +32 -14
  264. package/components/ln-tabs/ln-tabs-dev.scss +24 -0
  265. package/components/ln-tabs/ln-tabs.js +1 -0
  266. package/components/ln-tabs/ln-tabs.schema.json +75 -0
  267. package/components/ln-tabs/ln-tabs.scss +8 -0
  268. package/components/ln-tabs/src/ln-tabs.js +207 -0
  269. package/components/ln-tabs/src/tabs-model.js +82 -0
  270. package/{js → components}/ln-time/README.md +96 -95
  271. package/components/ln-time/ln-time-dev.scss +13 -0
  272. package/components/ln-time/ln-time.js +1 -0
  273. package/components/ln-time/ln-time.schema.json +38 -0
  274. package/{js → components}/ln-time/src/ln-time.js +206 -223
  275. package/components/ln-time/src/time-model.js +64 -0
  276. package/{js → components}/ln-toast/README.md +23 -5
  277. package/components/ln-toast/ln-toast-dev.scss +8 -0
  278. package/components/ln-toast/ln-toast.js +1 -0
  279. package/components/ln-toast/ln-toast.schema.json +65 -0
  280. package/components/ln-toast/ln-toast.scss +59 -0
  281. package/{js → components}/ln-toast/src/ln-toast.js +233 -232
  282. package/{js → components}/ln-toggle/README.md +198 -196
  283. package/{js → components}/ln-toggle/ln-toggle-dev.scss +13 -13
  284. package/components/ln-toggle/ln-toggle.js +1 -0
  285. package/components/ln-toggle/ln-toggle.schema.json +52 -0
  286. package/components/ln-toggle/ln-toggle.scss +37 -0
  287. package/{js → components}/ln-toggle/src/ln-toggle.js +61 -46
  288. package/{js → components}/ln-tooltip/README.md +6 -6
  289. package/{js → components}/ln-tooltip/ln-tooltip-dev.scss +9 -9
  290. package/components/ln-tooltip/ln-tooltip.js +1 -0
  291. package/components/ln-tooltip/ln-tooltip.schema.json +86 -0
  292. package/components/ln-tooltip/ln-tooltip.scss +9 -0
  293. package/{js → components}/ln-tooltip/src/ln-tooltip.js +14 -3
  294. package/{js → components}/ln-translations/README.md +3 -1
  295. package/components/ln-translations/ln-translations-dev.scss +13 -0
  296. package/components/ln-translations/ln-translations.js +1 -0
  297. package/components/ln-translations/ln-translations.schema.json +120 -0
  298. package/{js → components}/ln-translations/src/ln-translations.js +25 -10
  299. package/components/ln-ui-coordinator/README.md +101 -0
  300. package/components/ln-ui-coordinator/ln-ui-coordinator-dev.scss +8 -0
  301. package/components/ln-ui-coordinator/ln-ui-coordinator.js +1 -0
  302. package/components/ln-ui-coordinator/ln-ui-coordinator.schema.json +60 -0
  303. package/{js/ln-modal-coordinator/src/ln-modal-coordinator.js → components/ln-ui-coordinator/src/ln-ui-coordinator.js} +403 -345
  304. package/components/ln-upload/README.md +133 -0
  305. package/components/ln-upload/ln-upload-dev.scss +18 -0
  306. package/components/ln-upload/ln-upload.js +1 -0
  307. package/components/ln-upload/ln-upload.schema.json +193 -0
  308. package/components/ln-upload/ln-upload.scss +20 -0
  309. package/components/ln-upload/src/ln-upload.js +652 -0
  310. package/components/ln-upload/src/upload-model.js +78 -0
  311. package/{js → components}/ln-validate/README.md +182 -180
  312. package/{js → components}/ln-validate/ln-validate-dev.scss +15 -15
  313. package/components/ln-validate/ln-validate.js +1 -0
  314. package/components/ln-validate/ln-validate.schema.json +50 -0
  315. package/components/ln-validate/ln-validate.scss +7 -0
  316. package/{js → components}/ln-validate/src/ln-validate.js +23 -45
  317. package/components/ln-validate/src/validate-model.js +64 -0
  318. package/components/ln-websocket-connector/README.md +166 -0
  319. package/components/ln-websocket-connector/ln-websocket-connector.js +1 -0
  320. package/components/ln-websocket-connector/ln-websocket-connector.schema.json +29 -0
  321. package/components/ln-websocket-connector/src/ln-websocket-connector.js +279 -0
  322. package/ln-ashlar.html-data.json +1796 -0
  323. package/package.json +84 -61
  324. package/{scss → theme}/base/_global.scss +1 -2
  325. package/{scss → theme}/base/_reset.scss +1 -1
  326. package/{scss → theme}/base/_typography.scss +2 -0
  327. package/theme/brand.css +73 -0
  328. package/{scss → theme}/components/_app-shell.scss +8 -0
  329. package/theme/components/_button.scss +24 -0
  330. package/theme/components/_chart.scss +5 -0
  331. package/theme/components/_confirm.scss +6 -0
  332. package/{scss → theme}/components/_date.scss +30 -22
  333. package/{scss → theme}/components/_dropdown.scss +6 -1
  334. package/theme/components/_form.scss +140 -0
  335. package/{scss → theme}/components/_layout.scss +6 -0
  336. package/{scss → theme}/components/_ln-table.scss +1 -6
  337. package/theme/components/_picklist.scss +3 -0
  338. package/{scss → theme}/components/_sort.scss +10 -10
  339. package/theme/components/_status-badge.scss +6 -0
  340. package/{scss → theme}/components/_table.scss +16 -0
  341. package/{scss → theme}/components/_tabs.scss +0 -4
  342. package/theme/components/_toast.scss +48 -0
  343. package/{scss → theme}/components/_toggle.scss +1 -1
  344. package/theme/components/_upload.scss +45 -0
  345. package/{scss → theme}/config/_breakpoints.scss +1 -1
  346. package/theme/config/_contrast.scss +128 -0
  347. package/{scss → theme}/config/_density.scss +191 -205
  348. package/theme/config/_palette.scss +274 -0
  349. package/theme/config/_theme.scss +293 -0
  350. package/{scss → theme}/config/_tokens.scss +285 -441
  351. package/{scss → theme}/config/mixins/_accordion.scss +1 -1
  352. package/{scss → theme}/config/mixins/_alert.scss +1 -0
  353. package/{scss → theme}/config/mixins/_app-shell.scss +4 -2
  354. package/{scss → theme}/config/mixins/_borders.scss +2 -0
  355. package/theme/config/mixins/_btn.scss +175 -0
  356. package/{scss → theme}/config/mixins/_card.scss +55 -46
  357. package/theme/config/mixins/_chart.scss +82 -0
  358. package/{scss → theme}/config/mixins/_collapsible.scss +2 -1
  359. package/{scss → theme}/config/mixins/_colors.scss +8 -14
  360. package/{scss → theme}/config/mixins/_diagnostics.scss +48 -48
  361. package/theme/config/mixins/_dropdown.scss +169 -0
  362. package/{scss → theme}/config/mixins/_editor.scss +0 -7
  363. package/{scss → theme}/config/mixins/_filter.scss +47 -47
  364. package/{scss → theme}/config/mixins/_footer.scss +1 -1
  365. package/{scss → theme}/config/mixins/_form.scss +49 -18
  366. package/{scss → theme}/config/mixins/_index.scss +62 -61
  367. package/{scss → theme}/config/mixins/_layout.scss +6 -2
  368. package/{scss → theme}/config/mixins/_ln-table.scss +31 -10
  369. package/{scss → theme}/config/mixins/_loader.scss +1 -1
  370. package/{scss → theme}/config/mixins/_modal.scss +145 -149
  371. package/{scss → theme}/config/mixins/_motion.scss +13 -0
  372. package/{scss → theme}/config/mixins/_nav.scss +7 -7
  373. package/theme/config/mixins/_picklist.scss +66 -0
  374. package/theme/config/mixins/_popover.scss +96 -0
  375. package/{scss → theme}/config/mixins/_sidebar.scss +7 -4
  376. package/{scss → theme}/config/mixins/_sort.scss +43 -42
  377. package/{scss → theme}/config/mixins/_status-badge.scss +15 -2
  378. package/{scss → theme}/config/mixins/_table.scss +25 -14
  379. package/{scss → theme}/config/mixins/_tabs.scss +1 -1
  380. package/{scss → theme}/config/mixins/_toast.scss +174 -179
  381. package/{scss → theme}/config/mixins/_tooltip.scss +2 -2
  382. package/{scss → theme}/config/mixins/_typography.scss +16 -5
  383. package/{scss → theme}/config/mixins/_upload.scss +8 -6
  384. package/theme/ln-ashlar-core.scss +24 -0
  385. package/theme/ln-ashlar-dev.scss +66 -0
  386. package/theme/ln-ashlar-landing.scss +56 -0
  387. package/{scss/ln-ashlar.scss → theme/ln-ashlar-theme.scss} +63 -69
  388. package/theme/ln-ashlar.scss +8 -0
  389. package/theme/presentation/_bento.scss +151 -0
  390. package/theme/presentation/_cta.scss +111 -0
  391. package/theme/presentation/_effects.scss +216 -0
  392. package/theme/presentation/_features.scss +108 -0
  393. package/theme/presentation/_footer.scss +129 -0
  394. package/theme/presentation/_hero.scss +158 -0
  395. package/theme/presentation/_layout.scss +170 -0
  396. package/theme/presentation/_navbar.scss +140 -0
  397. package/theme/presentation/_pricing.scss +262 -0
  398. package/theme/presentation/_stats.scss +87 -0
  399. package/theme/presentation/_testimonials.scss +124 -0
  400. package/theme/presentation/_tokens.scss +39 -0
  401. package/theme/presentation/_typography.scss +163 -0
  402. package/{scss → theme}/utilities/_animations.scss +38 -0
  403. package/{scss → theme}/utilities/_utilities.scss +6 -0
  404. package/web-types.json +2312 -0
  405. package/js/ln-accordion/ln-accordion.js +0 -1
  406. package/js/ln-ajax/ln-ajax.js +0 -1
  407. package/js/ln-api-connector/ln-api-connector.js +0 -1
  408. package/js/ln-api-connector/src/ln-api-connector.js +0 -368
  409. package/js/ln-api-queue/ln-api-queue.js +0 -1
  410. package/js/ln-autoresize/ln-autoresize.js +0 -1
  411. package/js/ln-autosave/ln-autosave.js +0 -1
  412. package/js/ln-circular-progress/ln-circular-progress.js +0 -1
  413. package/js/ln-confirm/README.md +0 -162
  414. package/js/ln-confirm/_ln-confirm.scss +0 -24
  415. package/js/ln-confirm/ln-confirm.js +0 -1
  416. package/js/ln-core/crypto.js +0 -83
  417. package/js/ln-core/helpers.js +0 -788
  418. package/js/ln-core/index.js +0 -7
  419. package/js/ln-core/persist.js +0 -97
  420. package/js/ln-couchdb-connector/ln-couchdb-connector.js +0 -1
  421. package/js/ln-data-coordinator/ln-data-coordinator.js +0 -1
  422. package/js/ln-data-coordinator/src/data-read-policy.js +0 -18
  423. package/js/ln-data-store/ln-data-store.js +0 -1
  424. package/js/ln-date/ln-date.js +0 -1
  425. package/js/ln-date/src/ln-date.js +0 -603
  426. package/js/ln-debug/README.md +0 -58
  427. package/js/ln-debug/ln-debug.js +0 -1
  428. package/js/ln-debug/src/ln-debug.js +0 -19
  429. package/js/ln-dropdown/ln-dropdown.js +0 -1
  430. package/js/ln-dropdown/ln-dropdown.scss +0 -15
  431. package/js/ln-editor/ln-editor.js +0 -1
  432. package/js/ln-editor/ln-editor.scss +0 -43
  433. package/js/ln-external-links/ln-external-links.js +0 -1
  434. package/js/ln-fill/ln-fill.js +0 -1
  435. package/js/ln-filter/README.md +0 -189
  436. package/js/ln-filter/ln-filter-dev.scss +0 -8
  437. package/js/ln-filter/ln-filter.js +0 -1
  438. package/js/ln-filter/src/ln-filter.js +0 -419
  439. package/js/ln-form/ln-form.js +0 -1
  440. package/js/ln-http/ln-http.js +0 -1
  441. package/js/ln-link/ln-link.js +0 -1
  442. package/js/ln-list/README.md +0 -129
  443. package/js/ln-list/ln-list.js +0 -1
  444. package/js/ln-list/ln-list.scss +0 -28
  445. package/js/ln-modal/ln-modal-dev.scss +0 -13
  446. package/js/ln-modal/ln-modal.js +0 -1
  447. package/js/ln-modal/ln-modal.scss +0 -35
  448. package/js/ln-modal-coordinator/README.md +0 -99
  449. package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +0 -8
  450. package/js/ln-modal-coordinator/ln-modal-coordinator.js +0 -1
  451. package/js/ln-nav/ln-nav.js +0 -1
  452. package/js/ln-number/README.md +0 -149
  453. package/js/ln-number/ln-number.js +0 -1
  454. package/js/ln-number/src/ln-number.js +0 -443
  455. package/js/ln-options/ln-options.js +0 -1
  456. package/js/ln-popover/ln-popover.js +0 -1
  457. package/js/ln-progress/ln-progress.js +0 -1
  458. package/js/ln-progress/src/ln-progress.js +0 -98
  459. package/js/ln-router/ln-router.js +0 -1
  460. package/js/ln-search/README.md +0 -158
  461. package/js/ln-search/ln-search-dev.scss +0 -8
  462. package/js/ln-search/ln-search.js +0 -1
  463. package/js/ln-search/src/ln-search.js +0 -117
  464. package/js/ln-slug/ln-slug.js +0 -1
  465. package/js/ln-sort/ln-sort.js +0 -1
  466. package/js/ln-sort/src/ln-sort.js +0 -178
  467. package/js/ln-sortable/ln-sortable.js +0 -1
  468. package/js/ln-stat/ln-stat.js +0 -1
  469. package/js/ln-table/ln-table.js +0 -1
  470. package/js/ln-table/ln-table.scss +0 -11
  471. package/js/ln-table-coordinator/README.md +0 -73
  472. package/js/ln-table-coordinator/ln-table-coordinator.js +0 -1
  473. package/js/ln-table-coordinator/src/ln-table-coordinator.js +0 -160
  474. package/js/ln-tabs/ln-tabs-dev.scss +0 -18
  475. package/js/ln-tabs/ln-tabs.js +0 -1
  476. package/js/ln-tabs/src/ln-tabs.js +0 -188
  477. package/js/ln-time/ln-time.js +0 -1
  478. package/js/ln-toast/ln-toast.js +0 -1
  479. package/js/ln-toggle/ln-toggle.js +0 -1
  480. package/js/ln-tooltip/ln-tooltip.js +0 -1
  481. package/js/ln-tooltip/ln-tooltip.scss +0 -9
  482. package/js/ln-translations/ln-translations.js +0 -1
  483. package/js/ln-upload/README.md +0 -255
  484. package/js/ln-upload/ln-upload.js +0 -1
  485. package/js/ln-upload/ln-upload.scss +0 -20
  486. package/js/ln-upload/src/ln-upload.js +0 -423
  487. package/js/ln-validate/ln-validate.js +0 -1
  488. package/scss/components/_button.scss +0 -20
  489. package/scss/components/_confirm.scss +0 -5
  490. package/scss/components/_form.scss +0 -107
  491. package/scss/components/_status-badge.scss +0 -4
  492. package/scss/components/_toast.scss +0 -68
  493. package/scss/components/_upload.scss +0 -51
  494. package/scss/config/_theme.scss +0 -216
  495. package/scss/config/mixins/_btn.scss +0 -179
  496. package/scss/config/mixins/_confirm.scss +0 -31
  497. package/scss/config/mixins/_dropdown.scss +0 -141
  498. package/scss/config/mixins/_popover.scss +0 -64
  499. package/scss/ln-ashlar-dev.scss +0 -39
  500. /package/{js → components}/ln-core/positioning.js +0 -0
  501. /package/{js → components}/ln-core/reactive.js +0 -0
  502. /package/{js → components}/ln-filter/ln-filter.scss +0 -0
  503. /package/{js → components}/ln-icon/ln-icon.js +0 -0
  504. /package/{js → components}/ln-icon/src/ln-icon.js +0 -0
  505. /package/{js → components}/ln-search/ln-search.scss +0 -0
  506. /package/{js → components}/ln-toast/template.html +0 -0
  507. /package/{scss → theme}/components/_accordion.scss +0 -0
  508. /package/{scss → theme}/components/_ajax.scss +0 -0
  509. /package/{scss → theme}/components/_alert.scss +0 -0
  510. /package/{scss → theme}/components/_avatar.scss +0 -0
  511. /package/{scss → theme}/components/_breadcrumbs.scss +0 -0
  512. /package/{scss → theme}/components/_card.scss +0 -0
  513. /package/{scss → theme}/components/_chip.scss +0 -0
  514. /package/{scss → theme}/components/_circular-progress.scss +0 -0
  515. /package/{scss → theme}/components/_editor.scss +0 -0
  516. /package/{scss → theme}/components/_empty-state.scss +0 -0
  517. /package/{scss → theme}/components/_filter.scss +0 -0
  518. /package/{scss → theme}/components/_link.scss +0 -0
  519. /package/{scss → theme}/components/_loader.scss +0 -0
  520. /package/{scss → theme}/components/_modal.scss +0 -0
  521. /package/{scss → theme}/components/_nav.scss +0 -0
  522. /package/{scss → theme}/components/_page-header.scss +0 -0
  523. /package/{scss → theme}/components/_popover.scss +0 -0
  524. /package/{scss → theme}/components/_progress.scss +0 -0
  525. /package/{scss → theme}/components/_prose.scss +0 -0
  526. /package/{scss → theme}/components/_scrollbar.scss +0 -0
  527. /package/{scss → theme}/components/_sections.scss +0 -0
  528. /package/{scss → theme}/components/_sidebar.scss +0 -0
  529. /package/{scss → theme}/components/_stat-card.scss +0 -0
  530. /package/{scss → theme}/components/_stepper.scss +0 -0
  531. /package/{scss → theme}/components/_timeline.scss +0 -0
  532. /package/{scss → theme}/components/_tooltip.scss +0 -0
  533. /package/{scss → theme}/components/_translations.scss +0 -0
  534. /package/{scss → theme}/config/_icons.scss +0 -0
  535. /package/{scss → theme}/config/_mixins.scss +0 -0
  536. /package/{scss → theme}/config/mixins/_ajax.scss +0 -0
  537. /package/{scss → theme}/config/mixins/_avatar.scss +0 -0
  538. /package/{scss → theme}/config/mixins/_breadcrumbs.scss +0 -0
  539. /package/{scss → theme}/config/mixins/_breakpoints.scss +0 -0
  540. /package/{scss → theme}/config/mixins/_chip.scss +0 -0
  541. /package/{scss → theme}/config/mixins/_circular-progress.scss +0 -0
  542. /package/{scss → theme}/config/mixins/_display.scss +0 -0
  543. /package/{scss → theme}/config/mixins/_empty-state.scss +0 -0
  544. /package/{scss → theme}/config/mixins/_focus.scss +0 -0
  545. /package/{scss → theme}/config/mixins/_interaction.scss +0 -0
  546. /package/{scss → theme}/config/mixins/_kbd.scss +0 -0
  547. /package/{scss → theme}/config/mixins/_link.scss +0 -0
  548. /package/{scss → theme}/config/mixins/_page-header.scss +0 -0
  549. /package/{scss → theme}/config/mixins/_position.scss +0 -0
  550. /package/{scss → theme}/config/mixins/_progress.scss +0 -0
  551. /package/{scss → theme}/config/mixins/_prose.scss +0 -0
  552. /package/{scss → theme}/config/mixins/_shadows.scss +0 -0
  553. /package/{scss → theme}/config/mixins/_sizing.scss +0 -0
  554. /package/{scss → theme}/config/mixins/_spacing.scss +0 -0
  555. /package/{scss → theme}/config/mixins/_stat-card.scss +0 -0
  556. /package/{scss → theme}/config/mixins/_stepper.scss +0 -0
  557. /package/{scss → theme}/config/mixins/_timeline.scss +0 -0
  558. /package/{scss → theme}/config/mixins/_transitions.scss +0 -0
  559. /package/{scss → theme}/config/mixins/_translations.scss +0 -0
  560. /package/{scss → theme}/tabler-icons.txt +0 -0
@@ -0,0 +1,45 @@
1
+ {
2
+ "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
+ "component": "ln-form",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-debug": {
7
+ "direction": null,
8
+ "sources": [
9
+ "ln-form-dev.scss"
10
+ ]
11
+ },
12
+ "data-ln-form": {
13
+ "direction": null,
14
+ "sources": [
15
+ "ln-form-dev.scss",
16
+ "src/ln-form.js"
17
+ ],
18
+ "type": "marker",
19
+ "description": "Identifies the form element as an enhanced ln-form"
20
+ },
21
+ "data-ln-form-action-edit": {
22
+ "direction": "author",
23
+ "sources": [
24
+ "src/ln-form.js"
25
+ ],
26
+ "type": "string",
27
+ "fallback": "",
28
+ "description": "URL template or endpoint used when editing an existing record"
29
+ },
30
+ "data-ln-form-action-method": {
31
+ "direction": null,
32
+ "sources": [
33
+ "src/ln-form.js"
34
+ ],
35
+ "type": "enum",
36
+ "values": [
37
+ "PUT",
38
+ "POST",
39
+ "PATCH"
40
+ ],
41
+ "fallback": "PUT",
42
+ "description": "HTTP method used when submitting edit action"
43
+ }
44
+ }
45
+ }
@@ -1,99 +1,107 @@
1
- import { populateForm, dispatch, registerComponent } from '../../ln-core';
2
-
3
- (function () {
4
- const DOM_SELECTOR = 'data-ln-form';
5
- const DOM_ATTRIBUTE = 'lnForm';
6
- const ACTION_EDIT_ATTR = 'data-ln-form-action-edit';
7
- const ACTION_METHOD_ATTR = 'data-ln-form-action-method';
8
-
9
- if (window[DOM_ATTRIBUTE] !== undefined) return;
10
-
11
- // ─── Component ─────────────────────────────────────────────
12
-
13
- function _component(form) {
14
- this.dom = form;
15
- this._baseAction = form.getAttribute('action') || '';
16
-
17
- const self = this;
18
-
19
- this._onLnFill = function (e) {
20
- // Guard: only handle direct dispatches at this form, not bubbled
21
- // events from [data-ln-fillable] children inside the form.
22
- if (e.target !== self.dom) return;
23
- if (e.detail) {
24
- self.fill(e.detail);
25
- self._applyActionMode(e.detail);
26
- } else {
27
- // Null record = "new" mode. Native reset fires the 'reset'
28
- // event, which restores the base action below.
29
- self.dom.reset();
30
- }
31
- };
32
-
33
- this._onReset = function () {
34
- self._applyActionMode(null);
35
- };
36
-
37
- form.addEventListener('ln-fill', this._onLnFill);
38
- form.addEventListener('reset', this._onReset);
39
-
40
- return this;
41
- }
42
-
43
- _component.prototype.fill = function (data) {
44
- const filled = populateForm(this.dom, data);
45
-
46
- // Trigger events so reactive consumers (ln-validate, ln-autoresize)
47
- // pick up the changes. SELECT/checkbox/radio -> 'change',
48
- // everything else -> 'input'.
49
- for (let k = 0; k < filled.length; k++) {
50
- const el = filled[k];
51
- const isChangeBased = el.tagName === 'SELECT' || el.type === 'checkbox' || el.type === 'radio';
52
- el.dispatchEvent(new Event(isChangeBased ? 'change' : 'input', { bubbles: true }));
53
- }
54
- };
55
-
56
- _component.prototype._ensureMethodInput = function () {
57
- let input = this.dom.querySelector('input[name="_method"]');
58
- if (!input) {
59
- input = document.createElement('input');
60
- input.type = 'hidden';
61
- input.name = '_method';
62
- input.value = '';
63
- this.dom.appendChild(input);
64
- }
65
- return input;
66
- };
67
-
68
- _component.prototype._applyActionMode = function (record) {
69
- if (!this.dom.hasAttribute(ACTION_EDIT_ATTR)) return;
70
-
71
- const id = record && record.id != null && record.id !== '' ? record.id : null;
72
- const methodInput = this._ensureMethodInput();
73
-
74
- if (id !== null) {
75
- const template = this.dom.getAttribute(ACTION_EDIT_ATTR);
76
- if (template) {
77
- this.dom.setAttribute('action', template.replace(':id', encodeURIComponent(id)));
78
- } else {
79
- this.dom.setAttribute('action', this._baseAction.replace(/\/$/, '') + '/' + encodeURIComponent(id));
80
- }
81
- methodInput.value = this.dom.getAttribute(ACTION_METHOD_ATTR) || 'PUT';
82
- } else {
83
- this.dom.setAttribute('action', this._baseAction);
84
- methodInput.value = '';
85
- }
86
- };
87
-
88
- _component.prototype.destroy = function () {
89
- if (!this.dom[DOM_ATTRIBUTE]) return;
90
- this.dom.removeEventListener('ln-fill', this._onLnFill);
91
- this.dom.removeEventListener('reset', this._onReset);
92
- dispatch(this.dom, 'ln-form:destroyed', { target: this.dom });
93
- delete this.dom[DOM_ATTRIBUTE];
94
- };
95
-
96
- // ─── Init ──────────────────────────────────────────────────
97
-
98
- registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-form');
99
- })();
1
+ import { populateForm, registerComponent, defineAttrs, attrSpec, attrStr } from '../../ln-core';
2
+
3
+ (function () {
4
+ const DOM_SELECTOR = 'data-ln-form';
5
+ const DOM_ATTRIBUTE = 'lnForm';
6
+
7
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
8
+
9
+ // ─── Attribute Contract (SSOT) ──────────────────────────
10
+ const ATTRIBUTES = {
11
+ 'data-ln-form': { type: 'marker', description: 'Identifies the form element as an enhanced ln-form' },
12
+ 'data-ln-form-action-edit': { prop: '_actionEdit', type: 'string', read: attrStr, fallback: '', description: 'URL template or endpoint used when editing an existing record' },
13
+ 'data-ln-form-action-method': { prop: '_actionMethod', type: 'enum', values: ['PUT', 'POST', 'PATCH'], fallback: 'PUT', description: 'HTTP method used when submitting edit action' }
14
+ };
15
+ const ATTR_SPEC = attrSpec(ATTRIBUTES);
16
+
17
+ // ─── Component ─────────────────────────────────────────────
18
+
19
+ function _component(form) {
20
+ this.dom = form;
21
+ defineAttrs(this, form, ATTR_SPEC);
22
+ this._baseAction = form.getAttribute('action') || '';
23
+
24
+ const self = this;
25
+
26
+ this._onLnFill = function (e) {
27
+ // Guard: only handle direct dispatches at this form, not bubbled
28
+ // events from [data-ln-fillable] children inside the form.
29
+ if (e.target !== self.dom) return;
30
+ if (e.detail) {
31
+ self.fill(e.detail);
32
+ self._applyActionMode(e.detail);
33
+ } else {
34
+ // Null record = "new" mode. Native reset fires the 'reset'
35
+ // event, which restores the base action below.
36
+ self.dom.reset();
37
+ }
38
+ };
39
+
40
+ this._onReset = function () {
41
+ self._applyActionMode(null);
42
+ };
43
+
44
+ form.addEventListener('ln-fill', this._onLnFill);
45
+ form.addEventListener('reset', this._onReset);
46
+
47
+ return this;
48
+ }
49
+
50
+ _component.prototype.fill = function (data) {
51
+ const filled = populateForm(this.dom, data);
52
+
53
+ // Trigger events so reactive consumers (ln-validate, ln-autoresize)
54
+ // pick up the changes. SELECT/checkbox/radio -> 'change',
55
+ // everything else -> 'input'.
56
+ for (let k = 0; k < filled.length; k++) {
57
+ const el = filled[k];
58
+ const isChangeBased = el.tagName === 'SELECT' || el.type === 'checkbox' || el.type === 'radio';
59
+ el.dispatchEvent(new Event(isChangeBased ? 'change' : 'input', { bubbles: true }));
60
+ }
61
+ };
62
+
63
+ _component.prototype._ensureMethodInput = function () {
64
+ let input = this.dom.querySelector('input[name="_method"]');
65
+ if (!input) {
66
+ input = document.createElement('input');
67
+ input.type = 'hidden';
68
+ input.name = '_method';
69
+ input.value = '';
70
+ this.dom.appendChild(input);
71
+ }
72
+ return input;
73
+ };
74
+
75
+ _component.prototype._applyActionMode = function (record) {
76
+ if (!this.dom.hasAttribute('data-ln-form-action-edit')) return;
77
+
78
+ const id = record && record.id != null && record.id !== '' ? record.id : null;
79
+ const methodInput = this._ensureMethodInput();
80
+
81
+ if (id !== null) {
82
+ const template = this._actionEdit;
83
+ if (template) {
84
+ this.dom.setAttribute('action', template.replace(':id', encodeURIComponent(id)));
85
+ } else {
86
+ this.dom.setAttribute('action', this._baseAction.replace(/\/$/, '') + '/' + encodeURIComponent(id));
87
+ }
88
+ methodInput.value = this._actionMethod || 'PUT';
89
+ } else {
90
+ this.dom.setAttribute('action', this._baseAction);
91
+ methodInput.value = '';
92
+ }
93
+ };
94
+
95
+ _component.prototype.destroy = function () {
96
+ if (!this.dom[DOM_ATTRIBUTE]) return;
97
+ this.dom.removeEventListener('ln-fill', this._onLnFill);
98
+ this.dom.removeEventListener('reset', this._onReset);
99
+ delete this.dom[DOM_ATTRIBUTE];
100
+ };
101
+
102
+ // ─── Init ──────────────────────────────────────────────────
103
+
104
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-form', {
105
+ attributes: ATTRIBUTES
106
+ });
107
+ })();
@@ -1,8 +1,9 @@
1
1
  # ln-http
2
2
 
3
- A zero-dependency, global **HTTP Concurrency Coordinator** that intercepts browser network operations to prevent race conditions, out-of-order responses, and duplicate submission side-effects.
4
-
5
- It manages requests on two distinct pipelines: **Path A** (transparent GET/HEAD URL-deduplication wrapping `window.fetch`) and **Path B** (explicit key-based event-driven cancellations for POST/PUT/DELETE).
3
+ > A window-level HTTP concurrency coordinator (`window.lnHttp`). It transparently intercepts `fetch()` to call
4
+ > `AbortController.abort()` on superseded in-flight GET/HEAD requests targeting the exact same URL. Additionally, it listens
5
+ > globally for `ln-http:request` CustomEvents bearing a unique `key`: automatically aborting any running request with the same
6
+ > key, executing the fetch, and dispatching `ln-http:response` or `ln-http:error`.
6
7
 
7
8
  ---
8
9
 
@@ -68,9 +69,13 @@ element.dispatchEvent(new CustomEvent('ln-http:request', {
68
69
 
69
70
  ---
70
71
 
71
- ## ⚡ DOM Events (Path B response lifecycle)
72
+ ## ⚡ DOM Events (Path B response lifecycle & declarative control)
73
+
74
+ ### `ln-http:cancel`
75
+ Listened on `document`. Declaratively cancels in-flight requests without calling imperative window methods.
76
+ - `detail`: `{ key?: string, url?: string, all?: boolean }`
72
77
 
73
- Both events bubble from the element that dispatched the original `'ln-http:request'`.
78
+ Both response events bubble from the element that dispatched the original `'ln-http:request'`.
74
79
 
75
80
  ### `ln-http:response`
76
81
  Fired when `fetch` resolves. The consumer must branch on `ok`/`status` and parse the raw body.
@@ -92,7 +97,7 @@ Fired when network-level failures reject the fetch promise (excluding aborts).
92
97
 
93
98
  ## 🔧 Internals
94
99
 
95
- Source: `js/ln-http/ln-http.js`. A **service-style global**: wraps `window.fetch`, listens on `document`, no DOM instance, no `data-ln-*`, no MutationObserver. Two closure-private `Map`s hold all state — `_inflight` (`"METHOD URL" → AbortController`, Path A) and `_keyed` (`consumer key → AbortController`, Path B) — plus `_origFetch`, the unwrapped `fetch` captured at load, used to restore on `destroy()`.
100
+ Source: `components/ln-http/ln-http.js`. A **service-style global**: wraps `window.fetch`, listens on `document`, no DOM instance, no `data-ln-*`, no MutationObserver. Two closure-private `Map`s hold all state — `_inflight` (`"METHOD URL" → AbortController`, Path A) and `_keyed` (`consumer key → AbortController`, Path B) — plus `_origFetch`, the unwrapped `fetch` captured at load, used to restore on `destroy()`.
96
101
 
97
102
  ### Parasitic design
98
103
 
@@ -0,0 +1 @@
1
+ (function(){"use strict";function S(t){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return t?window.lnCore._debugIsContained(t):window.lnCore._debugSink!==null;if(t&&t.closest){const e=t.closest("[data-ln-debug]");return!(!e||typeof document<"u"&&e===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const t=console.warn;console.warn=function(...e){if(typeof e[0]=="string"&&(e[0].startsWith("[ln-")||e[0].startsWith("[lnCore"))){let d=null;for(let a=1;a<e.length;a++)if(e[a]&&e[a].nodeType===1){d=e[a];break}if(!S(d))return}t.apply(console,e)}}function p(t,e,o){const d=o||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",e,t,d),t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:d}))}function C(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){C(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}const m={};function k(t,e){m[t]=e}function v(t){return m[t]||{ingress:e=>e,egress:e=>e}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=k,window.lnCore.getDataMapper=v);function L(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,C(function(){new MutationObserver(function(){p(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const y={};function x(t,e){if(!t||typeof e!="object")return;const o=t.toLowerCase().split("-")[0];y[o]=e}function H(t){if(!t)return null;const e=t.toLowerCase().split("-")[0];return y[e]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=x,window.lnCore.getLocaleFallback=H,window.lnCore.ensureLocaleObserver=L);const b={};function O(t,e){b[t]||(b[t]=document.querySelector('[data-ln-template="'+t+'"]'));const o=b[t];return o?o.content.cloneNode(!0):(console.warn("["+(e||"ln-core")+'] Template "'+t+'" not found'),null)}function E(t,e){if(!t||!e)return t;const o=t.querySelectorAll("[data-ln-field]");for(let n=0;n<o.length;n++){const r=o[n],l=r.getAttribute("data-ln-field");e[l]!=null&&(r.textContent=e[l])}const d=t.querySelectorAll("[data-ln-attr]");for(let n=0;n<d.length;n++){const r=d[n],l=r.getAttribute("data-ln-attr").split(",");for(let i=0;i<l.length;i++){const c=l[i].trim().split(":");if(c.length!==2)continue;const u=c[0].trim(),f=c[1].trim();e[f]!=null&&r.setAttribute(u,e[f])}}const a=t.querySelectorAll("[data-ln-show]");for(let n=0;n<a.length;n++){const r=a[n],l=r.getAttribute("data-ln-show");l in e&&r.classList.toggle("hidden",!e[l])}const s=t.querySelectorAll("[data-ln-class]");for(let n=0;n<s.length;n++){const r=s[n],l=r.getAttribute("data-ln-class").split(",");for(let i=0;i<l.length;i++){const c=l[i].trim().split(":");if(c.length!==2)continue;const u=c[0].trim(),f=c[1].trim();f in e&&r.classList.toggle(u,!!e[f])}}return t}function D(t,e){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",t,e??null),t.dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0})));const o=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let d=0;d<o.length;d++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",o[d],e??null),o[d].dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));return t}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(t){if(!(!t.target.matches||!t.target.matches("[data-ln-fillable]")))if(t.detail)E(t.target,t.detail);else{const e=t.target.querySelectorAll("[data-ln-field]");for(let o=0;o<e.length;o++)e[o].textContent=""}})));function _(t,e){if(!t||!e)return t;const o=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;o.nextNode();){const s=o.currentNode;s.textContent.indexOf("{{")!==-1&&(s.textContent=s.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(n,r){return e[r]!==void 0?e[r]:""}))}const d=function(s,n){return e[n]!==void 0?e[n]:""},a=Array.from(t.querySelectorAll("*"));t.nodeType===1&&a.push(t);for(let s=0;s<a.length;s++){const n=a[s],r=n.attributes;for(let l=0;l<r.length;l++){const i=r[l];i.value.indexOf("{{")!==-1&&n.setAttribute(i.name,i.value.replace(/\{\{\s*(\w+)\s*\}\}/g,d))}}return t}function F(t,e,o,d,a,s){const n={};for(let l=0;l<t.children.length;l++){const i=t.children[l],c=i.getAttribute("data-ln-render-key");c&&(n[c]=i)}const r=document.createDocumentFragment();for(let l=0;l<e.length;l++){const i=e[l],c=String(d(i));let u=n[c];if(u)a(u,i,l);else{const f=O(o,s);if(!f||(_(f,i),u=f.firstElementChild,!u))continue;u.setAttribute("data-ln-render-key",c),a(u,i,l)}r.appendChild(u)}t.textContent="",t.appendChild(r)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=_,window.lnCore.fill=E,window.lnCore.lnFill=D,window.lnCore.renderList=F),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function A(t){return(t||"").replace(/^#/,"")}function g(t){const e=t===void 0?location.hash:t,o={},d=A(e);if(!d)return o;const a=d.split("&");for(let s=0;s<a.length;s++){const n=a[s];if(!n)continue;const r=n.indexOf(":"),l=r>-1?n.slice(0,r):n,i=r>-1?n.slice(r+1):"";if(l)try{o[l]=decodeURIComponent(i)}catch{o[l]=i}}return o}function T(t){if(!t)return null;const e=g();return t in e?e[t]:null}function M(t,e){if(!t)return;const o=g();e==null?delete o[t]:o[t]=String(e);const a=Object.keys(o).map(function(s){const n=o[s];return n===""?s:s+":"+encodeURIComponent(n)}).join("&");A(location.hash)!==a&&(location.hash=a)}function q(t){return t.button===1||t.ctrlKey||t.metaKey||t.shiftKey?!1:(t.preventDefault(),!0)}function B(t,e){if(!t||!t.hasAttribute("data-ln-hash"))return null;const o=t.getAttribute("data-ln-hash");if(o&&o.trim()!=="")return o.trim();const d=t.getAttribute("data-ln-sort")||t.getAttribute("data-ln-search-for")||t.getAttribute("data-ln-search")||t.getAttribute("data-ln-filter")||t.id;return d?e?d+"-"+e:d:e||null}function j(t,e){return!e||e==="none"||t===null||t===void 0?null:String(t)+"."+e}function W(t){return!t||typeof t!="string"?null:t.endsWith(".asc")?{fieldOrColumn:t.slice(0,-4),direction:"asc"}:t.endsWith(".desc")?{fieldOrColumn:t.slice(0,-5),direction:"desc"}:null}function U(t,e){return!t||!Array.isArray(e)||e.length===0?null:t+":"+e.map(encodeURIComponent).join(",")}function I(t){if(!t||typeof t!="string")return null;const e=t.indexOf(":");if(e===-1)return null;const o=t.slice(0,e),d=t.slice(e+1),a=d?d.split(",").map(function(s){try{return decodeURIComponent(s)}catch{return s}}).filter(Boolean):[];return{key:o,values:a}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=g,window.lnCore.hashGet=T,window.lnCore.hashSet=M,window.lnCore.hashLinkClick=q,window.lnCore.resolveHashNamespace=B,window.lnCore.hashSortEncode=j,window.lnCore.hashSortDecode=W,window.lnCore.hashFilterEncode=U,window.lnCore.hashFilterDecode=I);function G(t){if(typeof t=="string")return t;if(t&&typeof t=="object"){if(typeof t.href=="string")return t.href;if(typeof t.url=="string")return t.url}return String(t||"")}function K(t,e){return e&&e.method?String(e.method).toUpperCase():t&&typeof t=="object"&&t.method?String(t.method).toUpperCase():"GET"}function N(t,e){return(e||"GET")+" "+(t||"")}function R(t){const e=(t||"").toUpperCase();return e==="GET"||e==="HEAD"}(function(){if(window.lnHttp)return;const t=window.fetch.bind(window),e=new Map,o=new Map;function d(n,r){r=r||{};const l=G(n),i=K(n,r),c=N(l,i);R(i)&&e.has(c)&&(e.get(c).abort(),e.delete(c));const u=new AbortController,f=r.signal;let h=null;f&&(f.aborted?u.abort(f.reason):(h=function(){u.abort(f.reason)},f.addEventListener("abort",h,{once:!0})));const w=Object.assign({},r,{signal:u.signal});return e.set(c,u),t(n,w).finally(function(){f&&h&&f.removeEventListener("abort",h),e.get(c)===u&&e.delete(c)})}d.toString=function(){return"function fetch() { [ln-http wrapped] }"},window.fetch=d;function a(n){if(!n.detail||!n.detail.url)return;const r=n.target,l=(n.detail.method||(n.detail.body?"POST":"GET")).toUpperCase(),i=n.detail.key;i&&o.has(i)&&(o.get(i).abort(),o.delete(i));const c=new AbortController,u=n.detail.signal;let f=null;u&&(u.aborted?c.abort(u.reason):(f=function(){c.abort(u.reason)},u.addEventListener("abort",f,{once:!0}))),i&&o.set(i,c);const h={method:l,signal:c.signal};n.detail.body!==void 0&&(h.body=n.detail.body),window.fetch(n.detail.url,h).then(function(w){u&&f&&u.removeEventListener("abort",f),i&&o.get(i)===c&&o.delete(i),p(r,"ln-http:response",{ok:w.ok,status:w.status,response:w})}).catch(function(w){u&&f&&u.removeEventListener("abort",f),i&&o.get(i)===c&&o.delete(i),!(w&&w.name==="AbortError")&&p(r,"ln-http:error",{ok:!1,status:0,error:w})})}function s(n){const r=n.detail||{};r.all?window.lnHttp.cancelAll():r.key?window.lnHttp.cancelByKey(r.key):r.url&&window.lnHttp.cancel(r.url)}document.addEventListener("ln-http:request",a),document.addEventListener("ln-http:cancel",s),window.lnHttp={cancel:function(n){let r=!1;return e.forEach(function(l,i){i.endsWith(" "+n)&&(l.abort(),e.delete(i),r=!0)}),r},cancelByKey:function(n){return o.has(n)?(o.get(n).abort(),o.delete(n),!0):!1},cancelAll:function(){e.forEach(function(n){n.abort()}),e.clear(),o.forEach(function(n){n.abort()}),o.clear()},get inflight(){const n=[];return e.forEach(function(r,l){const i=l.indexOf(" ");n.push({method:l.slice(0,i),url:l.slice(i+1)})}),o.forEach(function(r,l){n.push({key:l})}),n},destroy:function(){window.lnHttp.cancelAll(),document.removeEventListener("ln-http:request",a),document.removeEventListener("ln-http:cancel",s),window.fetch=t,delete window.lnHttp}}})()})();
@@ -0,0 +1,6 @@
1
+ {
2
+ "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
+ "component": "ln-http",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {}
6
+ }
@@ -0,0 +1,51 @@
1
+ // ═══════════════════════════════════════════════════════════════
2
+ // http-core.js — Pure helpers for ln-http transport supervisor
3
+ // ═══════════════════════════════════════════════════════════════
4
+
5
+ /**
6
+ * Accept string | URL | Request | any → return string URL.
7
+ * @param {string|URL|Request|*} resource
8
+ * @returns {string}
9
+ */
10
+ export function extractUrl(resource) {
11
+ if (typeof resource === 'string') return resource;
12
+ if (resource && typeof resource === 'object') {
13
+ if (typeof resource.href === 'string') return resource.href;
14
+ if (typeof resource.url === 'string') return resource.url;
15
+ }
16
+ return String(resource || '');
17
+ }
18
+
19
+ /**
20
+ * Extract HTTP method from options or Request, default 'GET', uppercased.
21
+ * @param {*} resource
22
+ * @param {Object} [options]
23
+ * @returns {string}
24
+ */
25
+ export function extractMethod(resource, options) {
26
+ if (options && options.method) return String(options.method).toUpperCase();
27
+ if (resource && typeof resource === 'object' && resource.method) {
28
+ return String(resource.method).toUpperCase();
29
+ }
30
+ return 'GET';
31
+ }
32
+
33
+ /**
34
+ * Build deduplication cache key: "METHOD URL".
35
+ * @param {string} url
36
+ * @param {string} method
37
+ * @returns {string}
38
+ */
39
+ export function buildHttpKey(url, method) {
40
+ return (method || 'GET') + ' ' + (url || '');
41
+ }
42
+
43
+ /**
44
+ * Checks if the HTTP method is eligible for transparent deduplication (GET, HEAD).
45
+ * @param {string} method
46
+ * @returns {boolean}
47
+ */
48
+ export function isIdempotentMethod(method) {
49
+ const m = (method || '').toUpperCase();
50
+ return m === 'GET' || m === 'HEAD';
51
+ }
@@ -39,17 +39,19 @@
39
39
  // and _keyed (Path B, key-keyed). Aborts from either side are
40
40
  // idempotent and cooperate cleanly.
41
41
  //
42
- // Public API (window.lnHttp):
43
- // .cancel(url) — abort any Path A in-flight whose URL matches.
44
- // .cancelByKey(key) — abort the Path B in-flight with this key.
45
- // .cancelAll() — abort every in-flight (both paths).
46
- // .inflight — getter; returns Array<{ url, method, key? }>
47
- // covering both paths, for debugging.
48
- // .destroy() — restore the original fetch, remove the
49
- // document listener, clear both queues.
50
- // Used in dev hot-reload and tests.
42
+ // Public API & Event Control:
43
+ // ln-http:cancel — event on document { key?, url?, all? } to abort in-flight requests.
44
+ // window.lnHttp.cancel(url) — abort any Path A in-flight whose URL matches.
45
+ // window.lnHttp.cancelByKey(key) — abort the Path B in-flight with this key.
46
+ // window.lnHttp.cancelAll() — abort every in-flight (both paths).
47
+ // window.lnHttp.inflight — getter; returns Array<{ url, method, key? }>
48
+ // covering both paths, for debugging.
49
+ // window.lnHttp.destroy() — restore original fetch, remove document
50
+ // listeners, clear all in-flight queues.
51
+ // Used in dev hot-reload and tests.
51
52
 
52
53
  import { dispatch } from '../../ln-core';
54
+ import { extractUrl, extractMethod, buildHttpKey, isIdempotentMethod } from './http-core';
53
55
 
54
56
  (function () {
55
57
  if (window.lnHttp) return; // double-load guard
@@ -58,37 +60,17 @@ import { dispatch } from '../../ln-core';
58
60
  const _inflight = new Map(); // "METHOD URL" → AbortController (Path A)
59
61
  const _keyed = new Map(); // consumer key → AbortController (Path B)
60
62
 
61
- // ─── helpers ───────────────────────────────────────────────────
62
-
63
- // Accept string | URL | Request → return absolute URL string.
64
- function _extractUrl(resource) {
65
- if (typeof resource === 'string') return resource;
66
- if (resource instanceof URL) return resource.href;
67
- if (resource instanceof Request) return resource.url;
68
- return String(resource); // last-resort coercion
69
- }
70
-
71
- // Extract method from options OR Request, default GET, uppercased.
72
- function _extractMethod(resource, options) {
73
- if (options && options.method) return String(options.method).toUpperCase();
74
- if (resource instanceof Request) return resource.method.toUpperCase();
75
- return 'GET';
76
- }
77
-
78
- function _key(url, method) { return method + ' ' + url; }
79
- function _isIdempotent(method) { return method === 'GET' || method === 'HEAD'; }
80
-
81
63
  // ─── Path A: window.fetch wrapper ─────────────────────────────
82
64
 
83
65
  function _wrappedFetch(resource, options) {
84
66
  options = options || {};
85
67
 
86
- const url = _extractUrl(resource);
87
- const method = _extractMethod(resource, options);
88
- const key = _key(url, method);
68
+ const url = extractUrl(resource);
69
+ const method = extractMethod(resource, options);
70
+ const key = buildHttpKey(url, method);
89
71
 
90
72
  // Idempotent dedup — abort previous in-flight on same key.
91
- if (_isIdempotent(method) && _inflight.has(key)) {
73
+ if (isIdempotentMethod(method) && _inflight.has(key)) {
92
74
  _inflight.get(key).abort();
93
75
  _inflight.delete(key);
94
76
  }
@@ -189,7 +171,19 @@ import { dispatch } from '../../ln-core';
189
171
  });
190
172
  }
191
173
 
174
+ function _onCancel(e) {
175
+ const detail = e.detail || {};
176
+ if (detail.all) {
177
+ window.lnHttp.cancelAll();
178
+ } else if (detail.key) {
179
+ window.lnHttp.cancelByKey(detail.key);
180
+ } else if (detail.url) {
181
+ window.lnHttp.cancel(detail.url);
182
+ }
183
+ }
184
+
192
185
  document.addEventListener('ln-http:request', _onRequest);
186
+ document.addEventListener('ln-http:cancel', _onCancel);
193
187
 
194
188
  // ─── Public API ───────────────────────────────────────────────
195
189
 
@@ -231,6 +225,7 @@ import { dispatch } from '../../ln-core';
231
225
  destroy: function () {
232
226
  window.lnHttp.cancelAll();
233
227
  document.removeEventListener('ln-http:request', _onRequest);
228
+ document.removeEventListener('ln-http:cancel', _onCancel);
234
229
  window.fetch = _origFetch;
235
230
  delete window.lnHttp;
236
231
  }
@@ -1,5 +1,7 @@
1
1
  # ln-icon
2
2
 
3
+ > Applied to standard SVG references (`<svg class="ln-icon"><use href="#ln-icon-home"></use></svg>`). It monitors the DOM via `MutationObserver` for `<use>` tags, dynamically fetches missing SVG paths from a CDN or custom asset endpoint, caches them in `localStorage` under the `lni:` prefix, and injects them into a single hidden `#ln-icon-sprite` symbol sheet at the start of `<body>`. It executes zero JS rendering after hydration, leaving styling and color inheritance entirely to CSS `currentColor`.
4
+
3
5
  A zero-dependency, local-first **On-Demand SVG Sprite Generator** that dynamically monitors, fetches, and compiles SVG icons at runtime.
4
6
 
5
7
  Instead of bundling thousands of heavy vector paths or requiring complex manual build steps, it intercepts standard DOM `<use>` tags, fetches vector definitions from a remote CDN, caches them in `localStorage`, and injects them into a single unified hidden SVG sprite sheet.
@@ -8,10 +10,10 @@ Instead of bundling thousands of heavy vector paths or requiring complex manual
8
10
 
9
11
  ## 🧭 Philosophy & Architecture
10
12
 
11
- 1. **Declarative On-Demand Rendering:** Icons are declared directly in HTML. The component monitors the DOM via `MutationObserver` for `<use>` references with `#ln-` and `#lnc-` prefixes. It only fetches and compiles icons that are actively present on the page.
13
+ 1. **Declarative On-Demand Rendering:** Icons are declared directly in HTML. The component monitors the DOM via `MutationObserver` for `<use>` references with `#ln-icon-` and `#ln-icon-custom-` prefixes. It only fetches and compiles icons that are actively present on the page.
12
14
  2. **Dual-Prefix Routing:**
13
- - **`#ln-{name}`**: Automatically routes to the [Tabler Icons](https://tabler.io/icons) library fetched from a public CDN. No configuration required.
14
- - **`#lnc-{name}`**: Routes to a custom corporate CDN defined via global window settings.
15
+ - **`#ln-icon-{name}`**: Automatically routes to the [Tabler Icons](https://tabler.io/icons) library fetched from a public CDN. No configuration required.
16
+ - **`#ln-icon-custom-{name}`**: Routes to a custom corporate CDN defined via global window settings.
15
17
  3. **Local Caching Layer:** Fetched SVG path structures are instantly cached in `localStorage` under `lni:{id}`. Subsequent visits render icons instantly with zero network roundtrips.
16
18
 
17
19
  ---
@@ -62,7 +64,7 @@ Configure these properties before script initialization:
62
64
  | Variable | Default | Description |
63
65
  | :--- | :--- | :--- |
64
66
  | `LN_ICON_CDN` | `https://cdn.jsdelivr.net/npm/@tabler/icons@3.31.0/icons/outline` | Base CDN URL for Tabler Icons. |
65
- | `LN_ICON_CUSTOM_CDN` | `null` | Base CDN URL for custom `#lnc-` prefixed SVG resources. |
67
+ | `LN_ICON_CUSTOM_CDN` | `null` | Base CDN URL for custom `#ln-icon-custom-` prefixed SVG resources. |
66
68
 
67
69
  ---
68
70
 
@@ -84,14 +86,14 @@ useElement.setAttribute('href', '#ln-icon-check');
84
86
  ## ⚠️ Common Pitfalls
85
87
 
86
88
  - **Forgetting `ln-icon` Class:** Standard SVGs default to `100%` width/height. Failing to include the `ln-icon` class will cause the icon to blow up to full viewport size.
87
- - **Incorrect Prefix Configuration:** Forgetting to define `window.LN_ICON_CUSTOM_CDN` when using `#lnc-` will cause the loader to fail silently with undefined endpoint errors.
89
+ - **Incorrect Prefix Configuration:** Forgetting to define `window.LN_ICON_CUSTOM_CDN` when using `#ln-icon-custom-` will cause the loader to fail silently with undefined endpoint errors.
88
90
  - **Omitting `aria-hidden="true"`:** Screen readers attempt to read SVG nodes. Always decorate decorative icons with `aria-hidden="true"`, or include an `aria-label` on their parent button.
89
91
 
90
92
  ---
91
93
 
92
94
  ## 🔧 Internals
93
95
 
94
- Source: `js/ln-icon/ln-icon.js`. Routing is purely prefix-based — no config lists, no runtime mapping. Symbol IDs in the generated sprite mirror the full `href` value minus `#` (e.g. `#ln-icon-custom-file-pdf` → `<symbol id="ln-icon-custom-file-pdf">`).
96
+ Source: `components/ln-icon/ln-icon.js`. Routing is purely prefix-based — no config lists, no runtime mapping. Symbol IDs in the generated sprite mirror the full `href` value minus `#` (e.g. `#ln-icon-custom-file-pdf` → `<symbol id="ln-icon-custom-file-pdf">`).
95
97
 
96
98
  ### Scan
97
99
 
@@ -103,7 +105,7 @@ Two in-memory `Set`s (`loaded`, `pending`), keyed by full `href`, prevent duplic
103
105
 
104
106
  ### Fetch + symbol injection
105
107
 
106
- On a cache miss: resolve the CDN URL (`lnc-` → `LN_ICON_CUSTOM_CDN`, `ln-` → `LN_ICON_CDN`; `lnc-` bails silently if the custom CDN isn't configured), `fetch()`, then parse the raw SVG string — extract `viewBox` (fallback `'0 0 24 24'`), the inner content between the `<svg>` tags, and root presentation attributes (`fill`, `stroke`, `stroke-width`, `stroke-linecap`, `stroke-linejoin`). A `<symbol>` is built from these and appended to the sprite's `<defs>`; the raw SVG is also written back to `localStorage`. Errors delete the `pending` entry silently — the icon stays blank, no retry.
108
+ On a cache miss: resolve the CDN URL (`#ln-icon-custom-` → `LN_ICON_CUSTOM_CDN`, `#ln-icon-` → `LN_ICON_CDN`; `#ln-icon-custom-` bails silently if the custom CDN isn't configured), `fetch()`, then parse the raw SVG string — extract `viewBox` (fallback `'0 0 24 24'`), the inner content between the `<svg>` tags, and root presentation attributes (`fill`, `stroke`, `stroke-width`, `stroke-linecap`, `stroke-linejoin`). A `<symbol>` is built from these and appended to the sprite's `<defs>`; the raw SVG is also written back to `localStorage`. Errors delete the `pending` entry silently — the icon stays blank, no retry.
107
109
 
108
110
  ### Sprite element
109
111
 
@@ -123,8 +125,13 @@ Tabler SVGs use `stroke="currentColor"`, so rendered icons inherit the nearest a
123
125
 
124
126
  ### Cross-component icon injection
125
127
 
126
- Several components inject `<use>` elements dynamically rather than authoring them in HTML: `ln-toast` (`#ln-icon-x` dismiss button), `ln-upload` (`#lnc-file[-pdf|-doc|-epub]` per item, `#ln-icon-x` remove button), `ln-confirm` (swaps an existing `<use href>` to `#ln-icon-check` during confirm, restores on reset). All route through the same scan/fetch pipeline — no special-casing needed since detection is attribute-based, not author-time. `ln-sort` is the opposite case: its three trigger icons are statically authored in HTML (see `js/ln-sort/README.md`), never injected.
128
+ Several components inject `<use>` elements dynamically rather than authoring them in HTML: `ln-toast` (`#ln-icon-x` dismiss button), `ln-upload` (`#ln-icon-custom-file[-pdf|-doc|-epub]` per item, `#ln-icon-x` remove button), `ln-confirm` (swaps an existing `<use href>` to `#ln-icon-check` during confirm, restores on reset). All route through the same scan/fetch pipeline — no special-casing needed since detection is attribute-based, not author-time. `ln-sort` is the opposite case: its three trigger icons are statically authored in HTML (see `components/ln-sort/README.md`), never injected.
127
129
 
128
130
  ### Offline behavior
129
131
 
130
- Uncached icons fail silently (`.catch()`) if the page loads offline — the icon stays blank. For offline-first deployments, self-host the SVG set and point `window.LN_ICON_CDN` at a local server before the library initializes; `vite.config.js` copies `js/ln-icon/icons/*.svg` → `dist/icons/` at build for this purpose.
132
+ Uncached icons fail silently (`.catch()`) if the page loads offline — the icon stays blank. For offline-first deployments, self-host the SVG set and point `window.LN_ICON_CDN` at a local server before the library initializes; `vite.config.js` copies `components/ln-icon/icons/*.svg` → `dist/icons/` at build for this purpose.
133
+
134
+ ### Security & Trust Boundary
135
+
136
+ `ln-icon` dynamically fetches SVG markup from CDNs and injects it via `innerHTML` into `<symbol>` definitions without client-side sanitization. For threat modeling, `localStorage` persistence implications, strict CSP directives (`connect-src`), and self-hosting / offline SVG sprite guidance, see the [Dynamic SVG Icon Trust Boundary](../../docs/architecture/security.md#7-dynamic-svg-icon-trust-boundary-ln-icons) documentation in `docs/architecture/security.md`.
137
+
@@ -0,0 +1,6 @@
1
+ {
2
+ "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
+ "component": "ln-icon",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {}
6
+ }