@livenetworks/ashlar 1.6.1 → 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 (559) 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/{js → components}/ln-confirm/README.md +69 -18
  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 -171
  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/_ln-confirm.scss +0 -24
  414. package/js/ln-confirm/ln-confirm.js +0 -1
  415. package/js/ln-core/crypto.js +0 -83
  416. package/js/ln-core/helpers.js +0 -788
  417. package/js/ln-core/index.js +0 -7
  418. package/js/ln-core/persist.js +0 -97
  419. package/js/ln-couchdb-connector/ln-couchdb-connector.js +0 -1
  420. package/js/ln-data-coordinator/ln-data-coordinator.js +0 -1
  421. package/js/ln-data-coordinator/src/data-read-policy.js +0 -18
  422. package/js/ln-data-store/ln-data-store.js +0 -1
  423. package/js/ln-date/ln-date.js +0 -1
  424. package/js/ln-date/src/ln-date.js +0 -603
  425. package/js/ln-debug/README.md +0 -58
  426. package/js/ln-debug/ln-debug.js +0 -1
  427. package/js/ln-debug/src/ln-debug.js +0 -19
  428. package/js/ln-dropdown/ln-dropdown.js +0 -1
  429. package/js/ln-dropdown/ln-dropdown.scss +0 -15
  430. package/js/ln-editor/ln-editor.js +0 -1
  431. package/js/ln-editor/ln-editor.scss +0 -43
  432. package/js/ln-external-links/ln-external-links.js +0 -1
  433. package/js/ln-fill/ln-fill.js +0 -1
  434. package/js/ln-filter/README.md +0 -189
  435. package/js/ln-filter/ln-filter-dev.scss +0 -8
  436. package/js/ln-filter/ln-filter.js +0 -1
  437. package/js/ln-filter/src/ln-filter.js +0 -419
  438. package/js/ln-form/ln-form.js +0 -1
  439. package/js/ln-http/ln-http.js +0 -1
  440. package/js/ln-link/ln-link.js +0 -1
  441. package/js/ln-list/README.md +0 -129
  442. package/js/ln-list/ln-list.js +0 -1
  443. package/js/ln-list/ln-list.scss +0 -28
  444. package/js/ln-modal/ln-modal-dev.scss +0 -13
  445. package/js/ln-modal/ln-modal.js +0 -1
  446. package/js/ln-modal/ln-modal.scss +0 -35
  447. package/js/ln-modal-coordinator/README.md +0 -99
  448. package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +0 -8
  449. package/js/ln-modal-coordinator/ln-modal-coordinator.js +0 -1
  450. package/js/ln-nav/ln-nav.js +0 -1
  451. package/js/ln-number/README.md +0 -149
  452. package/js/ln-number/ln-number.js +0 -1
  453. package/js/ln-number/src/ln-number.js +0 -443
  454. package/js/ln-options/ln-options.js +0 -1
  455. package/js/ln-popover/ln-popover.js +0 -1
  456. package/js/ln-progress/ln-progress.js +0 -1
  457. package/js/ln-progress/src/ln-progress.js +0 -98
  458. package/js/ln-router/ln-router.js +0 -1
  459. package/js/ln-search/README.md +0 -158
  460. package/js/ln-search/ln-search-dev.scss +0 -8
  461. package/js/ln-search/ln-search.js +0 -1
  462. package/js/ln-search/src/ln-search.js +0 -117
  463. package/js/ln-slug/ln-slug.js +0 -1
  464. package/js/ln-sort/ln-sort.js +0 -1
  465. package/js/ln-sort/src/ln-sort.js +0 -178
  466. package/js/ln-sortable/ln-sortable.js +0 -1
  467. package/js/ln-stat/ln-stat.js +0 -1
  468. package/js/ln-table/ln-table.js +0 -1
  469. package/js/ln-table/ln-table.scss +0 -11
  470. package/js/ln-table-coordinator/README.md +0 -73
  471. package/js/ln-table-coordinator/ln-table-coordinator.js +0 -1
  472. package/js/ln-table-coordinator/src/ln-table-coordinator.js +0 -160
  473. package/js/ln-tabs/ln-tabs-dev.scss +0 -18
  474. package/js/ln-tabs/ln-tabs.js +0 -1
  475. package/js/ln-tabs/src/ln-tabs.js +0 -188
  476. package/js/ln-time/ln-time.js +0 -1
  477. package/js/ln-toast/ln-toast.js +0 -1
  478. package/js/ln-toggle/ln-toggle.js +0 -1
  479. package/js/ln-tooltip/ln-tooltip.js +0 -1
  480. package/js/ln-tooltip/ln-tooltip.scss +0 -9
  481. package/js/ln-translations/ln-translations.js +0 -1
  482. package/js/ln-upload/README.md +0 -255
  483. package/js/ln-upload/ln-upload.js +0 -1
  484. package/js/ln-upload/ln-upload.scss +0 -20
  485. package/js/ln-upload/src/ln-upload.js +0 -423
  486. package/js/ln-validate/ln-validate.js +0 -1
  487. package/scss/components/_button.scss +0 -20
  488. package/scss/components/_confirm.scss +0 -5
  489. package/scss/components/_form.scss +0 -107
  490. package/scss/components/_status-badge.scss +0 -4
  491. package/scss/components/_toast.scss +0 -68
  492. package/scss/components/_upload.scss +0 -51
  493. package/scss/config/_theme.scss +0 -216
  494. package/scss/config/mixins/_btn.scss +0 -179
  495. package/scss/config/mixins/_confirm.scss +0 -31
  496. package/scss/config/mixins/_dropdown.scss +0 -141
  497. package/scss/config/mixins/_popover.scss +0 -64
  498. package/scss/ln-ashlar-dev.scss +0 -39
  499. /package/{js → components}/ln-core/positioning.js +0 -0
  500. /package/{js → components}/ln-core/reactive.js +0 -0
  501. /package/{js → components}/ln-filter/ln-filter.scss +0 -0
  502. /package/{js → components}/ln-icon/ln-icon.js +0 -0
  503. /package/{js → components}/ln-icon/src/ln-icon.js +0 -0
  504. /package/{js → components}/ln-search/ln-search.scss +0 -0
  505. /package/{js → components}/ln-toast/template.html +0 -0
  506. /package/{scss → theme}/components/_accordion.scss +0 -0
  507. /package/{scss → theme}/components/_ajax.scss +0 -0
  508. /package/{scss → theme}/components/_alert.scss +0 -0
  509. /package/{scss → theme}/components/_avatar.scss +0 -0
  510. /package/{scss → theme}/components/_breadcrumbs.scss +0 -0
  511. /package/{scss → theme}/components/_card.scss +0 -0
  512. /package/{scss → theme}/components/_chip.scss +0 -0
  513. /package/{scss → theme}/components/_circular-progress.scss +0 -0
  514. /package/{scss → theme}/components/_editor.scss +0 -0
  515. /package/{scss → theme}/components/_empty-state.scss +0 -0
  516. /package/{scss → theme}/components/_filter.scss +0 -0
  517. /package/{scss → theme}/components/_link.scss +0 -0
  518. /package/{scss → theme}/components/_loader.scss +0 -0
  519. /package/{scss → theme}/components/_modal.scss +0 -0
  520. /package/{scss → theme}/components/_nav.scss +0 -0
  521. /package/{scss → theme}/components/_page-header.scss +0 -0
  522. /package/{scss → theme}/components/_popover.scss +0 -0
  523. /package/{scss → theme}/components/_progress.scss +0 -0
  524. /package/{scss → theme}/components/_prose.scss +0 -0
  525. /package/{scss → theme}/components/_scrollbar.scss +0 -0
  526. /package/{scss → theme}/components/_sections.scss +0 -0
  527. /package/{scss → theme}/components/_sidebar.scss +0 -0
  528. /package/{scss → theme}/components/_stat-card.scss +0 -0
  529. /package/{scss → theme}/components/_stepper.scss +0 -0
  530. /package/{scss → theme}/components/_timeline.scss +0 -0
  531. /package/{scss → theme}/components/_tooltip.scss +0 -0
  532. /package/{scss → theme}/components/_translations.scss +0 -0
  533. /package/{scss → theme}/config/_icons.scss +0 -0
  534. /package/{scss → theme}/config/_mixins.scss +0 -0
  535. /package/{scss → theme}/config/mixins/_ajax.scss +0 -0
  536. /package/{scss → theme}/config/mixins/_avatar.scss +0 -0
  537. /package/{scss → theme}/config/mixins/_breadcrumbs.scss +0 -0
  538. /package/{scss → theme}/config/mixins/_breakpoints.scss +0 -0
  539. /package/{scss → theme}/config/mixins/_chip.scss +0 -0
  540. /package/{scss → theme}/config/mixins/_circular-progress.scss +0 -0
  541. /package/{scss → theme}/config/mixins/_display.scss +0 -0
  542. /package/{scss → theme}/config/mixins/_empty-state.scss +0 -0
  543. /package/{scss → theme}/config/mixins/_focus.scss +0 -0
  544. /package/{scss → theme}/config/mixins/_interaction.scss +0 -0
  545. /package/{scss → theme}/config/mixins/_kbd.scss +0 -0
  546. /package/{scss → theme}/config/mixins/_link.scss +0 -0
  547. /package/{scss → theme}/config/mixins/_page-header.scss +0 -0
  548. /package/{scss → theme}/config/mixins/_position.scss +0 -0
  549. /package/{scss → theme}/config/mixins/_progress.scss +0 -0
  550. /package/{scss → theme}/config/mixins/_prose.scss +0 -0
  551. /package/{scss → theme}/config/mixins/_shadows.scss +0 -0
  552. /package/{scss → theme}/config/mixins/_sizing.scss +0 -0
  553. /package/{scss → theme}/config/mixins/_spacing.scss +0 -0
  554. /package/{scss → theme}/config/mixins/_stat-card.scss +0 -0
  555. /package/{scss → theme}/config/mixins/_stepper.scss +0 -0
  556. /package/{scss → theme}/config/mixins/_timeline.scss +0 -0
  557. /package/{scss → theme}/config/mixins/_transitions.scss +0 -0
  558. /package/{scss → theme}/config/mixins/_translations.scss +0 -0
  559. /package/{scss → theme}/tabler-icons.txt +0 -0
@@ -1,21 +1,63 @@
1
- import { registerComponent, dispatch, dispatchCancelable } from '../../ln-core';
2
- import { persistGet, persistSet } from '../../ln-core';
1
+ import { dispatch, dispatchCancelable, isTargetDisabled, registerComponent, shouldIgnoreClick } from '../../ln-core';
3
2
 
4
3
  (function () {
5
4
  const DOM_SELECTOR = 'data-ln-toggle';
6
5
  const DOM_ATTRIBUTE = 'lnToggle';
6
+ const TRIGGER_ATTRIBUTE = 'data-ln-toggle-for';
7
+ const ACTION_ATTRIBUTE = 'data-ln-toggle-action';
7
8
 
8
9
  if (window[DOM_ATTRIBUTE] !== undefined) return;
9
10
 
11
+ const ATTRIBUTES = {
12
+ 'data-ln-toggle': { effect: _syncAttribute, type: 'enum', values: ['open', 'close'], fallback: 'close', description: 'Visibility state of toggleable element' },
13
+ 'data-ln-toggle-for': { type: 'string', description: 'Target element ID to toggle on trigger click' },
14
+ 'data-ln-toggle-action': { type: 'enum', values: ['open', 'close', 'toggle'], fallback: 'toggle', description: 'Action performed on target element when trigger is clicked' }
15
+ };
16
+
17
+ const instances = new Set();
18
+ let clickListener = null;
19
+
20
+ function _getNextState(current, action) {
21
+ if (action === 'open') return 'open';
22
+ if (action === 'close') return 'close';
23
+ return current === 'open' ? 'close' : 'open';
24
+ }
10
25
 
26
+ function _ensureClickListener() {
27
+ if (clickListener) return;
28
+ clickListener = function (e) {
29
+ if (shouldIgnoreClick(e)) return;
11
30
 
31
+ const trigger = e.target.closest('[' + TRIGGER_ATTRIBUTE + ']');
32
+ if (!trigger || isTargetDisabled(trigger)) return;
33
+
34
+ const targetId = trigger.getAttribute(TRIGGER_ATTRIBUTE);
35
+ if (!targetId) return;
36
+
37
+ const target = document.getElementById(targetId);
38
+ if (!target || !target[DOM_ATTRIBUTE]) return;
39
+
40
+ e.preventDefault();
41
+ const action = trigger.getAttribute(ACTION_ATTRIBUTE) || 'toggle';
42
+ const current = target.getAttribute(DOM_SELECTOR);
43
+ target.setAttribute(DOM_SELECTOR, _getNextState(current, action));
44
+ };
45
+ document.addEventListener('click', clickListener);
46
+ }
47
+
48
+ function _maybeRemoveClickListener() {
49
+ if (instances.size > 0 || !clickListener) return;
50
+ document.removeEventListener('click', clickListener);
51
+ clickListener = null;
52
+ }
12
53
 
13
54
  function _syncTriggerAria(panelEl, isOpen) {
55
+ if (!panelEl || !panelEl.id) return;
14
56
  const triggers = document.querySelectorAll(
15
- '[data-ln-toggle-for="' + panelEl.id + '"]'
57
+ '[' + TRIGGER_ATTRIBUTE + '="' + panelEl.id + '"]'
16
58
  );
17
- for (const trigger of triggers) {
18
- trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
59
+ for (let i = 0; i < triggers.length; i++) {
60
+ triggers[i].setAttribute('aria-expanded', isOpen ? 'true' : 'false');
19
61
  }
20
62
  }
21
63
 
@@ -39,22 +81,22 @@ import { persistGet, persistSet } from '../../ln-core';
39
81
  this.dom.addEventListener('ln-toggle:request-close', this._onRequestClose);
40
82
  this.dom.addEventListener('ln-toggle:request-toggle', this._onRequestToggle);
41
83
 
42
- // ─── Restore persisted state ──────────────────────────────
43
- if (dom.hasAttribute('data-ln-persist')) {
44
- const saved = persistGet('toggle', dom);
45
- if (saved !== null) {
46
- dom.setAttribute(DOM_SELECTOR, saved);
47
- }
48
- }
49
-
84
+ // Committed state tracker — acts as the read-only query surface (el.lnToggle.isOpen),
85
+ // prevents re-entrant attribute sync loops, and ensures cancelable `before-*` event
86
+ // handlers observe the pre-transition state.
50
87
  this.isOpen = dom.getAttribute(DOM_SELECTOR) === 'open';
51
88
 
89
+ // Visual convenience class synchronized for CSS transitions (@mixin collapsible)
90
+ // and consumer components (e.g., ln-dropdown menu visibility).
52
91
  if (this.isOpen) {
53
92
  dom.classList.add('open');
54
93
  }
55
94
 
56
95
  _syncTriggerAria(dom, this.isOpen);
57
96
 
97
+ instances.add(this);
98
+ _ensureClickListener();
99
+
58
100
  return this;
59
101
  }
60
102
 
@@ -68,7 +110,7 @@ import { persistGet, persistSet } from '../../ln-core';
68
110
 
69
111
  _component.prototype.toggle = function () {
70
112
  const current = this.dom.getAttribute(DOM_SELECTOR);
71
- this.dom.setAttribute(DOM_SELECTOR, current === 'open' ? 'close' : 'open');
113
+ this.dom.setAttribute(DOM_SELECTOR, _getNextState(current, 'toggle'));
72
114
  };
73
115
 
74
116
  _component.prototype.destroy = function () {
@@ -76,8 +118,10 @@ import { persistGet, persistSet } from '../../ln-core';
76
118
  this.dom.removeEventListener('ln-toggle:request-open', this._onRequestOpen);
77
119
  this.dom.removeEventListener('ln-toggle:request-close', this._onRequestClose);
78
120
  this.dom.removeEventListener('ln-toggle:request-toggle', this._onRequestToggle);
79
- dispatch(this.dom, 'ln-toggle:destroyed', { target: this.dom });
121
+
122
+ instances.delete(this);
80
123
  delete this.dom[DOM_ATTRIBUTE];
124
+ _maybeRemoveClickListener();
81
125
  };
82
126
 
83
127
  // ─── Attribute Sync ────────────────────────────────────────
@@ -101,9 +145,6 @@ import { persistGet, persistSet } from '../../ln-core';
101
145
  el.classList.add('open');
102
146
  _syncTriggerAria(el, true);
103
147
  dispatch(el, 'ln-toggle:open', { target: el });
104
- if (el.hasAttribute('data-ln-persist')) {
105
- persistSet('toggle', el, 'open');
106
- }
107
148
  } else {
108
149
  const before = dispatchCancelable(el, 'ln-toggle:before-close', { target: el });
109
150
  if (before.defaultPrevented) {
@@ -114,39 +155,13 @@ import { persistGet, persistSet } from '../../ln-core';
114
155
  el.classList.remove('open');
115
156
  _syncTriggerAria(el, false);
116
157
  dispatch(el, 'ln-toggle:close', { target: el });
117
- if (el.hasAttribute('data-ln-persist')) {
118
- persistSet('toggle', el, 'close');
119
- }
120
158
  }
121
159
  }
122
160
 
123
- // ─── Event Delegation ──────────────────────────────────────
124
-
125
- document.addEventListener('click', function (e) {
126
- if (e.ctrlKey || e.metaKey || e.button === 1) return;
127
-
128
- const trigger = e.target.closest('[data-ln-toggle-for]');
129
- if (trigger) {
130
- const targetId = trigger.getAttribute('data-ln-toggle-for');
131
- const target = document.getElementById(targetId);
132
- if (target && target[DOM_ATTRIBUTE]) {
133
- e.preventDefault();
134
- const action = trigger.getAttribute('data-ln-toggle-action') || 'toggle';
135
- if (action === 'open') {
136
- target.setAttribute(DOM_SELECTOR, 'open');
137
- } else if (action === 'close') {
138
- target.setAttribute(DOM_SELECTOR, 'close');
139
- } else if (action === 'toggle') {
140
- const current = target.getAttribute(DOM_SELECTOR);
141
- target.setAttribute(DOM_SELECTOR, current === 'open' ? 'close' : 'open');
142
- }
143
- }
144
- }
145
- });
146
-
147
161
  // ─── Init ──────────────────────────────────────────────────
148
162
 
149
163
  registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-toggle', {
150
- onAttributeChange: _syncAttribute
164
+ attributes: ATTRIBUTES,
165
+ persist: { attr: DOM_SELECTOR, hashActive: null }
151
166
  });
152
167
  })();
@@ -1,8 +1,8 @@
1
1
  # ln-tooltip
2
2
 
3
- A zero-dependency, progressively enhanced **Dual-Layer Tooltip Primitive** that displays lightweight contextual descriptions on hover and focus.
4
-
5
- It supports two levels of execution: a **pure CSS baseline** (zero JS footprint, utilizing pseudo-elements) and a **JS progressive enhancement layer** (top-layer promoted via the native Popover API to escape parent clipping, viewport-aware auto-flipping, and automated `aria-describedby` wiring).
3
+ > Applied to any element via `data-ln-tooltip="text"`. Pure CSS displays a `::after` pseudo-element on `:hover` and `:focus`.
4
+ > With `data-ln-tooltip-enhance`, `mouseenter`/`focus` injects a `<div role="tooltip">` into a single shared `<div id="ln-tooltip-portal">` in `<body>`,
5
+ > calls `portal.showPopover()`, and calculates inline `top`/`left`; `mouseleave`/`blur` calls `portal.hidePopover()`.
6
6
 
7
7
  ---
8
8
 
@@ -61,11 +61,11 @@ Add `data-ln-tooltip-enhance` to activate advanced positioning and accessibility
61
61
 
62
62
  ## 🔧 Internals
63
63
 
64
- Source: `js/ln-tooltip/ln-tooltip.js`. Tooltips are mechanically inert from the data layer's perspective — no coordinator wiring, the only event dispatched is `ln-tooltip:destroyed`.
64
+ Source: `components/ln-tooltip/ln-tooltip.js`. Tooltips are mechanically inert from the data layer's perspective — no coordinator wiring, no events dispatched.
65
65
 
66
66
  ### Two render strategies, one attribute
67
67
 
68
- `scss/components/_tooltip.scss` applies `@mixin tooltip` to every `[data-ln-tooltip]` element unconditionally — a `::after` pseudo-element reading `attr(data-ln-tooltip)` (falling back to `attr(title)`), toggled on `:hover`/`:focus-visible`. Zero JS. The JS layer attaches only when an element matches `[data-ln-tooltip-enhance]` (explicit opt-in) or `[data-ln-tooltip][title]` (auto-enhance, so the CSS baseline can't otherwise stop the native `title` tooltip from leaking through). For either match, co-located CSS sets `content: none` on the `::after`, suppressing the baseline so only one bubble ever renders:
68
+ `theme/components/_tooltip.scss` applies `@mixin tooltip` to every `[data-ln-tooltip]` element unconditionally — a `::after` pseudo-element reading `attr(data-ln-tooltip)` (falling back to `attr(title)`), toggled on `:hover`/`:focus-visible`. Zero JS. The JS layer attaches only when an element matches `[data-ln-tooltip-enhance]` (explicit opt-in) or `[data-ln-tooltip][title]` (auto-enhance, so the CSS baseline can't otherwise stop the native `title` tooltip from leaking through). For either match, co-located CSS sets `content: none` on the `::after`, suppressing the baseline so only one bubble ever renders:
69
69
 
70
70
  ```scss
71
71
  [data-ln-tooltip][data-ln-tooltip-enhance]::after,
@@ -92,4 +92,4 @@ Module-level `activeTrigger`/`activeTooltipNode` enforce a hard single-tooltip i
92
92
 
93
93
  `registerComponent` wires a `MutationObserver` watching for `[data-ln-tooltip-enhance], [data-ln-tooltip][title]`. On init, four listeners attach to the trigger: `mouseenter`/`focus` → `_show(el)`; `mouseleave`/`blur` → `_hide()` guarded by `activeTrigger === el` (so one trigger can't hide another's tooltip). `focus`/`blur` use the capture phase so events from focusable descendants reach the trigger reliably.
94
94
 
95
- The component never auto-destroys on element removal — listeners are garbage-collected with the element. `destroy()` exists for the rarer case of unwiring without removing the element: removes all four listeners, `_hide()`s if this trigger is currently active, deletes the instance properties, dispatches `ln-tooltip:destroyed`.
95
+ The component never auto-destroys on element removal — listeners are garbage-collected with the element. `destroy()` exists for the rarer case of unwiring without removing the element: removes all four listeners, `_hide()`s if this trigger is currently active, deletes the instance properties.
@@ -1,9 +1,9 @@
1
- @use '../../scss/config/mixins' as *;
2
-
3
- [data-ln-debug] {
4
- // 1. Empty Tooltip: accessibility warning
5
- // Flags a [data-ln-tooltip] declared empty with no native title fallback (renders an empty bubble); :not([title]) honors the documented title fallback
6
- [data-ln-tooltip=""]:not([title]) {
7
- @include dev-inline-error("data-ln-tooltip is empty and has no title fallback");
8
- }
9
- }
1
+ @use '../../theme/config/mixins' as *;
2
+
3
+ [data-ln-debug] {
4
+ // 1. Empty Tooltip: accessibility warning
5
+ // Flags a [data-ln-tooltip] declared empty with no native title fallback (renders an empty bubble); :not([title]) honors the documented title fallback
6
+ [data-ln-tooltip=""]:not([title]) {
7
+ @include dev-inline-error("data-ln-tooltip is empty and has no title fallback");
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ (function(){"use strict";function I(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function L(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let o=null;for(let i=1;i<t.length;i++)if(t[i]&&t[i].nodeType===1){o=t[i];break}if(!L(o))return}e.apply(console,t)}}function P(e,t,n){const o=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,o),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:o}))}function S(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){S(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const B={};function V(e,t){B[e]=t}function U(e){return B[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=V,window.lnCore.getDataMapper=U);function Q(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,S(function(){new MutationObserver(function(){P(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const F={};function G(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];F[n]=t}function K(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return F[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=G,window.lnCore.getLocaleFallback=K,window.lnCore.ensureLocaleObserver=Q);const O={};function J(e,t){O[e]||(O[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=O[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function q(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let l=0;l<n.length;l++){const s=n[l],d=s.getAttribute("data-ln-field");t[d]!=null&&(s.textContent=t[d])}const o=e.querySelectorAll("[data-ln-attr]");for(let l=0;l<o.length;l++){const s=o[l],d=s.getAttribute("data-ln-attr").split(",");for(let c=0;c<d.length;c++){const f=d[c].trim().split(":");if(f.length!==2)continue;const a=f[0].trim(),h=f[1].trim();t[h]!=null&&s.setAttribute(a,t[h])}}const i=e.querySelectorAll("[data-ln-show]");for(let l=0;l<i.length;l++){const s=i[l],d=s.getAttribute("data-ln-show");d in t&&s.classList.toggle("hidden",!t[d])}const r=e.querySelectorAll("[data-ln-class]");for(let l=0;l<r.length;l++){const s=r[l],d=s.getAttribute("data-ln-class").split(",");for(let c=0;c<d.length;c++){const f=d[c].trim().split(":");if(f.length!==2)continue;const a=f[0].trim(),h=f[1].trim();h in t&&s.classList.toggle(a,!!t[h])}}return e}function X(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let o=0;o<n.length;o++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[o],t??null),n[o].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)q(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function N(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const r=n.currentNode;r.textContent.indexOf("{{")!==-1&&(r.textContent=r.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(l,s){return t[s]!==void 0?t[s]:""}))}const o=function(r,l){return t[l]!==void 0?t[l]:""},i=Array.from(e.querySelectorAll("*"));e.nodeType===1&&i.push(e);for(let r=0;r<i.length;r++){const l=i[r],s=l.attributes;for(let d=0;d<s.length;d++){const c=s[d];c.value.indexOf("{{")!==-1&&l.setAttribute(c.name,c.value.replace(/\{\{\s*(\w+)\s*\}\}/g,o))}}return e}function Y(e,t,n,o,i,r){const l={};for(let d=0;d<e.children.length;d++){const c=e.children[d],f=c.getAttribute("data-ln-render-key");f&&(l[f]=c)}const s=document.createDocumentFragment();for(let d=0;d<t.length;d++){const c=t[d],f=String(o(c));let a=l[f];if(a)i(a,c,d);else{const h=J(n,r);if(!h||(N(h,c),a=h.firstElementChild,!a))continue;a.setAttribute("data-ln-render-key",f),i(a,c,d)}s.appendChild(a)}e.textContent="",e.appendChild(s)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=N,window.lnCore.fill=q,window.lnCore.lnFill=X,window.lnCore.renderList=Y);function M(e,t,n,o,i){if(!e||t===null)return!0;const r=o||"ln-component",l=e.type;if(l==="trigger"||l==="marker"||l==="string"||l==="list"||!l||t===""&&e.fallback!==void 0)return!0;const s=d=>{i?console.warn(d,i):console.warn(d)};if(l==="boolean"){const d=t.trim().toLowerCase();return d!==""&&d!=="true"&&d!=="false"&&d!=="1"&&d!=="0"?(s(`[${r}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(l==="enum"){const d=e.values||[];return d.includes(t)?!0:(s(`[${r}] Invalid value "${t}" for attribute "${n}". Allowed: ${d.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(l==="integer"){if(!/^-?\d+$/.test(t))return s(`[${r}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const d=parseInt(t,10);return e.min!==void 0&&d<e.min?(s(`[${r}] Value ${d} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&d>e.max?(s(`[${r}] Value ${d} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(l==="float")return isNaN(Number(t))?(s(`[${r}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(l==="json")try{return JSON.parse(t),!0}catch(d){return s(`[${r}] Invalid JSON for attribute "${n}": ${d.message}. Fallback: ${e.fallback}.`),!1}return!0}function Z(e){const t={};for(const n in e){const o=e[n];o&&o.effect&&(t[n]=o.effect)}return Object.keys(t).length?t:null}function z(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function T(e,t,n,o){if(e.nodeType!==1)return;const r=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",l=Array.from(e.querySelectorAll(r));e.matches&&e.matches(r)&&l.push(e);for(const s of l)if(!s[n]){window.lnCore._persistSink&&s.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(s,t);try{s[n]=new o(s)}catch(d){console.error("["+n+"] init failed",s,d)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function ee(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function te(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function W(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function ne(e){const t=W(),n=e.observed||[];for(let o=0;o<n.length;o++){const i=n[o];t.byAttr.has(i)||t.byAttr.set(i,[]),t.byAttr.get(i).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const o in e.attributes){t.byDeclaredAttr.has(o)||t.byDeclaredAttr.set(o,[]);const i=t.byDeclaredAttr.get(o);i.some(r=>r.componentTag===e.componentTag)||i.push({spec:e.attributes[o],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const o=z(e);if(o===null)t.reactiveWildcard.push(e);else for(const i of o)t.byReactive.has(i)||t.byReactive.set(i,[]),t.byReactive.get(i).push(e)}e.persist&&t.persist.push(e)}function R(e,t,n,o){for(let i=0;i<e.length;i++){const r=e[i];if(!t[r.attribute])continue;const l=r.effects&&r.effects[n];l?l(t,n,o):r.onAttrChange&&(!r.declared||r.declared.has(n))&&r.onAttrChange(t,n,o)}}function oe(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const o=W(),i=o.byAttr.get(n);if(I()&&o.byDeclaredAttr&&o.byDeclaredAttr.has(n)&&L(t)){const r=o.byDeclaredAttr.get(n),l=t.getAttribute(n);for(let s=0;s<r.length;s++)M(r[s].spec,l,n,r[s].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||i)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const r=o.byReactive.get(n);r&&R(r,t,n,e.oldValue),o.reactiveWildcard.length&&R(o.reactiveWildcard,t,n,e.oldValue)}if(i)for(let r=0;r<i.length;r++){const l=i[r];if(l.handler){l.handler(t,n,e.oldValue);continue}l.onAttributeChange&&t[l.attribute]?l.onAttributeChange(t,n):(T(t,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(t))}}function re(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,S(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{oe(t[n])}catch(o){console.error("[ln-core] mutation handler failed",t[n].target,o)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function j(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function ie(e){const t=j();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const o=t[n];if(o.onSubtreeChange){const i=o.query,r=e.target.nodeType===1?e.target.matches(i)?e.target:e.target.closest(i):e.target.parentElement?e.target.parentElement.closest(i):null;r&&o.onSubtreeChange(r,e)}}for(let n=0;n<e.addedNodes.length;n++){const o=e.addedNodes[n];if(o.nodeType===1)for(let i=0;i<t.length;i++){const r=t[i];T(o,r.selector,r.attribute,r.ComponentFn),r.onInit&&r.onInit(o)}}for(let n=0;n<e.removedNodes.length;n++){const o=e.removedNodes[n];if(o.nodeType===1)for(let i=0;i<t.length;i++){const r=t[i],l=r.query,s=Array.from(o.querySelectorAll(l));o.matches&&o.matches(l)&&s.push(o);for(let d=0;d<s.length;d++){const c=s[d];if(!document.contains(c)){const f=c[r.attribute];if(f&&typeof f.destroy=="function")try{f.destroy()}catch(a){console.error("["+r.attribute+"] destroy failed",c,a)}delete c[r.attribute]}}}}}}function le(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,S(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const o=t[n];if(o.type==="childList")try{ie(o)}catch(i){console.error("[ln-core] lifecycle handler failed",o.target,i)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function se(e,t,n,o,i={}){const r=i.extraAttributes||[],l=i.onAttributeChange||null,s=i.onSubtreeChange||null,d=i.onInit||null,c=i.onAttrChange||null,f=i.effects||null,a=i.attributes||null,h=a?Z(a):f,v=a?new Set(Object.keys(a)):null,A=i.persist||null;function y(b){const p=b||document.body;if(T(p,e,t,n),a&&I())for(const w in a){const $=a[w],_=Array.from(p.querySelectorAll("["+w+"]"));p.matches&&p.matches("["+w+"]")&&_.push(p);for(let E=0;E<_.length;E++){const k=_[E];L(k)&&M($,k.getAttribute(w),w,o,k)}}d&&d(p)}const g=[];if(e.indexOf("[")!==-1){const b=/\[([\w-]+)/g;let p;for(;(p=b.exec(e))!==null;)g.push(p[1])}else g.push(e);ne({selector:e,attribute:t,componentTag:o,attributes:a,ComponentFn:n,onInit:d,observed:g.concat(r),onAttributeChange:l,onAttrChange:c,effects:h,declared:v,persist:A}),re();const C=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";j().push({selector:e,attribute:t,ComponentFn:n,onInit:d,onSubtreeChange:s,query:C}),le(),window[t]=y;function u(){ee()>0?te(function(){y(document.body)}):y(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",u):u(),y}function H(e){return(e||"").replace(/^#/,"")}function x(e){const t=e===void 0?location.hash:e,n={},o=H(t);if(!o)return n;const i=o.split("&");for(let r=0;r<i.length;r++){const l=i[r];if(!l)continue;const s=l.indexOf(":"),d=s>-1?l.slice(0,s):l,c=s>-1?l.slice(s+1):"";if(d)try{n[d]=decodeURIComponent(c)}catch{n[d]=c}}return n}function de(e){if(!e)return null;const t=x();return e in t?t[e]:null}function ue(e,t){if(!e)return;const n=x();t==null?delete n[e]:n[e]=String(t);const i=Object.keys(n).map(function(r){const l=n[r];return l===""?r:r+":"+encodeURIComponent(l)}).join("&");H(location.hash)!==i&&(location.hash=i)}function ce(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function ae(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const o=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return o?t?o+"-"+t:o:t||null}function fe(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function we(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function he(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function pe(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),o=e.slice(t+1),i=o?o.split(",").map(function(r){try{return decodeURIComponent(r)}catch{return r}}).filter(Boolean):[];return{key:n,values:i}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=x,window.lnCore.hashGet=de,window.lnCore.hashSet=ue,window.lnCore.hashLinkClick=ce,window.lnCore.resolveHashNamespace=ae,window.lnCore.hashSortEncode=fe,window.lnCore.hashSortDecode=we,window.lnCore.hashFilterEncode=he,window.lnCore.hashFilterDecode=pe);function be(e,t,n,o){const i=o,r=window.innerWidth,l=window.innerHeight,s=t.width,d=t.height,c=n.split("-"),f=c[0],a=c[1]==="start"||c[1]==="end"?c[1]:"center",h={top:["top","bottom","right","left"],bottom:["bottom","top","right","left"],left:["left","right","top","bottom"],right:["right","left","top","bottom"]},v=h[f]||h.bottom;function A(u){return u==="top"||u==="bottom"?a==="start"?e.left:a==="end"?e.right-s:e.left+(e.width-s)/2:a==="start"?e.top:a==="end"?e.bottom-d:e.top+(e.height-d)/2}function y(u){let b,p,w=!0;return u==="top"?(b=e.top-i-d,p=A(u),b<0&&(w=!1)):u==="bottom"?(b=e.bottom+i,p=A(u),b+d>l&&(w=!1)):u==="left"?(b=A(u),p=e.left-i-s,p<0&&(w=!1)):(b=A(u),p=e.right+i,p+s>r&&(w=!1)),{top:b,left:p,side:u,fits:w}}let g=null;for(let u=0;u<v.length;u++){const b=y(v[u]);if(b.fits){g=b;break}}g||(g=y(v[0]));let m=g.top,C=g.left;return s>=r?C=0:(C<0&&(C=0),C+s>r&&(C=r-s)),d>=l?m=0:(m<0&&(m=0),m+d>l&&(m=l-d)),{top:m,left:C,placement:g.side}}(function(){const e="data-ln-tooltip-enhance",t="data-ln-tooltip",n="data-ln-tooltip-position",o="lnTooltipEnhance",i="ln-tooltip-portal";if(window[o]!==void 0)return;const r={"data-ln-tooltip-enhance":{type:"marker",description:"Activates enhanced tooltip behavior on element or container"},"data-ln-tooltip-enhanced":{type:"marker",description:"Runtime marker applied to enhanced tooltip trigger"},"data-ln-tooltip":{type:"string",description:"Tooltip text content to display"},"data-ln-tooltip-position":{type:"enum",values:["top","top-start","top-end","bottom","bottom-start","bottom-end","left","left-start","left-end","right","right-start","right-end"],fallback:"top",description:"Preferred positioning anchor"},"data-ln-tooltip-placement":{type:"enum",values:["top","top-start","top-end","bottom","bottom-start","bottom-end","left","left-start","left-end","right","right-start","right-end"],description:"Active calculated placement applied at runtime"}};let l=0,s=null,d=null,c=null,f=null,a=null,h=null;function v(){return s&&s.parentNode||(s=document.getElementById(i),s||(s=document.createElement("div"),s.id=i,document.body.appendChild(s)),s.hasAttribute("popover")||s.setAttribute("popover","manual")),s}function A(){h||(h=function(u){u.key==="Escape"&&m()},document.addEventListener("keydown",h))}function y(){h&&(document.removeEventListener("keydown",h),h=null)}function g(u){if(c===u)return;m();const b=u.getAttribute(t)||u.getAttribute("title");if(!b)return;v(),typeof s.showPopover=="function"&&s.showPopover(),u.hasAttribute("title")&&(f=u.getAttribute("title"),u.removeAttribute("title"));const p=u.getAttribute("aria-describedby");p?a=p:a=null;const w=document.createElement("div");w.className="ln-tooltip",w.textContent=b,u[o+"Uid"]||(l+=1,u[o+"Uid"]="ln-tooltip-"+l),w.id=u[o+"Uid"],s.appendChild(w);const $=w.offsetWidth,_=w.offsetHeight,E=u.getBoundingClientRect(),k=u.getAttribute(n)||"top",D=be(E,{width:$,height:_},k,6);w.style.top=D.top+"px",w.style.left=D.left+"px",w.setAttribute("data-ln-tooltip-placement",D.placement),a?u.setAttribute("aria-describedby",a+" "+w.id):u.setAttribute("aria-describedby",w.id),d=w,c=u,A()}function m(){if(!d){y();return}c&&(a!==null?c.setAttribute("aria-describedby",a):c.removeAttribute("aria-describedby"),a=null,f!==null&&c.setAttribute("title",f)),f=null,d.parentNode&&d.parentNode.removeChild(d),d=null,c=null,s&&typeof s.hidePopover=="function"&&s.matches(":popover-open")&&s.hidePopover(),y()}function C(u){return this.dom=u,u.hasAttribute("data-ln-tooltip-enhanced")||(u.setAttribute("data-ln-tooltip-enhanced",""),this._addedEnhancedAttr=!0),this._onEnter=function(){g(u)},this._onLeave=function(){c===u&&!u.contains(document.activeElement)&&m()},this._onFocus=function(){g(u)},this._onBlur=function(){c===u&&!u.matches(":hover")&&m()},u.addEventListener("mouseenter",this._onEnter),u.addEventListener("mouseleave",this._onLeave),u.addEventListener("focus",this._onFocus,!0),u.addEventListener("blur",this._onBlur,!0),this}C.prototype.destroy=function(){const u=this.dom;u.removeEventListener("mouseenter",this._onEnter),u.removeEventListener("mouseleave",this._onLeave),u.removeEventListener("focus",this._onFocus,!0),u.removeEventListener("blur",this._onBlur,!0),c===u&&m(),this._addedEnhancedAttr&&u.removeAttribute("data-ln-tooltip-enhanced"),delete u[o],delete u[o+"Uid"]},se("["+e+"], [data-ln-tooltip-enhanced], ["+t+"][title]",o,C,"ln-tooltip",{attributes:r})})()})();
@@ -0,0 +1,86 @@
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-tooltip",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-debug": {
7
+ "direction": null,
8
+ "sources": [
9
+ "ln-tooltip-dev.scss"
10
+ ]
11
+ },
12
+ "data-ln-tooltip": {
13
+ "direction": "author",
14
+ "sources": [
15
+ "ln-tooltip-dev.scss",
16
+ "ln-tooltip.scss",
17
+ "src/ln-tooltip.js"
18
+ ],
19
+ "type": "string",
20
+ "description": "Tooltip text content to display"
21
+ },
22
+ "data-ln-tooltip-enhance": {
23
+ "direction": null,
24
+ "sources": [
25
+ "ln-tooltip.scss",
26
+ "src/ln-tooltip.js"
27
+ ],
28
+ "type": "marker",
29
+ "description": "Activates enhanced tooltip behavior on element or container"
30
+ },
31
+ "data-ln-tooltip-enhanced": {
32
+ "direction": "both",
33
+ "sources": [
34
+ "ln-tooltip.scss",
35
+ "src/ln-tooltip.js"
36
+ ],
37
+ "type": "marker",
38
+ "description": "Runtime marker applied to enhanced tooltip trigger"
39
+ },
40
+ "data-ln-tooltip-placement": {
41
+ "direction": "runtime",
42
+ "sources": [
43
+ "src/ln-tooltip.js"
44
+ ],
45
+ "type": "enum",
46
+ "values": [
47
+ "top",
48
+ "top-start",
49
+ "top-end",
50
+ "bottom",
51
+ "bottom-start",
52
+ "bottom-end",
53
+ "left",
54
+ "left-start",
55
+ "left-end",
56
+ "right",
57
+ "right-start",
58
+ "right-end"
59
+ ],
60
+ "description": "Active calculated placement applied at runtime"
61
+ },
62
+ "data-ln-tooltip-position": {
63
+ "direction": "author",
64
+ "sources": [
65
+ "src/ln-tooltip.js"
66
+ ],
67
+ "type": "enum",
68
+ "values": [
69
+ "top",
70
+ "top-start",
71
+ "top-end",
72
+ "bottom",
73
+ "bottom-start",
74
+ "bottom-end",
75
+ "left",
76
+ "left-start",
77
+ "left-end",
78
+ "right",
79
+ "right-start",
80
+ "right-end"
81
+ ],
82
+ "fallback": "top",
83
+ "description": "Preferred positioning anchor"
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,9 @@
1
+ // ==========================================================================
2
+ // ln-tooltip — Functional State
3
+ // ==========================================================================
4
+
5
+ [data-ln-tooltip][data-ln-tooltip-enhance]::after,
6
+ [data-ln-tooltip][data-ln-tooltip-enhanced]::after,
7
+ [data-ln-tooltip][title]::after {
8
+ content: none;
9
+ }
@@ -1,4 +1,4 @@
1
- import { computePlacement, dispatch, registerComponent } from '../../ln-core';
1
+ import { computePlacement, registerComponent } from '../../ln-core';
2
2
 
3
3
  (function () {
4
4
  const TRIGGER_SELECTOR = 'data-ln-tooltip-enhance';
@@ -9,6 +9,15 @@ import { computePlacement, dispatch, registerComponent } from '../../ln-core';
9
9
 
10
10
  if (window[DOM_ATTRIBUTE] !== undefined) return;
11
11
 
12
+ // ─── Attribute Contract (SSOT) ──────────────────────────
13
+ const ATTRIBUTES = {
14
+ 'data-ln-tooltip-enhance': { type: 'marker', description: 'Activates enhanced tooltip behavior on element or container' },
15
+ 'data-ln-tooltip-enhanced': { type: 'marker', description: 'Runtime marker applied to enhanced tooltip trigger' },
16
+ 'data-ln-tooltip': { type: 'string', description: 'Tooltip text content to display' },
17
+ 'data-ln-tooltip-position': { type: 'enum', values: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'], fallback: 'top', description: 'Preferred positioning anchor' },
18
+ 'data-ln-tooltip-placement': { type: 'enum', values: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'], description: 'Active calculated placement applied at runtime' }
19
+ };
20
+
12
21
  let uidCounter = 0;
13
22
  let portal = null;
14
23
  let activeTooltipNode = null;
@@ -193,7 +202,6 @@ import { computePlacement, dispatch, registerComponent } from '../../ln-core';
193
202
  }
194
203
  delete el[DOM_ATTRIBUTE];
195
204
  delete el[DOM_ATTRIBUTE + 'Uid'];
196
- dispatch(el, 'ln-tooltip:destroyed', { trigger: el });
197
205
  };
198
206
 
199
207
  // ─── Registration ──────────────────────────────────────────
@@ -202,7 +210,10 @@ import { computePlacement, dispatch, registerComponent } from '../../ln-core';
202
210
  '[' + TRIGGER_SELECTOR + '], [data-ln-tooltip-enhanced], [' + TEXT_ATTR + '][title]',
203
211
  DOM_ATTRIBUTE,
204
212
  _component,
205
- 'ln-tooltip'
213
+ 'ln-tooltip',
214
+ {
215
+ attributes: ATTRIBUTES
216
+ }
206
217
  );
207
218
  })();
208
219
 
@@ -1,5 +1,7 @@
1
1
  # ln-translations
2
2
 
3
+ > Applied to a form element (`<form data-ln-translations data-ln-translations-default="en">`). It coordinates translatable input containers (`[data-ln-translatable="field"]`), dynamically cloning the inner form controls with deterministic nested names (`trans[lang][field]`) when languages are added, and synchronizes active language badges and dropdown menus via `<template>` elements. It manages no network transport or validation, submitting cloned fields natively through the standard `<form>` submission pipeline.
4
+
3
5
  A zero-dependency, event-driven **Form Translation Coordinator** that manages inline multi-lingual inputs by dynamically cloning translatable fields on-demand.
4
6
 
5
7
  Instead of writing custom layout handlers to manage multi-language fields, this component intercepts locale additions and removals, duplicates translatable fields with appropriate naming conventions, and handles pre-rendered server payloads automatically.
@@ -101,7 +103,7 @@ Instead of writing custom layout handlers to manage multi-language fields, this
101
103
 
102
104
  ## 🔧 Internals
103
105
 
104
- Source: `js/ln-translations/ln-translations.js`. Instance state per `[data-ln-translations]` form (`form.lnTranslations`): `activeLanguages` (Set), `defaultLang`, `locales` map, `badgesEl`/`menuEl` refs, and the `placeholderLabel`/`removeLabel` templates (`{lang}` token).
106
+ Source: `components/ln-translations/ln-translations.js`. Instance state per `[data-ln-translations]` form (`form.lnTranslations`): `activeLanguages` (Set), `defaultLang`, `locales` map, `badgesEl`/`menuEl` refs, and the `placeholderLabel`/`removeLabel` templates (`{lang}` token).
105
107
 
106
108
  ### Add-language flow
107
109
 
@@ -0,0 +1,13 @@
1
+ @use '../../theme/config/mixins' as *;
2
+
3
+ [data-ln-debug] {
4
+ // 1. Badges Container Check: [data-ln-translations] must contain an active languages list
5
+ [data-ln-translations]:not(:has([data-ln-translations-active])) {
6
+ @include dev-dom-error("[data-ln-translations] is missing a required <ul data-ln-translations-active> container.");
7
+ }
8
+
9
+ // 2. Translatable Field Key Check: translatable field key cannot be empty
10
+ [data-ln-translatable=""] {
11
+ @include dev-inline-error("data-ln-translatable field key cannot be empty");
12
+ }
13
+ }
@@ -0,0 +1 @@
1
+ (function(){"use strict";function T(){return typeof window>"u"?!1:window.lnDebug===!0||window.lnCore&&window.lnCore._debugSink?!0:typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug")||document.body.querySelector("[data-ln-debug]")!==null:!1}function A(e){if(typeof window>"u")return!1;if(window.lnDebug===!0)return!0;if(window.lnCore&&window.lnCore._debugIsContained)return e?window.lnCore._debugIsContained(e):window.lnCore._debugSink!==null;if(e&&e.closest){const t=e.closest("[data-ln-debug]");return!(!t||typeof document<"u"&&t===document.documentElement)}return typeof document<"u"&&document.body?document.body.hasAttribute("data-ln-debug"):!1}if(typeof window<"u"&&(window.lnCore=window.lnCore||{},!window.lnCore._warnBound)){window.lnCore._warnBound=!0;const e=console.warn;console.warn=function(...t){if(typeof t[0]=="string"&&(t[0].startsWith("[ln-")||t[0].startsWith("[lnCore"))){let r=null;for(let a=1;a<t.length;a++)if(t[a]&&t[a].nodeType===1){r=t[a];break}if(!A(r))return}e.apply(console,t)}}function v(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:r}))}function N(e,t,n){const r=n||{};window.lnCore._debugSink&&window.lnCore._debugSink("event",t,e,r);const a=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:r});return e.dispatchEvent(a),a}function C(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){C(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}const $={};function U(e,t){$[e]=t}function K(e){return $[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=U,window.lnCore.getDataMapper=K);function J(){typeof window>"u"||(window.lnCore=window.lnCore||{},!window.lnCore._localeObserverBound&&(window.lnCore._localeObserverBound=!0,C(function(){new MutationObserver(function(){v(document,"ln-core:locale-change",{})}).observe(document.documentElement,{attributes:!0,attributeFilter:["lang"],subtree:!0})},"ln-core")))}const I={};function Q(e,t){if(!e||typeof t!="object")return;const n=e.toLowerCase().split("-")[0];I[n]=t}function G(e){if(!e)return null;const t=e.toLowerCase().split("-")[0];return I[t]||null}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerLocaleFallback=Q,window.lnCore.getLocaleFallback=G,window.lnCore.ensureLocaleObserver=J);const _={};function S(e,t){_[e]||(_[e]=document.querySelector('[data-ln-template="'+e+'"]'));const n=_[e];return n?n.content.cloneNode(!0):(console.warn("["+(t||"ln-core")+'] Template "'+e+'" not found'),null)}function B(e,t){if(!e||!t)return e;const n=e.querySelectorAll("[data-ln-field]");for(let o=0;o<n.length;o++){const s=n[o],i=s.getAttribute("data-ln-field");t[i]!=null&&(s.textContent=t[i])}const r=e.querySelectorAll("[data-ln-attr]");for(let o=0;o<r.length;o++){const s=r[o],i=s.getAttribute("data-ln-attr").split(",");for(let c=0;c<i.length;c++){const u=i[c].trim().split(":");if(u.length!==2)continue;const d=u[0].trim(),f=u[1].trim();t[f]!=null&&s.setAttribute(d,t[f])}}const a=e.querySelectorAll("[data-ln-show]");for(let o=0;o<a.length;o++){const s=a[o],i=s.getAttribute("data-ln-show");i in t&&s.classList.toggle("hidden",!t[i])}const l=e.querySelectorAll("[data-ln-class]");for(let o=0;o<l.length;o++){const s=l[o],i=s.getAttribute("data-ln-class").split(",");for(let c=0;c<i.length;c++){const u=i[c].trim().split(":");if(u.length!==2)continue;const d=u[0].trim(),f=u[1].trim();f in t&&s.classList.toggle(d,!!t[f])}}return e}function z(e,t){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&(window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",e,t??null),e.dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0})));const n=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let r=0;r<n.length;r++)window.lnCore._debugSink&&window.lnCore._debugSink("event","ln-fill",n[r],t??null),n[r].dispatchEvent(new CustomEvent("ln-fill",{detail:t??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)B(e.target,e.detail);else{const t=e.target.querySelectorAll("[data-ln-field]");for(let n=0;n<t.length;n++)t[n].textContent=""}})));function F(e,t){if(!e||!t)return e;const n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const l=n.currentNode;l.textContent.indexOf("{{")!==-1&&(l.textContent=l.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(o,s){return t[s]!==void 0?t[s]:""}))}const r=function(l,o){return t[o]!==void 0?t[o]:""},a=Array.from(e.querySelectorAll("*"));e.nodeType===1&&a.push(e);for(let l=0;l<a.length;l++){const o=a[l],s=o.attributes;for(let i=0;i<s.length;i++){const c=s[i];c.value.indexOf("{{")!==-1&&o.setAttribute(c.name,c.value.replace(/\{\{\s*(\w+)\s*\}\}/g,r))}}return e}function X(e,t,n,r,a,l){const o={};for(let i=0;i<e.children.length;i++){const c=e.children[i],u=c.getAttribute("data-ln-render-key");u&&(o[u]=c)}const s=document.createDocumentFragment();for(let i=0;i<t.length;i++){const c=t[i],u=String(r(c));let d=o[u];if(d)a(d,c,i);else{const f=S(n,l);if(!f||(F(f,c),d=f.firstElementChild,!d))continue;d.setAttribute("data-ln-render-key",u),a(d,c,i)}s.appendChild(d)}e.textContent="",e.appendChild(s)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.fillTemplate=F,window.lnCore.fill=B,window.lnCore.lnFill=z,window.lnCore.renderList=X);function w(e,t,n){const r=e.getAttribute(t);return r===null?n:r}function Y(e,t,n){const r=parseInt(e.getAttribute(t),10);return isNaN(r)?n:r}function Z(e,t,n=!1){const r=e.getAttribute(t);if(r===null)return!!n;const a=r.trim().toLowerCase();return!(a==="false"||a==="0")}function ee(e,t){return(e.getAttribute(t)||"").split(",").map(n=>n.trim()).filter(Boolean)}const L=new Map;function te(e,t){const n=(e?e.join("|"):"")+"::"+t;if(L.has(n))return L.get(n);const r=new Set(e||[]),a=function(l,o){const s=l.getAttribute(o);return s!==null&&r.has(s)?s:t};return L.set(n,a),a}function ne(e,t,n){const r=parseFloat(e.getAttribute(t));return isNaN(r)?n:r}function re(e,t,n){const r=e.getAttribute(t);if(!r)return n;try{return JSON.parse(r)}catch{return n}}function M(e,t,n,r,a){if(!e||t===null)return!0;const l=r||"ln-component",o=e.type;if(o==="trigger"||o==="marker"||o==="string"||o==="list"||!o||t===""&&e.fallback!==void 0)return!0;const s=i=>{a?console.warn(i,a):console.warn(i)};if(o==="boolean"){const i=t.trim().toLowerCase();return i!==""&&i!=="true"&&i!=="false"&&i!=="1"&&i!=="0"?(s(`[${l}] Invalid value "${t}" for boolean attribute "${n}". Allowed: "true", "false", or presence-only.`),!1):!0}if(o==="enum"){const i=e.values||[];return i.includes(t)?!0:(s(`[${l}] Invalid value "${t}" for attribute "${n}". Allowed: ${i.join(", ")}. Fallback: "${e.fallback}".`),!1)}if(o==="integer"){if(!/^-?\d+$/.test(t))return s(`[${l}] Invalid integer "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1;const i=parseInt(t,10);return e.min!==void 0&&i<e.min?(s(`[${l}] Value ${i} for attribute "${n}" is less than min (${e.min}).`),!1):e.max!==void 0&&i>e.max?(s(`[${l}] Value ${i} for attribute "${n}" is greater than max (${e.max}).`),!1):!0}if(o==="float")return isNaN(Number(t))?(s(`[${l}] Invalid float "${t}" for attribute "${n}". Fallback: ${e.fallback}.`),!1):!0;if(o==="json")try{return JSON.parse(t),!0}catch(i){return s(`[${l}] Invalid JSON for attribute "${n}": ${i.message}. Fallback: ${e.fallback}.`),!1}return!0}function oe(e,t,n){for(const r in n){const[a,l,o]=n[r];Object.defineProperty(e,r,{get:function(){return a(t,l,o)},enumerable:!0,configurable:!0})}return e}function ae(e){const t={};for(const n in e){const r=e[n];if(!r||!r.prop)continue;let a=r.read;if(!a&&r.type)switch(r.type){case"enum":a=te(r.values,r.fallback);break;case"integer":a=Y;break;case"float":a=ne;break;case"boolean":a=Z;break;case"list":a=ee;break;case"json":a=re;break;case"string":default:a=w;break}a||(a=w),t[r.prop]=[a,n,r.fallback]}return t}function ie(e){const t={};for(const n in e){const r=e[n];r&&r.effect&&(t[n]=r.effect)}return Object.keys(t).length?t:null}function le(e){if(e.onAttrChange&&!e.declared)return null;const t=new Set;if(e.effects)for(const n in e.effects)t.add(n);if(e.onAttrChange&&e.declared)for(const n of e.declared)t.add(n);return t}function k(e,t,n,r){if(e.nodeType!==1)return;const l=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",o=Array.from(e.querySelectorAll(l));e.matches&&e.matches(l)&&o.push(e);for(const s of o)if(!s[n]){window.lnCore._persistSink&&s.hasAttribute("data-ln-persist")&&window.lnCore._persistSink(s,t);try{s[n]=new r(s)}catch(i){console.error("["+n+"] init failed",s,i)}}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[]);function se(){return typeof window<"u"&&window.lnCore&&window.lnCore._bootHolds||0}function ce(e){typeof window<"u"?(window.lnCore=window.lnCore||{},window.lnCore._bootHolds=window.lnCore._bootHolds||0,window.lnCore._bootQueue=window.lnCore._bootQueue||[],window.lnCore._bootHolds>0?window.lnCore._bootQueue.push(e):setTimeout(e,0)):e()}function j(){window.lnCore=window.lnCore||{};const e=window.lnCore._attrRegistry=window.lnCore._attrRegistry||{byAttr:new Map,reactive:[],persist:[]};return e.byReactive=e.byReactive||new Map,e.reactiveWildcard=e.reactiveWildcard||[],e}function de(e){const t=j(),n=e.observed||[];for(let r=0;r<n.length;r++){const a=n[r];t.byAttr.has(a)||t.byAttr.set(a,[]),t.byAttr.get(a).push(e)}if(t.byDeclaredAttr=t.byDeclaredAttr||new Map,e.attributes)for(const r in e.attributes){t.byDeclaredAttr.has(r)||t.byDeclaredAttr.set(r,[]);const a=t.byDeclaredAttr.get(r);a.some(l=>l.componentTag===e.componentTag)||a.push({spec:e.attributes[r],componentTag:e.componentTag})}if(e.onAttrChange||e.effects){t.reactive.push(e);const r=le(e);if(r===null)t.reactiveWildcard.push(e);else for(const a of r)t.byReactive.has(a)||t.byReactive.set(a,[]),t.byReactive.get(a).push(e)}e.persist&&t.persist.push(e)}function W(e,t,n,r){for(let a=0;a<e.length;a++){const l=e[a];if(!t[l.attribute])continue;const o=l.effects&&l.effects[n];o?o(t,n,r):l.onAttrChange&&(!l.declared||l.declared.has(n))&&l.onAttrChange(t,n,r)}}function ue(e){const t=e.target,n=e.attributeName;if(e.oldValue===t.getAttribute(n))return;const r=j(),a=r.byAttr.get(n);if(T()&&r.byDeclaredAttr&&r.byDeclaredAttr.has(n)&&A(t)){const l=r.byDeclaredAttr.get(n),o=t.getAttribute(n);for(let s=0;s<l.length;s++)M(l[s].spec,o,n,l[s].componentTag,t)}if(window.lnCore._debugSink&&(n.indexOf("data-ln-")===0||a)&&window.lnCore._debugSink("attr",n,t,{oldValue:e.oldValue,newValue:t.getAttribute(n)}),n.indexOf("data-ln-")===0){const l=r.byReactive.get(n);l&&W(l,t,n,e.oldValue),r.reactiveWildcard.length&&W(r.reactiveWildcard,t,n,e.oldValue)}if(a)for(let l=0;l<a.length;l++){const o=a[l];if(o.handler){o.handler(t,n,e.oldValue);continue}o.onAttributeChange&&t[o.attribute]?o.onAttributeChange(t,n):(k(t,o.selector,o.attribute,o.ComponentFn),o.onInit&&o.onInit(t))}}function fe(){window.lnCore=window.lnCore||{},!window.lnCore._attrObserverBound&&(window.lnCore._attrObserverBound=!0,C(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++)try{ue(t[n])}catch(r){console.error("[ln-core] mutation handler failed",t[n].target,r)}}).observe(document.body,{attributes:!0,subtree:!0,attributeOldValue:!0})},"ln-core"))}function V(){return window.lnCore=window.lnCore||{},window.lnCore._lifecycleRegistry=window.lnCore._lifecycleRegistry||[]}function ge(e){const t=V();if(t.length){if(e.target)for(let n=0;n<t.length;n++){const r=t[n];if(r.onSubtreeChange){const a=r.query,l=e.target.nodeType===1?e.target.matches(a)?e.target:e.target.closest(a):e.target.parentElement?e.target.parentElement.closest(a):null;l&&r.onSubtreeChange(l,e)}}for(let n=0;n<e.addedNodes.length;n++){const r=e.addedNodes[n];if(r.nodeType===1)for(let a=0;a<t.length;a++){const l=t[a];k(r,l.selector,l.attribute,l.ComponentFn),l.onInit&&l.onInit(r)}}for(let n=0;n<e.removedNodes.length;n++){const r=e.removedNodes[n];if(r.nodeType===1)for(let a=0;a<t.length;a++){const l=t[a],o=l.query,s=Array.from(r.querySelectorAll(o));r.matches&&r.matches(o)&&s.push(r);for(let i=0;i<s.length;i++){const c=s[i];if(!document.contains(c)){const u=c[l.attribute];if(u&&typeof u.destroy=="function")try{u.destroy()}catch(d){console.error("["+l.attribute+"] destroy failed",c,d)}delete c[l.attribute]}}}}}}function be(){window.lnCore=window.lnCore||{},!window.lnCore._lifecycleObserverBound&&(window.lnCore._lifecycleObserverBound=!0,C(function(){new MutationObserver(function(t){for(let n=0;n<t.length;n++){const r=t[n];if(r.type==="childList")try{ge(r)}catch(a){console.error("[ln-core] lifecycle handler failed",r.target,a)}}}).observe(document.body,{childList:!0,subtree:!0})},"ln-core"))}function pe(e,t,n,r,a={}){const l=a.extraAttributes||[],o=a.onAttributeChange||null,s=a.onSubtreeChange||null,i=a.onInit||null,c=a.onAttrChange||null,u=a.effects||null,d=a.attributes||null,f=d?ie(d):u,b=d?new Set(Object.keys(d)):null,m=a.persist||null;function g(q){const p=q||document.body;if(k(p,e,t,n),d&&T())for(const y in d){const Le=d[y],O=Array.from(p.querySelectorAll("["+y+"]"));p.matches&&p.matches("["+y+"]")&&O.push(p);for(let D=0;D<O.length;D++){const R=O[D];A(R)&&M(Le,R.getAttribute(y),y,r,R)}}i&&i(p)}const h=[];if(e.indexOf("[")!==-1){const q=/\[([\w-]+)/g;let p;for(;(p=q.exec(e))!==null;)h.push(p[1])}else h.push(e);de({selector:e,attribute:t,componentTag:r,attributes:d,ComponentFn:n,onInit:i,observed:h.concat(l),onAttributeChange:o,onAttrChange:c,effects:f,declared:b,persist:m}),fe();const Se=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]";V().push({selector:e,attribute:t,ComponentFn:n,onInit:i,onSubtreeChange:s,query:Se}),be(),window[t]=g;function H(){se()>0?ce(function(){g(document.body)}):g(document.body)}return document.readyState==="loading"?document.addEventListener("DOMContentLoaded",H):H(),g}function P(e){return(e||"").replace(/^#/,"")}function E(e){const t=e===void 0?location.hash:e,n={},r=P(t);if(!r)return n;const a=r.split("&");for(let l=0;l<a.length;l++){const o=a[l];if(!o)continue;const s=o.indexOf(":"),i=s>-1?o.slice(0,s):o,c=s>-1?o.slice(s+1):"";if(i)try{n[i]=decodeURIComponent(c)}catch{n[i]=c}}return n}function he(e){if(!e)return null;const t=E();return e in t?t[e]:null}function we(e,t){if(!e)return;const n=E();t==null?delete n[e]:n[e]=String(t);const a=Object.keys(n).map(function(l){const o=n[l];return o===""?l:l+":"+encodeURIComponent(o)}).join("&");P(location.hash)!==a&&(location.hash=a)}function me(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}function ye(e,t){if(!e||!e.hasAttribute("data-ln-hash"))return null;const n=e.getAttribute("data-ln-hash");if(n&&n.trim()!=="")return n.trim();const r=e.getAttribute("data-ln-sort")||e.getAttribute("data-ln-search-for")||e.getAttribute("data-ln-search")||e.getAttribute("data-ln-filter")||e.id;return r?t?r+"-"+t:r:t||null}function Ce(e,t){return!t||t==="none"||e===null||e===void 0?null:String(e)+"."+t}function Ae(e){return!e||typeof e!="string"?null:e.endsWith(".asc")?{fieldOrColumn:e.slice(0,-4),direction:"asc"}:e.endsWith(".desc")?{fieldOrColumn:e.slice(0,-5),direction:"desc"}:null}function ve(e,t){return!e||!Array.isArray(t)||t.length===0?null:e+":"+t.map(encodeURIComponent).join(",")}function _e(e){if(!e||typeof e!="string")return null;const t=e.indexOf(":");if(t===-1)return null;const n=e.slice(0,t),r=e.slice(t+1),a=r?r.split(",").map(function(l){try{return decodeURIComponent(l)}catch{return l}}).filter(Boolean):[];return{key:n,values:a}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=E,window.lnCore.hashGet=he,window.lnCore.hashSet=we,window.lnCore.hashLinkClick=me,window.lnCore.resolveHashNamespace=ye,window.lnCore.hashSortEncode=Ce,window.lnCore.hashSortDecode=Ae,window.lnCore.hashFilterEncode=ve,window.lnCore.hashFilterDecode=_e),(function(){const e="data-ln-translations",t="lnTranslations";if(window[t]!==void 0)return;const n={"data-ln-translations":{type:"marker",description:"Mounts multi-language translation manager on form container"},"data-ln-translations-default":{prop:"defaultLang",read:w,type:"string",fallback:"",description:"Default primary language code (e.g. en)"},"data-ln-translations-placeholder":{prop:"placeholderLabel",read:w,type:"string",fallback:"{lang} translation",description:"Placeholder label pattern for cloned translation inputs"},"data-ln-translations-remove-label":{prop:"removeLabel",read:w,type:"string",fallback:"Remove {lang}",description:"Accessible label template for translation removal buttons"},"data-ln-translations-locales":{prop:"_localesRaw",read:w,type:"json",fallback:"",description:"JSON dictionary of supported locale codes and display labels"},"data-ln-translations-active":{type:"marker",description:"Container holding active language badge tags"},"data-ln-translations-add":{type:"string",description:"Trigger button action to add specified language translation fields"},"data-ln-translations-lang":{type:"string",description:"Language code associated with active language badge"},"data-ln-translations-prefix":{type:"string",description:"Prefix format pattern for cloned translated field names"},"data-ln-translatable":{type:"marker",description:"Marks form control as translatable into multiple languages"},"data-ln-translatable-lang":{type:"string",description:"Language code assigned to specific translatable input instance"}},r=ae(n),a={en:"English",sq:"Shqip",sr:"Srpski"};function l(o){if(this.dom=o,oe(this,o,r),this.activeLanguages=new Set,this.badgesEl=o.querySelector("[data-ln-translations-active]"),this.menuEl=o.querySelector("[data-ln-dropdown] > [data-ln-toggle]"),this.locales=a,this._localesRaw)try{this.locales=JSON.parse(this._localesRaw)}catch{console.warn("[ln-translations] Invalid JSON in data-ln-translations-locales")}this._applyDefaultLang(),this._updateDropdown();const s=this;return this._onRequestAdd=function(i){i.detail&&i.detail.lang&&s.addLanguage(i.detail.lang)},this._onRequestRemove=function(i){i.detail&&i.detail.lang&&s.removeLanguage(i.detail.lang)},o.addEventListener("ln-translations:request-add",this._onRequestAdd),o.addEventListener("ln-translations:request-remove",this._onRequestRemove),this._detectExisting(),this}l.prototype._applyDefaultLang=function(){if(!this.defaultLang)return;const o=this.dom.querySelectorAll("[data-ln-translatable]");for(const s of o){const i=s.querySelectorAll("input:not([data-ln-translatable-lang]), textarea:not([data-ln-translatable-lang]), select:not([data-ln-translatable-lang])");for(const c of i)c.setAttribute("data-ln-translatable-lang",this.defaultLang)}},l.prototype._detectExisting=function(){const o=this.dom.querySelectorAll("[data-ln-translatable-lang]");for(const s of o){const i=s.getAttribute("data-ln-translatable-lang");i&&i!==this.defaultLang&&this.activeLanguages.add(i)}this.activeLanguages.size>0&&(this._updateBadges(),this._updateDropdown())},l.prototype._updateDropdown=function(){if(!this.menuEl)return;this.menuEl.textContent="";const o=this;let s=0;for(const c in this.locales){if(!this.locales.hasOwnProperty(c)||this.activeLanguages.has(c))continue;s++;const u=S("ln-translations-menu-item","ln-translations");if(!u)return;const d=u.querySelector("[data-ln-translations-lang]");d.setAttribute("data-ln-translations-lang",c),d.textContent=this.locales[c],d.addEventListener("click",function(f){f.ctrlKey||f.metaKey||f.button===1||(f.preventDefault(),f.stopPropagation(),o.menuEl.getAttribute("data-ln-toggle")==="open"&&o.menuEl.setAttribute("data-ln-toggle","close"),o.addLanguage(c))}),this.menuEl.appendChild(u)}const i=this.dom.querySelector("[data-ln-translations-add]");i&&(i.hidden=s===0)},l.prototype._updateBadges=function(){if(!this.badgesEl)return;this.badgesEl.textContent="";const o=this;this.activeLanguages.forEach(function(s){const i=S("ln-translations-badge","ln-translations");if(!i)return;const c=i.querySelector("[data-ln-translations-lang]");c.setAttribute("data-ln-translations-lang",s);const u=c.querySelector("span");u.textContent=o.locales[s]||s.toUpperCase();const d=c.querySelector("button"),f=o.locales[s]||s.toUpperCase();d.setAttribute("aria-label",o.removeLabel.replace("{lang}",f)),d.addEventListener("click",function(b){b.ctrlKey||b.metaKey||b.button===1||(b.preventDefault(),b.stopPropagation(),o.removeLanguage(s))}),o.badgesEl.appendChild(i)})},l.prototype.addLanguage=function(o,s){if(this.activeLanguages.has(o))return;const i=this.locales[o]||o;if(N(this.dom,"ln-translations:before-add",{target:this.dom,lang:o,langName:i}).defaultPrevented)return;this.activeLanguages.add(o),s=s||{};const u=this.dom.querySelectorAll("[data-ln-translatable]");for(const d of u){const f=d.getAttribute("data-ln-translatable"),b=d.getAttribute("data-ln-translations-prefix")||"",m=d.querySelector(this.defaultLang?'[data-ln-translatable-lang="'+this.defaultLang+'"]':"input:not([data-ln-translatable-lang]), textarea:not([data-ln-translatable-lang]), select:not([data-ln-translatable-lang])");if(!m)continue;const g=m.cloneNode(m.tagName==="SELECT");b?g.name=b+"[trans]["+o+"]["+f+"]":g.name="trans["+o+"]["+f+"]",g.value=s[f]!==void 0?s[f]:"",g.removeAttribute("id"),"placeholder"in g&&(g.placeholder=this.placeholderLabel.replace("{lang}",i)),g.setAttribute("data-ln-translatable-lang",o);const h=d.querySelectorAll('[data-ln-translatable-lang]:not([data-ln-translatable-lang="'+this.defaultLang+'"])'),x=h.length>0?h[h.length-1]:m;x.parentNode.insertBefore(g,x.nextSibling)}this._updateDropdown(),this._updateBadges(),v(this.dom,"ln-translations:added",{target:this.dom,lang:o,langName:i})},l.prototype.removeLanguage=function(o){if(!this.activeLanguages.has(o)||N(this.dom,"ln-translations:before-remove",{target:this.dom,lang:o}).defaultPrevented)return;const i=this.dom.querySelectorAll('[data-ln-translatable-lang="'+o+'"]');for(const c of i)c.parentNode.removeChild(c);this.activeLanguages.delete(o),this._updateDropdown(),this._updateBadges(),v(this.dom,"ln-translations:removed",{target:this.dom,lang:o})},l.prototype.getActiveLanguages=function(){return new Set(this.activeLanguages)},l.prototype.hasLanguage=function(o){return this.activeLanguages.has(o)},l.prototype.destroy=function(){if(!this.dom[t])return;const o=this.defaultLang,s=this.dom.querySelectorAll("[data-ln-translatable-lang]");for(const i of s)i.getAttribute("data-ln-translatable-lang")!==o&&i.parentNode.removeChild(i);this.dom.removeEventListener("ln-translations:request-add",this._onRequestAdd),this.dom.removeEventListener("ln-translations:request-remove",this._onRequestRemove),delete this.dom[t]},pe(e,t,l,"ln-translations",{attributes:n})})()})();
@@ -0,0 +1,120 @@
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-translations",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-debug": {
7
+ "direction": null,
8
+ "sources": [
9
+ "ln-translations-dev.scss"
10
+ ]
11
+ },
12
+ "data-ln-dropdown": {
13
+ "direction": "author",
14
+ "sources": [
15
+ "src/ln-translations.js"
16
+ ]
17
+ },
18
+ "data-ln-toggle": {
19
+ "direction": "both",
20
+ "sources": [
21
+ "src/ln-translations.js"
22
+ ]
23
+ },
24
+ "data-ln-translatable": {
25
+ "direction": "author",
26
+ "sources": [
27
+ "ln-translations-dev.scss",
28
+ "src/ln-translations.js"
29
+ ],
30
+ "type": "marker",
31
+ "description": "Marks form control as translatable into multiple languages"
32
+ },
33
+ "data-ln-translatable-lang": {
34
+ "direction": "both",
35
+ "sources": [
36
+ "src/ln-translations.js"
37
+ ],
38
+ "type": "string",
39
+ "description": "Language code assigned to specific translatable input instance"
40
+ },
41
+ "data-ln-translations": {
42
+ "direction": null,
43
+ "sources": [
44
+ "ln-translations-dev.scss",
45
+ "src/ln-translations.js"
46
+ ],
47
+ "type": "marker",
48
+ "description": "Mounts multi-language translation manager on form container"
49
+ },
50
+ "data-ln-translations-active": {
51
+ "direction": "author",
52
+ "sources": [
53
+ "ln-translations-dev.scss",
54
+ "src/ln-translations.js"
55
+ ],
56
+ "type": "marker",
57
+ "description": "Container holding active language badge tags"
58
+ },
59
+ "data-ln-translations-add": {
60
+ "direction": "author",
61
+ "sources": [
62
+ "src/ln-translations.js"
63
+ ],
64
+ "type": "string",
65
+ "description": "Trigger button action to add specified language translation fields"
66
+ },
67
+ "data-ln-translations-default": {
68
+ "direction": null,
69
+ "sources": [
70
+ "src/ln-translations.js"
71
+ ],
72
+ "type": "string",
73
+ "fallback": "",
74
+ "description": "Default primary language code (e.g. en)"
75
+ },
76
+ "data-ln-translations-lang": {
77
+ "direction": "both",
78
+ "sources": [
79
+ "src/ln-translations.js"
80
+ ],
81
+ "type": "string",
82
+ "description": "Language code associated with active language badge"
83
+ },
84
+ "data-ln-translations-locales": {
85
+ "direction": null,
86
+ "sources": [
87
+ "src/ln-translations.js"
88
+ ],
89
+ "type": "json",
90
+ "fallback": "",
91
+ "description": "JSON dictionary of supported locale codes and display labels"
92
+ },
93
+ "data-ln-translations-placeholder": {
94
+ "direction": null,
95
+ "sources": [
96
+ "src/ln-translations.js"
97
+ ],
98
+ "type": "string",
99
+ "fallback": "{lang} translation",
100
+ "description": "Placeholder label pattern for cloned translation inputs"
101
+ },
102
+ "data-ln-translations-prefix": {
103
+ "direction": "author",
104
+ "sources": [
105
+ "src/ln-translations.js"
106
+ ],
107
+ "type": "string",
108
+ "description": "Prefix format pattern for cloned translated field names"
109
+ },
110
+ "data-ln-translations-remove-label": {
111
+ "direction": null,
112
+ "sources": [
113
+ "src/ln-translations.js"
114
+ ],
115
+ "type": "string",
116
+ "fallback": "Remove {lang}",
117
+ "description": "Accessible label template for translation removal buttons"
118
+ }
119
+ }
120
+ }