@livenetworks/ashlar 1.3.2

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 (232) hide show
  1. package/README.md +177 -0
  2. package/js/COMPONENTS.md +1102 -0
  3. package/js/index.js +41 -0
  4. package/js/ln-accordion/README.md +137 -0
  5. package/js/ln-accordion/ln-accordion.js +1 -0
  6. package/js/ln-accordion/src/ln-accordion.js +41 -0
  7. package/js/ln-ajax/README.md +91 -0
  8. package/js/ln-ajax/ln-ajax.js +1 -0
  9. package/js/ln-ajax/src/ln-ajax.js +277 -0
  10. package/js/ln-api-connector/README.md +150 -0
  11. package/js/ln-api-connector/ln-api-connector.js +1 -0
  12. package/js/ln-api-connector/src/ln-api-connector.js +265 -0
  13. package/js/ln-autoresize/README.md +80 -0
  14. package/js/ln-autoresize/ln-autoresize.js +1 -0
  15. package/js/ln-autoresize/src/ln-autoresize.js +47 -0
  16. package/js/ln-autosave/README.md +92 -0
  17. package/js/ln-autosave/ln-autosave.js +1 -0
  18. package/js/ln-autosave/src/ln-autosave.js +147 -0
  19. package/js/ln-circular-progress/README.md +161 -0
  20. package/js/ln-circular-progress/ln-circular-progress.js +1 -0
  21. package/js/ln-circular-progress/src/ln-circular-progress.js +133 -0
  22. package/js/ln-confirm/README.md +86 -0
  23. package/js/ln-confirm/_ln-confirm.scss +13 -0
  24. package/js/ln-confirm/ln-confirm.js +1 -0
  25. package/js/ln-confirm/src/ln-confirm.js +131 -0
  26. package/js/ln-core/crypto.js +83 -0
  27. package/js/ln-core/helpers.js +411 -0
  28. package/js/ln-core/index.js +5 -0
  29. package/js/ln-core/persist.js +71 -0
  30. package/js/ln-core/positioning.js +207 -0
  31. package/js/ln-core/reactive.js +74 -0
  32. package/js/ln-couchdb-connector/README.md +156 -0
  33. package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -0
  34. package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +348 -0
  35. package/js/ln-data-coordinator/README.md +165 -0
  36. package/js/ln-data-coordinator/ln-data-coordinator.js +1 -0
  37. package/js/ln-data-coordinator/src/ln-data-coordinator.js +249 -0
  38. package/js/ln-data-store/README.md +94 -0
  39. package/js/ln-data-store/ln-data-store.js +1 -0
  40. package/js/ln-data-store/src/ln-data-store.js +699 -0
  41. package/js/ln-data-table/README.md +110 -0
  42. package/js/ln-data-table/ln-data-table.js +1 -0
  43. package/js/ln-data-table/ln-data-table.scss +10 -0
  44. package/js/ln-data-table/src/ln-data-table.js +1103 -0
  45. package/js/ln-date/README.md +151 -0
  46. package/js/ln-date/ln-date.js +1 -0
  47. package/js/ln-date/src/ln-date.js +442 -0
  48. package/js/ln-dropdown/README.md +117 -0
  49. package/js/ln-dropdown/ln-dropdown.js +1 -0
  50. package/js/ln-dropdown/ln-dropdown.scss +15 -0
  51. package/js/ln-dropdown/src/ln-dropdown.js +174 -0
  52. package/js/ln-external-links/README.md +341 -0
  53. package/js/ln-external-links/ln-external-links.js +1 -0
  54. package/js/ln-external-links/src/ln-external-links.js +116 -0
  55. package/js/ln-filter/README.md +99 -0
  56. package/js/ln-filter/ln-filter.js +1 -0
  57. package/js/ln-filter/ln-filter.scss +7 -0
  58. package/js/ln-filter/src/ln-filter.js +404 -0
  59. package/js/ln-form/README.md +101 -0
  60. package/js/ln-form/ln-form.js +1 -0
  61. package/js/ln-form/src/ln-form.js +199 -0
  62. package/js/ln-http/README.md +89 -0
  63. package/js/ln-http/ln-http.js +1 -0
  64. package/js/ln-http/src/ln-http.js +219 -0
  65. package/js/ln-icons/README.md +88 -0
  66. package/js/ln-icons/ln-icons.js +1 -0
  67. package/js/ln-icons/src/ln-icons.js +169 -0
  68. package/js/ln-link/README.md +303 -0
  69. package/js/ln-link/ln-link.js +1 -0
  70. package/js/ln-link/src/ln-link.js +196 -0
  71. package/js/ln-modal/README.md +154 -0
  72. package/js/ln-modal/ln-modal.js +1 -0
  73. package/js/ln-modal/ln-modal.scss +11 -0
  74. package/js/ln-modal/src/ln-modal.js +201 -0
  75. package/js/ln-nav/README.md +70 -0
  76. package/js/ln-nav/ln-nav.js +1 -0
  77. package/js/ln-nav/src/ln-nav.js +177 -0
  78. package/js/ln-number/README.md +122 -0
  79. package/js/ln-number/ln-number.js +1 -0
  80. package/js/ln-number/src/ln-number.js +302 -0
  81. package/js/ln-popover/README.md +127 -0
  82. package/js/ln-popover/ln-popover.js +1 -0
  83. package/js/ln-popover/src/ln-popover.js +288 -0
  84. package/js/ln-progress/README.md +442 -0
  85. package/js/ln-progress/ln-progress.js +1 -0
  86. package/js/ln-progress/src/ln-progress.js +150 -0
  87. package/js/ln-search/README.md +83 -0
  88. package/js/ln-search/ln-search.js +1 -0
  89. package/js/ln-search/ln-search.scss +7 -0
  90. package/js/ln-search/src/ln-search.js +114 -0
  91. package/js/ln-sortable/README.md +95 -0
  92. package/js/ln-sortable/ln-sortable.js +1 -0
  93. package/js/ln-sortable/src/ln-sortable.js +203 -0
  94. package/js/ln-table/README.md +101 -0
  95. package/js/ln-table/ln-table-sort.js +1 -0
  96. package/js/ln-table/ln-table.js +1 -0
  97. package/js/ln-table/ln-table.scss +11 -0
  98. package/js/ln-table/src/ln-table-sort.js +168 -0
  99. package/js/ln-table/src/ln-table.js +473 -0
  100. package/js/ln-tabs/README.md +137 -0
  101. package/js/ln-tabs/ln-tabs.js +1 -0
  102. package/js/ln-tabs/src/ln-tabs.js +171 -0
  103. package/js/ln-time/README.md +81 -0
  104. package/js/ln-time/ln-time.js +1 -0
  105. package/js/ln-time/src/ln-time.js +192 -0
  106. package/js/ln-toast/README.md +122 -0
  107. package/js/ln-toast/ln-toast.js +15 -0
  108. package/js/ln-toast/src/ln-toast.js +210 -0
  109. package/js/ln-toast/template.html +14 -0
  110. package/js/ln-toggle/README.md +137 -0
  111. package/js/ln-toggle/ln-toggle.js +1 -0
  112. package/js/ln-toggle/src/ln-toggle.js +139 -0
  113. package/js/ln-tooltip/README.md +58 -0
  114. package/js/ln-tooltip/ln-tooltip.js +1 -0
  115. package/js/ln-tooltip/ln-tooltip.scss +9 -0
  116. package/js/ln-tooltip/src/ln-tooltip.js +169 -0
  117. package/js/ln-translations/README.md +96 -0
  118. package/js/ln-translations/ln-translations.js +1 -0
  119. package/js/ln-translations/src/ln-translations.js +275 -0
  120. package/js/ln-upload/README.md +180 -0
  121. package/js/ln-upload/ln-upload.js +1 -0
  122. package/js/ln-upload/ln-upload.scss +20 -0
  123. package/js/ln-upload/src/ln-upload.js +407 -0
  124. package/js/ln-validate/README.md +108 -0
  125. package/js/ln-validate/ln-validate.js +1 -0
  126. package/js/ln-validate/src/ln-validate.js +160 -0
  127. package/package.json +55 -0
  128. package/scss/base/_global.scss +83 -0
  129. package/scss/base/_reset.scss +17 -0
  130. package/scss/base/_typography.scss +125 -0
  131. package/scss/components/_accordion.scss +34 -0
  132. package/scss/components/_ajax.scss +15 -0
  133. package/scss/components/_alert.scss +5 -0
  134. package/scss/components/_app-shell.scss +15 -0
  135. package/scss/components/_avatar.scss +6 -0
  136. package/scss/components/_breadcrumbs.scss +33 -0
  137. package/scss/components/_button.scss +20 -0
  138. package/scss/components/_card.scss +10 -0
  139. package/scss/components/_chip.scss +5 -0
  140. package/scss/components/_circular-progress.scss +29 -0
  141. package/scss/components/_confirm.scss +5 -0
  142. package/scss/components/_data-table.scss +83 -0
  143. package/scss/components/_dropdown.scss +25 -0
  144. package/scss/components/_empty-state.scss +22 -0
  145. package/scss/components/_form.scss +100 -0
  146. package/scss/components/_layout.scss +8 -0
  147. package/scss/components/_link.scss +11 -0
  148. package/scss/components/_ln-table.scss +60 -0
  149. package/scss/components/_loader.scss +6 -0
  150. package/scss/components/_modal.scss +20 -0
  151. package/scss/components/_nav.scss +9 -0
  152. package/scss/components/_page-header.scss +10 -0
  153. package/scss/components/_popover.scss +10 -0
  154. package/scss/components/_progress.scss +17 -0
  155. package/scss/components/_prose.scss +5 -0
  156. package/scss/components/_scrollbar.scss +32 -0
  157. package/scss/components/_sections.scss +12 -0
  158. package/scss/components/_sidebar.scss +5 -0
  159. package/scss/components/_stat-card.scss +5 -0
  160. package/scss/components/_status-badge.scss +4 -0
  161. package/scss/components/_stepper.scss +5 -0
  162. package/scss/components/_table.scss +19 -0
  163. package/scss/components/_tabs.scss +21 -0
  164. package/scss/components/_timeline.scss +14 -0
  165. package/scss/components/_toast.scss +41 -0
  166. package/scss/components/_toggle.scss +81 -0
  167. package/scss/components/_tooltip.scss +18 -0
  168. package/scss/components/_translations.scss +111 -0
  169. package/scss/components/_upload.scss +51 -0
  170. package/scss/config/_breakpoints.scss +72 -0
  171. package/scss/config/_density.scss +117 -0
  172. package/scss/config/_icons.scss +37 -0
  173. package/scss/config/_mixins.scss +13 -0
  174. package/scss/config/_theme.scss +216 -0
  175. package/scss/config/_tokens.scss +419 -0
  176. package/scss/config/mixins/_accordion.scss +52 -0
  177. package/scss/config/mixins/_ajax.scss +39 -0
  178. package/scss/config/mixins/_alert.scss +82 -0
  179. package/scss/config/mixins/_app-shell.scss +312 -0
  180. package/scss/config/mixins/_avatar.scss +109 -0
  181. package/scss/config/mixins/_borders.scss +36 -0
  182. package/scss/config/mixins/_breadcrumbs.scss +72 -0
  183. package/scss/config/mixins/_breakpoints.scss +62 -0
  184. package/scss/config/mixins/_btn.scss +179 -0
  185. package/scss/config/mixins/_card.scss +338 -0
  186. package/scss/config/mixins/_chip.scss +66 -0
  187. package/scss/config/mixins/_circular-progress.scss +71 -0
  188. package/scss/config/mixins/_collapsible.scss +24 -0
  189. package/scss/config/mixins/_colors.scss +46 -0
  190. package/scss/config/mixins/_confirm.scss +31 -0
  191. package/scss/config/mixins/_data-table.scss +346 -0
  192. package/scss/config/mixins/_display.scss +32 -0
  193. package/scss/config/mixins/_dropdown.scss +143 -0
  194. package/scss/config/mixins/_empty-state.scss +30 -0
  195. package/scss/config/mixins/_focus.scss +55 -0
  196. package/scss/config/mixins/_footer.scss +42 -0
  197. package/scss/config/mixins/_form.scss +601 -0
  198. package/scss/config/mixins/_index.scss +58 -0
  199. package/scss/config/mixins/_interaction.scss +15 -0
  200. package/scss/config/mixins/_kbd.scss +22 -0
  201. package/scss/config/mixins/_layout.scss +117 -0
  202. package/scss/config/mixins/_link.scss +55 -0
  203. package/scss/config/mixins/_ln-table.scss +420 -0
  204. package/scss/config/mixins/_loader.scss +26 -0
  205. package/scss/config/mixins/_modal.scss +66 -0
  206. package/scss/config/mixins/_motion.scss +19 -0
  207. package/scss/config/mixins/_nav.scss +273 -0
  208. package/scss/config/mixins/_page-header.scss +69 -0
  209. package/scss/config/mixins/_popover.scss +25 -0
  210. package/scss/config/mixins/_position.scss +32 -0
  211. package/scss/config/mixins/_progress.scss +56 -0
  212. package/scss/config/mixins/_prose.scss +127 -0
  213. package/scss/config/mixins/_shadows.scss +8 -0
  214. package/scss/config/mixins/_sidebar.scss +95 -0
  215. package/scss/config/mixins/_sizing.scss +6 -0
  216. package/scss/config/mixins/_spacing.scss +19 -0
  217. package/scss/config/mixins/_stat-card.scss +68 -0
  218. package/scss/config/mixins/_status-badge.scss +83 -0
  219. package/scss/config/mixins/_stepper.scss +78 -0
  220. package/scss/config/mixins/_table.scss +215 -0
  221. package/scss/config/mixins/_tabs.scss +64 -0
  222. package/scss/config/mixins/_timeline.scss +69 -0
  223. package/scss/config/mixins/_toast.scss +148 -0
  224. package/scss/config/mixins/_tooltip.scss +111 -0
  225. package/scss/config/mixins/_transitions.scss +10 -0
  226. package/scss/config/mixins/_translations.scss +124 -0
  227. package/scss/config/mixins/_typography.scss +57 -0
  228. package/scss/config/mixins/_upload.scss +168 -0
  229. package/scss/ln-ashlar.scss +62 -0
  230. package/scss/tabler-icons.txt +5039 -0
  231. package/scss/utilities/_animations.scss +83 -0
  232. package/scss/utilities/_utilities.scss +49 -0
@@ -0,0 +1 @@
1
+ (function(){"use strict";function y(n,e,r){const o=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:r||{}});return n.dispatchEvent(o),o}function g(n,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){g(n,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}n()}function p(n,e,r,o){if(n.nodeType!==1)return;const u=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",t=Array.from(n.querySelectorAll(u));n.matches&&n.matches(u)&&t.push(n);for(const i of t)i[r]||(i[r]=new o(i))}function v(n,e,r,o,f={}){const u=f.extraAttributes||[],t=f.onAttributeChange||null,i=f.onInit||null;function c(l){const s=l||document.body;p(s,n,e,r),i&&i(s)}return g(function(){const l=new MutationObserver(function(d){for(let h=0;h<d.length;h++){const a=d[h];if(a.type==="childList")for(let m=0;m<a.addedNodes.length;m++){const b=a.addedNodes[m];b.nodeType===1&&(p(b,n,e,r),i&&i(b))}else a.type==="attributes"&&(t&&a.target[e]?t(a.target,a.attributeName):(p(a.target,n,e,r),i&&i(a.target)))}});let s=[];if(n.indexOf("[")!==-1){const d=/\[([\w-]+)/g;let h;for(;(h=d.exec(n))!==null;)s.push(h[1])}else s.push(n);l.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:s.concat(u)})},o),window[e]=c,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){c(document.body)}):c(document.body),c}const _={};function w(n,e){_[n]=e}function C(n){return _[n]||{ingress:e=>e,egress:e=>e}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=w,window.lnCore.getDataMapper=C),(function(){const n="data-ln-search",e="lnSearch",r="data-ln-search-initialized",o="data-ln-search-hide";if(window[e]!==void 0)return;function u(t){if(t.hasAttribute(r))return this;this.dom=t,this.targetId=t.getAttribute(n);const i=t.tagName;if(this.input=i==="INPUT"||i==="TEXTAREA"?t:t.querySelector('[name="search"]')||t.querySelector('input[type="search"]')||t.querySelector('input[type="text"]'),this.itemsSelector=t.getAttribute("data-ln-search-items")||null,this._debounceTimer=null,this._attachHandler(),this.input&&this.input.value.trim()){const c=this;queueMicrotask(function(){c._search(c.input.value.trim().toLowerCase())})}return t.setAttribute(r,""),this}u.prototype._attachHandler=function(){if(!this.input)return;const t=this;this._clearBtn=this.dom.querySelector("[data-ln-search-clear]"),this._clearBtn&&(this._onClear=function(){t.input.value="",t._search(""),t.input.focus()},this._clearBtn.addEventListener("click",this._onClear)),this._onInput=function(){clearTimeout(t._debounceTimer),t._debounceTimer=setTimeout(function(){t._search(t.input.value.trim().toLowerCase())},150)},this.input.addEventListener("input",this._onInput)},u.prototype._search=function(t){const i=document.getElementById(this.targetId);if(!i||y(i,"ln-search:change",{term:t,targetId:this.targetId}).defaultPrevented)return;const l=this.itemsSelector?i.querySelectorAll(this.itemsSelector):i.children;for(let s=0;s<l.length;s++){const d=l[s];d.removeAttribute(o),t&&!d.textContent.replace(/\s+/g," ").toLowerCase().includes(t)&&d.setAttribute(o,"true")}},u.prototype.destroy=function(){this.dom[e]&&(clearTimeout(this._debounceTimer),this.input&&this._onInput&&this.input.removeEventListener("input",this._onInput),this._clearBtn&&this._onClear&&this._clearBtn.removeEventListener("click",this._onClear),this.dom.removeAttribute(r),delete this.dom[e])},v(n,e,u,"ln-search")})()})();
@@ -0,0 +1,7 @@
1
+ // ==========================================================================
2
+ // ln-search — Text search component
3
+ // ==========================================================================
4
+
5
+ [data-ln-search-hide="true"] {
6
+ display: none !important;
7
+ }
@@ -0,0 +1,114 @@
1
+ import { dispatchCancelable, registerComponent } from '../../ln-core';
2
+
3
+ (function () {
4
+ const DOM_SELECTOR = 'data-ln-search';
5
+ const DOM_ATTRIBUTE = 'lnSearch';
6
+ const INIT_ATTR = 'data-ln-search-initialized';
7
+ const HIDE_ATTR = 'data-ln-search-hide';
8
+ const DEBOUNCE_MS = 150;
9
+
10
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
11
+
12
+ // ─── Component ─────────────────────────────────────────────
13
+
14
+ function _component(dom) {
15
+ if (dom.hasAttribute(INIT_ATTR)) return this;
16
+
17
+ this.dom = dom;
18
+ this.targetId = dom.getAttribute(DOM_SELECTOR);
19
+
20
+ // Support data-ln-search directly on <input> or on a wrapper element
21
+ const tag = dom.tagName;
22
+ this.input = (tag === 'INPUT' || tag === 'TEXTAREA') ? dom
23
+ : dom.querySelector('[name="search"]')
24
+ || dom.querySelector('input[type="search"]')
25
+ || dom.querySelector('input[type="text"]');
26
+
27
+ this.itemsSelector = dom.getAttribute('data-ln-search-items') || null;
28
+ this._debounceTimer = null;
29
+
30
+ this._attachHandler();
31
+
32
+ // Apply initial value (browser form restore may pre-fill the input).
33
+ // Deferred so all components finish init before the event dispatches.
34
+ if (this.input && this.input.value.trim()) {
35
+ const self = this;
36
+ queueMicrotask(function () {
37
+ self._search(self.input.value.trim().toLowerCase());
38
+ });
39
+ }
40
+
41
+ dom.setAttribute(INIT_ATTR, '');
42
+ return this;
43
+ }
44
+
45
+ // ─── Handler ───────────────────────────────────────────────
46
+
47
+ _component.prototype._attachHandler = function () {
48
+ if (!this.input) return;
49
+ const self = this;
50
+
51
+ // Clear button inside the wrapper
52
+ this._clearBtn = this.dom.querySelector('[data-ln-search-clear]');
53
+ if (this._clearBtn) {
54
+ this._onClear = function () {
55
+ self.input.value = '';
56
+ self._search('');
57
+ self.input.focus();
58
+ };
59
+ this._clearBtn.addEventListener('click', this._onClear);
60
+ }
61
+
62
+ this._onInput = function () {
63
+ clearTimeout(self._debounceTimer);
64
+ self._debounceTimer = setTimeout(function () {
65
+ self._search(self.input.value.trim().toLowerCase());
66
+ }, DEBOUNCE_MS);
67
+ };
68
+
69
+ this.input.addEventListener('input', this._onInput);
70
+ };
71
+
72
+ _component.prototype._search = function (term) {
73
+ const target = document.getElementById(this.targetId);
74
+ if (!target) return;
75
+
76
+ // Dispatch cancelable event on target.
77
+ // Consumers (e.g. ln-table) can call preventDefault() to handle filtering
78
+ // themselves and skip the default DOM show/hide behaviour.
79
+ const evt = dispatchCancelable(target, 'ln-search:change', { term: term, targetId: this.targetId });
80
+ if (evt.defaultPrevented) return;
81
+
82
+ // Default behaviour: show/hide items in target
83
+ // data-ln-search-items="selector" enables deep targeting via querySelectorAll
84
+ const children = this.itemsSelector
85
+ ? target.querySelectorAll(this.itemsSelector)
86
+ : target.children;
87
+
88
+ for (let i = 0; i < children.length; i++) {
89
+ const el = children[i];
90
+ el.removeAttribute(HIDE_ATTR);
91
+
92
+ if (term && !el.textContent.replace(/\s+/g, ' ').toLowerCase().includes(term)) {
93
+ el.setAttribute(HIDE_ATTR, 'true');
94
+ }
95
+ }
96
+ };
97
+
98
+ _component.prototype.destroy = function () {
99
+ if (!this.dom[DOM_ATTRIBUTE]) return;
100
+ clearTimeout(this._debounceTimer);
101
+ if (this.input && this._onInput) {
102
+ this.input.removeEventListener('input', this._onInput);
103
+ }
104
+ if (this._clearBtn && this._onClear) {
105
+ this._clearBtn.removeEventListener('click', this._onClear);
106
+ }
107
+ this.dom.removeAttribute(INIT_ATTR);
108
+ delete this.dom[DOM_ATTRIBUTE];
109
+ };
110
+
111
+ // ─── Init ──────────────────────────────────────────────────
112
+
113
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-search');
114
+ })();
@@ -0,0 +1,95 @@
1
+ # ln-sortable
2
+
3
+ A zero-dependency, high-performance **Drag & Drop Reordering Primitive** driven by browser Pointer Events APIs, designed for seamless mouse and touch interactions.
4
+
5
+ It focuses strictly on visual DOM restructuring, delegating server-side synchronization, database persistence, and list state saves completely to the parent coordinator via telemetry events.
6
+
7
+ ---
8
+
9
+ ## 🧭 Philosophy & Architecture
10
+
11
+ 1. **Pointer Concurrency:** Built natively on Pointer Events APIs (`pointerdown`, `pointermove`, `pointerup`). It replaces heavy HTML5 Drag & Drop frameworks, offering high-performance dragging and layout shifts across desktop, mobile, and hybrid touch displays.
12
+ 2. **Visual Isolation:** The primitive does not import or inject inline styles or layout rules. Instead, it exposes CSS state hooks (e.g. `.ln-sortable--dragging`) on active elements, leaving transitions and placeholders fully to the stylesheet.
13
+ 3. **HTML Attribute as Single Source of Truth:** Component states are governed entirely by the `data-ln-sortable` attribute. Standard JS API calls (`enable()`, `disable()`) write directly to the attribute, which is observed via `MutationObserver` to coordinate internals.
14
+
15
+ ---
16
+
17
+ ## 📦 Minimal Blueprint
18
+
19
+ ### Drag Anywhere List
20
+ Mark the container list directly with `data-ln-sortable`. All direct children become instantly sortable.
21
+ ```html
22
+ <ul data-ln-sortable>
23
+ <li>First Item</li>
24
+ <li>Second Item</li>
25
+ <li>Third Item</li>
26
+ </ul>
27
+ ```
28
+
29
+ ### Handles-Only List (Premium Touch-Safe)
30
+ For touch screens, limit drag triggers to a specific nested grab handle via `data-ln-sortable-handle`.
31
+ ```html
32
+ <ol data-ln-sortable>
33
+ <li>
34
+ <span data-ln-sortable-handle>
35
+ <svg class="ln-icon" aria-hidden="true"><use href="#ln-menu"></use></svg>
36
+ </span>
37
+ <span>Dashboard</span>
38
+ </li>
39
+ <li>
40
+ <span data-ln-sortable-handle>
41
+ <svg class="ln-icon" aria-hidden="true"><use href="#ln-menu"></use></svg>
42
+ </span>
43
+ <span>Users</span>
44
+ </li>
45
+ </ol>
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 🛠️ Declarative API Contract
51
+
52
+ ### HTML Attributes
53
+
54
+ | Attribute | Elements | Description |
55
+ | :--- | :--- | :--- |
56
+ | `data-ln-sortable` | Container (`<ul>`, `<ol>`, etc.) | Component root. Can be empty (enabled) or `"disabled"`. |
57
+ | `data-ln-sortable-handle` | Descendant of list item | Identifies drag trigger. When present, pointer clicks outside fail to drag. |
58
+
59
+ ### CSS Class Hooks
60
+
61
+ The component toggles these visual state classes at runtime:
62
+
63
+ | Class | Element | Description |
64
+ | :--- | :--- | :--- |
65
+ | `.ln-sortable--active` | Container | Drag session in progress. Pointer events on text selection are locked. |
66
+ | `.ln-sortable--dragging` | Dragged item | The item currently being dragged. Typically styled with reduced opacity. |
67
+ | `.ln-sortable--drop-before` | Neighbor item | Target item top-half placeholder. Highlight top border. |
68
+ | `.ln-sortable--drop-after` | Neighbor item | Target item bottom-half placeholder. Highlight bottom border. |
69
+
70
+ ---
71
+
72
+ ## ⚡ DOM Events
73
+
74
+ All events bubble from the container element.
75
+
76
+ ### `ln-sortable:before-drag`
77
+ Fired when pointer down triggers drag.
78
+ - **Cancelable**: Yes. Call `e.preventDefault()` to cancel the drag action (e.g. list is locked).
79
+ - **Payload (`detail`)**: `{ item: HTMLElement, index: number }`
80
+
81
+ ### `ln-sortable:reordered`
82
+ Fired when an item drop successfully changes the DOM index order.
83
+ - **Payload (`detail`)**: `{ item: HTMLElement, oldIndex: number, newIndex: number }`
84
+
85
+ ---
86
+
87
+ ## ⚠️ Common Pitfalls
88
+
89
+ - **Missing `touch-action: none`:** Drag handles must carry `touch-action: none` in CSS. Failing to add this prevents mobile browsers from scrolling, causing the browser to capture pointer tracks and breaking touch drags.
90
+ - **Auto-Sync Assumptions:** The primitive does not call servers automatically. You must wire an `ln-sortable:reordered` listener and dispatch your sync request (e.g. via `ln-http`):
91
+ ```javascript
92
+ document.addEventListener('ln-sortable:reordered', function(e) {
93
+ saveNewListOrder(e.target.id, e.detail.oldIndex, e.detail.newIndex);
94
+ });
95
+ ```
@@ -0,0 +1 @@
1
+ (function(){"use strict";function m(i,n,f){i.dispatchEvent(new CustomEvent(n,{bubbles:!0,detail:f||{}}))}function w(i,n,f){const d=new CustomEvent(n,{bubbles:!0,cancelable:!0,detail:f||{}});return i.dispatchEvent(d),d}function v(i,n){if(!document.body){document.addEventListener("DOMContentLoaded",function(){v(i,n)}),console.warn("["+n+'] Script loaded before <body> — add "defer" to your <script> tag');return}i()}function p(i,n,f,d){if(i.nodeType!==1)return;const r=n.indexOf("[")!==-1||n.indexOf(".")!==-1||n.indexOf("#")!==-1?n:"["+n+"]",t=Array.from(i.querySelectorAll(r));i.matches&&i.matches(r)&&t.push(i);for(const e of t)e[f]||(e[f]=new d(e))}function y(i,n,f,d,h={}){const r=h.extraAttributes||[],t=h.onAttributeChange||null,e=h.onInit||null;function a(o){const l=o||document.body;p(l,i,n,f),e&&e(l)}return v(function(){const o=new MutationObserver(function(s){for(let u=0;u<s.length;u++){const c=s[u];if(c.type==="childList")for(let b=0;b<c.addedNodes.length;b++){const g=c.addedNodes[b];g.nodeType===1&&(p(g,i,n,f),e&&e(g))}else c.type==="attributes"&&(t&&c.target[n]?t(c.target,c.attributeName):(p(c.target,i,n,f),e&&e(c.target)))}});let l=[];if(i.indexOf("[")!==-1){const s=/\[([\w-]+)/g;let u;for(;(u=s.exec(i))!==null;)l.push(u[1])}else l.push(i);o.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:l.concat(r)})},d),window[n]=a,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){a(document.body)}):a(document.body),a}const E={};function L(i,n){E[i]=n}function A(i){return E[i]||{ingress:n=>n,egress:n=>n}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=L,window.lnCore.getDataMapper=A),(function(){const i="data-ln-sortable",n="lnSortable",f="data-ln-sortable-handle";if(window[n]!==void 0)return;function d(r){this.dom=r,this.isEnabled=r.getAttribute(i)!=="disabled",this._dragging=null,r.setAttribute("aria-roledescription","sortable list");const t=this;return this._onPointerDown=function(e){t.isEnabled&&t._handlePointerDown(e)},r.addEventListener("pointerdown",this._onPointerDown),this}d.prototype.enable=function(){this.isEnabled||this.dom.setAttribute(i,"")},d.prototype.disable=function(){this.isEnabled&&this.dom.setAttribute(i,"disabled")},d.prototype.destroy=function(){this.dom[n]&&(this.dom.removeEventListener("pointerdown",this._onPointerDown),m(this.dom,"ln-sortable:destroyed",{target:this.dom}),delete this.dom[n])},d.prototype._handlePointerDown=function(r){let t=r.target.closest("["+f+"]"),e;if(t){for(e=t;e&&e.parentElement!==this.dom;)e=e.parentElement;if(!e||e.parentElement!==this.dom)return}else{if(this.dom.querySelector("["+f+"]"))return;for(e=r.target;e&&e.parentElement!==this.dom;)e=e.parentElement;if(!e||e.parentElement!==this.dom)return;t=e}const o=Array.from(this.dom.children).indexOf(e);if(w(this.dom,"ln-sortable:before-drag",{item:e,index:o}).defaultPrevented)return;r.preventDefault(),t.setPointerCapture(r.pointerId),this._dragging=e,e.classList.add("ln-sortable--dragging"),e.setAttribute("aria-grabbed","true"),this.dom.classList.add("ln-sortable--active"),m(this.dom,"ln-sortable:drag-start",{item:e,index:o});const s=this,u=function(b){s._handlePointerMove(b)},c=function(b){s._handlePointerEnd(b),t.removeEventListener("pointermove",u),t.removeEventListener("pointerup",c),t.removeEventListener("pointercancel",c)};t.addEventListener("pointermove",u),t.addEventListener("pointerup",c),t.addEventListener("pointercancel",c)},d.prototype._handlePointerMove=function(r){if(!this._dragging)return;const t=Array.from(this.dom.children),e=this._dragging;for(const a of t)a.classList.remove("ln-sortable--drop-before","ln-sortable--drop-after");for(const a of t){if(a===e)continue;const o=a.getBoundingClientRect(),l=o.top+o.height/2;if(r.clientY>=o.top&&r.clientY<l){a.classList.add("ln-sortable--drop-before");break}else if(r.clientY>=l&&r.clientY<=o.bottom){a.classList.add("ln-sortable--drop-after");break}}},d.prototype._handlePointerEnd=function(r){if(!this._dragging)return;const t=this._dragging,e=Array.from(this.dom.children),a=e.indexOf(t);let o=null,l=null;for(const s of e){if(s.classList.contains("ln-sortable--drop-before")){o=s,l="before";break}if(s.classList.contains("ln-sortable--drop-after")){o=s,l="after";break}}for(const s of e)s.classList.remove("ln-sortable--drop-before","ln-sortable--drop-after");if(t.classList.remove("ln-sortable--dragging"),t.removeAttribute("aria-grabbed"),this.dom.classList.remove("ln-sortable--active"),o&&o!==t){l==="before"?this.dom.insertBefore(t,o):this.dom.insertBefore(t,o.nextElementSibling);const u=Array.from(this.dom.children).indexOf(t);m(this.dom,"ln-sortable:reordered",{item:t,oldIndex:a,newIndex:u})}this._dragging=null};function h(r){const t=r[n];if(!t)return;const e=r.getAttribute(i)!=="disabled";e!==t.isEnabled&&(t.isEnabled=e,m(r,e?"ln-sortable:enabled":"ln-sortable:disabled",{target:r}))}y(i,n,d,"ln-sortable",{onAttributeChange:h})})()})();
@@ -0,0 +1,203 @@
1
+ import { registerComponent, dispatch, dispatchCancelable } from '../../ln-core';
2
+
3
+ (function () {
4
+ 'use strict';
5
+
6
+ const DOM_SELECTOR = 'data-ln-sortable';
7
+ const DOM_ATTRIBUTE = 'lnSortable';
8
+ const HANDLE_ATTR = 'data-ln-sortable-handle';
9
+
10
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
11
+
12
+ // ─── Component ─────────────────────────────────────────────
13
+
14
+ function _component(dom) {
15
+ this.dom = dom;
16
+ this.isEnabled = dom.getAttribute(DOM_SELECTOR) !== 'disabled';
17
+ this._dragging = null;
18
+
19
+ dom.setAttribute('aria-roledescription', 'sortable list');
20
+
21
+ const self = this;
22
+
23
+ this._onPointerDown = function (e) {
24
+ if (!self.isEnabled) return;
25
+ self._handlePointerDown(e);
26
+ };
27
+ dom.addEventListener('pointerdown', this._onPointerDown);
28
+
29
+ return this;
30
+ }
31
+
32
+ // ─── Public API ────────────────────────────────────────────
33
+
34
+ _component.prototype.enable = function () {
35
+ if (this.isEnabled) return;
36
+ this.dom.setAttribute(DOM_SELECTOR, '');
37
+ };
38
+
39
+ _component.prototype.disable = function () {
40
+ if (!this.isEnabled) return;
41
+ this.dom.setAttribute(DOM_SELECTOR, 'disabled');
42
+ };
43
+
44
+ _component.prototype.destroy = function () {
45
+ if (!this.dom[DOM_ATTRIBUTE]) return;
46
+ this.dom.removeEventListener('pointerdown', this._onPointerDown);
47
+ dispatch(this.dom, 'ln-sortable:destroyed', { target: this.dom });
48
+ delete this.dom[DOM_ATTRIBUTE];
49
+ };
50
+
51
+ // ─── Pointer Handlers ──────────────────────────────────────
52
+
53
+ _component.prototype._handlePointerDown = function (e) {
54
+ let handle = e.target.closest('[' + HANDLE_ATTR + ']');
55
+ let item;
56
+
57
+ if (handle) {
58
+ item = handle;
59
+ while (item && item.parentElement !== this.dom) {
60
+ item = item.parentElement;
61
+ }
62
+ if (!item || item.parentElement !== this.dom) return;
63
+ } else {
64
+ if (this.dom.querySelector('[' + HANDLE_ATTR + ']')) return;
65
+
66
+ item = e.target;
67
+ while (item && item.parentElement !== this.dom) {
68
+ item = item.parentElement;
69
+ }
70
+ if (!item || item.parentElement !== this.dom) return;
71
+ handle = item;
72
+ }
73
+
74
+ const children = Array.from(this.dom.children);
75
+ const index = children.indexOf(item);
76
+
77
+ const before = dispatchCancelable(this.dom, 'ln-sortable:before-drag', {
78
+ item: item,
79
+ index: index
80
+ });
81
+ if (before.defaultPrevented) return;
82
+
83
+ e.preventDefault();
84
+ handle.setPointerCapture(e.pointerId);
85
+
86
+ this._dragging = item;
87
+
88
+ item.classList.add('ln-sortable--dragging');
89
+ item.setAttribute('aria-grabbed', 'true');
90
+ this.dom.classList.add('ln-sortable--active');
91
+
92
+ dispatch(this.dom, 'ln-sortable:drag-start', {
93
+ item: item,
94
+ index: index
95
+ });
96
+
97
+ const self = this;
98
+ const onMove = function (ev) { self._handlePointerMove(ev); };
99
+ const onEnd = function (ev) {
100
+ self._handlePointerEnd(ev);
101
+ handle.removeEventListener('pointermove', onMove);
102
+ handle.removeEventListener('pointerup', onEnd);
103
+ handle.removeEventListener('pointercancel', onEnd);
104
+ };
105
+
106
+ handle.addEventListener('pointermove', onMove);
107
+ handle.addEventListener('pointerup', onEnd);
108
+ handle.addEventListener('pointercancel', onEnd);
109
+ };
110
+
111
+ _component.prototype._handlePointerMove = function (e) {
112
+ if (!this._dragging) return;
113
+
114
+ const children = Array.from(this.dom.children);
115
+ const dragging = this._dragging;
116
+
117
+ for (const child of children) {
118
+ child.classList.remove('ln-sortable--drop-before', 'ln-sortable--drop-after');
119
+ }
120
+
121
+ for (const child of children) {
122
+ if (child === dragging) continue;
123
+
124
+ const rect = child.getBoundingClientRect();
125
+ const mid = rect.top + rect.height / 2;
126
+
127
+ if (e.clientY >= rect.top && e.clientY < mid) {
128
+ child.classList.add('ln-sortable--drop-before');
129
+ break;
130
+ } else if (e.clientY >= mid && e.clientY <= rect.bottom) {
131
+ child.classList.add('ln-sortable--drop-after');
132
+ break;
133
+ }
134
+ }
135
+ };
136
+
137
+ _component.prototype._handlePointerEnd = function (e) {
138
+ if (!this._dragging) return;
139
+
140
+ const item = this._dragging;
141
+ const children = Array.from(this.dom.children);
142
+ const oldIndex = children.indexOf(item);
143
+
144
+ let dropTarget = null;
145
+ let dropPosition = null;
146
+
147
+ for (const child of children) {
148
+ if (child.classList.contains('ln-sortable--drop-before')) {
149
+ dropTarget = child;
150
+ dropPosition = 'before';
151
+ break;
152
+ }
153
+ if (child.classList.contains('ln-sortable--drop-after')) {
154
+ dropTarget = child;
155
+ dropPosition = 'after';
156
+ break;
157
+ }
158
+ }
159
+
160
+ for (const child of children) {
161
+ child.classList.remove('ln-sortable--drop-before', 'ln-sortable--drop-after');
162
+ }
163
+ item.classList.remove('ln-sortable--dragging');
164
+ item.removeAttribute('aria-grabbed');
165
+ this.dom.classList.remove('ln-sortable--active');
166
+
167
+ if (dropTarget && dropTarget !== item) {
168
+ if (dropPosition === 'before') {
169
+ this.dom.insertBefore(item, dropTarget);
170
+ } else {
171
+ this.dom.insertBefore(item, dropTarget.nextElementSibling);
172
+ }
173
+
174
+ const newChildren = Array.from(this.dom.children);
175
+ const newIndex = newChildren.indexOf(item);
176
+
177
+ dispatch(this.dom, 'ln-sortable:reordered', {
178
+ item: item,
179
+ oldIndex: oldIndex,
180
+ newIndex: newIndex
181
+ });
182
+ }
183
+
184
+ this._dragging = null;
185
+ };
186
+
187
+ // ─── Attribute Sync ────────────────────────────────────────
188
+
189
+ function _syncEnabled(el) {
190
+ const instance = el[DOM_ATTRIBUTE];
191
+ if (!instance) return;
192
+ const shouldBeEnabled = el.getAttribute(DOM_SELECTOR) !== 'disabled';
193
+ if (shouldBeEnabled === instance.isEnabled) return;
194
+ instance.isEnabled = shouldBeEnabled;
195
+ dispatch(el, shouldBeEnabled ? 'ln-sortable:enabled' : 'ln-sortable:disabled', { target: el });
196
+ }
197
+
198
+ // ─── Init ──────────────────────────────────────────────────
199
+
200
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-sortable', {
201
+ onAttributeChange: _syncEnabled
202
+ });
203
+ })();
@@ -0,0 +1,101 @@
1
+ # ln-table
2
+
3
+ A zero-dependency, high-performance **Server-Rendered Table Primitive** that enhances standard, pre-populated HTML tables in-place. It parses existing DOM `<tbody>` elements once on load and layers ultra-fast, client-side in-memory sorting, column filtering, text searches, and scroll virtualization on top of pre-rendered markup.
4
+
5
+ ---
6
+
7
+ ## 🧭 Philosophy & Architecture
8
+
9
+ 1. **`ln-table` vs `ln-data-table`:**
10
+ - Use `ln-table` when rows are already printed in `<tbody>` by your backend server on page load. It parses existing elements and manipulates visibility in-place.
11
+ - Use `ln-data-table` when your data source is an active JS array (such as an `ln-store` IndexedDB cache or REST fetch API) and needs to clone structural templates.
12
+ 2. **Platform-First Search Indexing:** Search and column filters scan the visible display texts of cells, whereas sorting reads numeric or date values defined in `data-ln-value` overrides.
13
+ 3. **Actions Column Exclusivity:** The last cell (`<td>`) of each row is automatically ignored during search indexes. This prevents buttons (e.g. Edit / Delete) from polluting search results.
14
+
15
+ ---
16
+
17
+ ## 📦 Minimal Blueprint
18
+
19
+ ```html
20
+ <div id="employees-table" data-ln-table>
21
+ <!-- Empty State Template -->
22
+ <template data-ln-table-empty>
23
+ <article class="ln-table__empty-state">
24
+ <h3>No matches found</h3>
25
+ <button type="button" data-ln-table-clear>Clear filters</button>
26
+ </article>
27
+ </template>
28
+
29
+ <table>
30
+ <thead>
31
+ <tr>
32
+ <th data-ln-sort="string">Name</th>
33
+ <th data-ln-sort="number">Salary</th>
34
+ </tr>
35
+ </thead>
36
+ <tbody>
37
+ <tr>
38
+ <td>Dalibor Sojic</td>
39
+ <td data-ln-value="120000">$120,000</td>
40
+ </tr>
41
+ </tbody>
42
+ </table>
43
+ </div>
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 🛠️ Declarative API Contract
49
+
50
+ ### HTML Attributes
51
+
52
+ | Attribute | Elements | Description |
53
+ | :--- | :--- | :--- |
54
+ | `data-ln-table` | `<div>` wrapper | Root marker. Target must carry a unique `id`. |
55
+ | `data-ln-sort="type"` | `<th>` | Marks column sortable. Values: `string`, `number`, `date`. |
56
+ | `data-ln-value="raw"` | `<td>` | Raw value used exclusively by sort comparators. |
57
+ | `data-ln-filter-col="key"` | `<th>` | Maps the column to an active `ln-filter` key. |
58
+ | `data-ln-table-clear` | `<button>` | Click delegate. Instantly clears search query and active filters. |
59
+
60
+ ### JS API
61
+
62
+ Access the parsed database model directly via the `lnTable` property on the wrapper element:
63
+
64
+ ```javascript
65
+ const table = document.getElementById('employees-table');
66
+
67
+ // 1. Inspect parsed arrays
68
+ const cachedRows = table.lnTable._data; // All parsed DOM rows
69
+ const visibleRows = table.lnTable._filteredData; // Current matches
70
+
71
+ // 2. Tear down listeners and unlock column widths
72
+ table.lnTable.destroy();
73
+ ```
74
+
75
+ ---
76
+
77
+ ## ⚡ DOM Events
78
+
79
+ ### Emitted
80
+
81
+ | Event | Payload | Description |
82
+ | :--- | :--- | :--- |
83
+ | `ln-table:ready` | `{ total }` | Dispatched after the initial DOM parse is complete. |
84
+ | `ln-table:filter` | `{ term, matched, total }` | Dispatched after any search or filter state updates. |
85
+ | `ln-table:sorted` | `{ column, direction }` | Dispatched after sorting is recalculated in-place. |
86
+
87
+ ### Received
88
+
89
+ `ln-table` coordinates with sibling UI primitives through standard DOM event channels:
90
+
91
+ - **Listens to `ln-search:change`:** Updates text queries and filters the DOM.
92
+ - **Listens to `ln-table:sort`:** Triggers structural sorting in-place.
93
+ - **Listens to `ln-filter:changed`:** Triggers column filters and highlights headers.
94
+
95
+ ---
96
+
97
+ ## ⚠️ Common Pitfalls
98
+
99
+ - **Bypassing the ID Requirement:** Sibling search controls (`ln-search`) target wrappers by calling `getElementById(targetId)`. The table wrapper **must** carry a unique `id`.
100
+ - **Dynamic Row Replacements:** Modifying cell values after initial paint using `innerHTML` will be lost. The script caches rows as static strings on page load. Use `ln-data-table` instead for dynamic writes.
101
+ - **Scroll Container Wrapping:** Do not wrap tables in scroll trapping boxes (`overflow-y: auto`). Scroll virtualization and sticky headers automatically bind to the outer window scroll bounds.
@@ -0,0 +1 @@
1
+ (function(){"use strict";function _(t,o,l){t.dispatchEvent(new CustomEvent(o,{bubbles:!0,detail:l||{}}))}function d(t,o){if(!document.body){document.addEventListener("DOMContentLoaded",function(){d(t,o)}),console.warn("["+o+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}const f={};function g(t,o){f[t]=o}function w(t){return f[t]||{ingress:o=>o,egress:o=>o}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=g,window.lnCore.getDataMapper=w);const E="ln:";function S(){return location.pathname.replace(/\/+$/,"").toLowerCase()||"/"}function p(t,o){const l=o.getAttribute("data-ln-persist"),c=l!==null&&l!==""?l:o.id;return c?E+t+":"+S()+":"+c:(console.warn('[ln-persist] Element requires id or data-ln-persist="key"',o),null)}function v(t,o){const l=p(t,o);if(!l)return null;try{const c=localStorage.getItem(l);return c!==null?JSON.parse(c):null}catch{return null}}function b(t,o,l){const c=p(t,o);if(c)try{localStorage.setItem(c,JSON.stringify(l))}catch{}}(function(){const t="lnTableSort",o="data-ln-sort",l="data-ln-sort-active";if(window[t]!==void 0)return;function c(i){a(i)}function a(i){const r=Array.from(i.querySelectorAll("table"));i.tagName==="TABLE"&&r.push(i),r.forEach(function(e){if(e[t])return;const s=Array.from(e.querySelectorAll("th["+o+"]"));s.length&&(e[t]=new u(e,s))})}function h(i,r){i.querySelectorAll("[data-ln-sort-icon]").forEach(function(s){const n=s.getAttribute("data-ln-sort-icon");r==null?s.classList.toggle("hidden",n!==null&&n!==""):s.classList.toggle("hidden",n!==r)})}function u(i,r){this.table=i,this.ths=r,this._col=-1,this._dir=null;const e=this;r.forEach(function(n,m){n[t+"Bound"]||(n[t+"Bound"]=!0,n._lnSortClick=function(A){const y=A.target.closest("button, a, input, select, textarea, [data-ln-dropdown]");y&&y!==n||e._handleClick(m,n)},n.addEventListener("click",n._lnSortClick))});const s=i.closest("[data-ln-table][data-ln-persist]");if(s){const n=v("table-sort",s);n&&n.dir&&n.col>=0&&n.col<r.length&&(this._handleClick(n.col,r[n.col]),n.dir==="desc"&&this._handleClick(n.col,r[n.col]))}return this}u.prototype._handleClick=function(i,r){let e;this._col!==i?e="asc":this._dir==="asc"?e="desc":this._dir==="desc"?e=null:e="asc",this.ths.forEach(function(n){n.removeAttribute(l),h(n,null)}),e===null?(this._col=-1,this._dir=null):(this._col=i,this._dir=e,r.setAttribute(l,e),h(r,e)),_(this.table,"ln-table:sort",{column:i,sortType:r.getAttribute(o),direction:e});const s=this.table.closest("[data-ln-table][data-ln-persist]");s&&(e===null?b("table-sort",s,null):b("table-sort",s,{col:i,dir:e}))},u.prototype.destroy=function(){this.table[t]&&(this.ths.forEach(function(i){i._lnSortClick&&(i.removeEventListener("click",i._lnSortClick),delete i._lnSortClick),delete i[t+"Bound"]}),delete this.table[t])};function C(){d(function(){new MutationObserver(function(r){r.forEach(function(e){e.type==="childList"?e.addedNodes.forEach(function(s){s.nodeType===1&&a(s)}):e.type==="attributes"&&a(e.target)})}).observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[o]})},"ln-table-sort")}window[t]=c,C(),document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){c(document.body)}):c(document.body)})()})();
@@ -0,0 +1 @@
1
+ (function(){"use strict";function m(d,i,p){d.dispatchEvent(new CustomEvent(i,{bubbles:!0,detail:p||{}}))}function C(d,i){if(!document.body){document.addEventListener("DOMContentLoaded",function(){C(d,i)}),console.warn("["+i+'] Script loaded before <body> — add "defer" to your <script> tag');return}d()}function T(d,i,p,g){if(d.nodeType!==1)return;const w=i.indexOf("[")!==-1||i.indexOf(".")!==-1||i.indexOf("#")!==-1?i:"["+i+"]",b=Array.from(d.querySelectorAll(w));d.matches&&d.matches(w)&&b.push(d);for(const c of b)c[p]||(c[p]=new g(c))}function A(d,i,p,g,v={}){const w=v.extraAttributes||[],b=v.onAttributeChange||null,c=v.onInit||null;function e(l){const t=l||document.body;T(t,d,i,p),c&&c(t)}return C(function(){const l=new MutationObserver(function(n){for(let h=0;h<n.length;h++){const s=n[h];if(s.type==="childList")for(let u=0;u<s.addedNodes.length;u++){const o=s.addedNodes[u];o.nodeType===1&&(T(o,d,i,p),c&&c(o))}else s.type==="attributes"&&(b&&s.target[i]?b(s.target,s.attributeName):(T(s.target,d,i,p),c&&c(s.target)))}});let t=[];if(d.indexOf("[")!==-1){const n=/\[([\w-]+)/g;let h;for(;(h=n.exec(d))!==null;)t.push(h[1])}else t.push(d);l.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:t.concat(w)})},g),window[i]=e,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){e(document.body)}):e(document.body),e}const L={};function D(d,i){L[d]=i}function O(d){return L[d]||{ingress:i=>i,egress:i=>i}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=D,window.lnCore.getDataMapper=O),(function(){const d="data-ln-table",i="lnTable",p="data-ln-sort",g="data-ln-table-empty";if(window[i]!==void 0)return;const b=typeof Intl<"u"?new Intl.Collator(document.documentElement.lang||void 0,{sensitivity:"base"}):null;function c(e){this.dom=e,this.table=e.querySelector("table"),this.tbody=e.querySelector("tbody"),this.thead=e.querySelector("thead");const l=this.thead?this.thead.querySelector("tr:last-child"):null;this.ths=l?Array.from(l.querySelectorAll("th")):[],this._data=[],this._filteredData=[],this._searchTerm="",this._sortCol=-1,this._sortDir=null,this._sortType=null,this._columnFilters={},this._virtual=!1,this._rowHeight=0,this._vStart=-1,this._vEnd=-1,this._rafId=null,this._scrollHandler=null,this._colgroup=null;const t=this;return this._emptyTbodyObserver=null,this.tbody&&this.tbody.rows.length>0?this._parseRows():this.tbody&&(this._emptyTbodyObserver=new MutationObserver(function(){t.tbody.rows.length>0&&(t._emptyTbodyObserver.disconnect(),t._emptyTbodyObserver=null,t._parseRows())}),this._emptyTbodyObserver.observe(this.tbody,{childList:!0})),this._onSearch=function(n){n.preventDefault(),t._searchTerm=n.detail.term,t._applyFilterAndSort(),t._vStart=-1,t._vEnd=-1,t._render(),m(e,"ln-table:filter",{term:t._searchTerm,matched:t._filteredData.length,total:t._data.length})},e.addEventListener("ln-search:change",this._onSearch),this._onSort=function(n){t._sortCol=n.detail.direction===null?-1:n.detail.column,t._sortDir=n.detail.direction,t._sortType=n.detail.sortType,t._applyFilterAndSort(),t._vStart=-1,t._vEnd=-1,t._render(),m(e,"ln-table:sorted",{column:n.detail.column,direction:n.detail.direction,matched:t._filteredData.length,total:t._data.length})},e.addEventListener("ln-table:sort",this._onSort),this._onColumnFilter=function(n){const h=n.detail.key;let s=!1;for(let r=0;r<t.ths.length;r++)if(t.ths[r].getAttribute("data-ln-filter-col")===h){s=!0;break}if(!s)return;const u=n.detail.values;if(!u||u.length===0)delete t._columnFilters[h];else{const r=[];for(let a=0;a<u.length;a++)r.push(u[a].toLowerCase());t._columnFilters[h]=r}const o=t.dom.querySelector('th[data-ln-filter-col="'+h+'"]');o&&(u&&u.length>0?o.setAttribute("data-ln-filter-active",""):o.removeAttribute("data-ln-filter-active")),t._applyFilterAndSort(),t._vStart=-1,t._vEnd=-1,t._render(),m(e,"ln-table:filter",{term:t._searchTerm,matched:t._filteredData.length,total:t._data.length})},e.addEventListener("ln-filter:changed",this._onColumnFilter),this._onClear=function(n){if(!n.target.closest("[data-ln-table-clear]"))return;t._searchTerm="";const s=document.querySelector('[data-ln-search="'+e.id+'"]');if(s){const o=s.tagName==="INPUT"?s:s.querySelector("input");o&&(o.value="")}t._columnFilters={};for(let o=0;o<t.ths.length;o++)t.ths[o].removeAttribute("data-ln-filter-active");const u=document.querySelectorAll('[data-ln-filter="'+e.id+'"]');for(let o=0;o<u.length;o++){const r=u[o].querySelector("[data-ln-filter-reset]");r&&(r.checked=!0,r.dispatchEvent(new Event("change",{bubbles:!0})))}t._applyFilterAndSort(),t._vStart=-1,t._vEnd=-1,t._render(),m(e,"ln-table:filter",{term:"",matched:t._filteredData.length,total:t._data.length})},e.addEventListener("click",this._onClear),this}c.prototype._parseRows=function(){const e=this.tbody.rows,l=this.ths;this._data=[];const t=[];for(let n=0;n<l.length;n++)t[n]=l[n].getAttribute(p);e.length>0&&(this._rowHeight=e[0].offsetHeight||40),this._lockColumnWidths();for(let n=0;n<e.length;n++){const h=e[n],s=[],u=[],o=[];for(let r=0;r<h.cells.length;r++){const a=h.cells[r],f=a.textContent.trim(),_=a.hasAttribute("data-ln-value")?a.getAttribute("data-ln-value"):f,y=t[r];u[r]=f.toLowerCase(),y==="number"||y==="date"?s[r]=parseFloat(_)||0:y==="string"?s[r]=String(_):s[r]=null,r<h.cells.length-1&&o.push(f.toLowerCase())}this._data.push({sortKeys:s,rawTexts:u,html:h.outerHTML,searchText:o.join(" ")})}this._filteredData=this._data.slice(),this._render(),m(this.dom,"ln-table:ready",{total:this._data.length})},c.prototype._applyFilterAndSort=function(){const e=this._searchTerm,l=this._columnFilters,t=Object.keys(l).length>0,n=this.ths,h={};if(t)for(let a=0;a<n.length;a++){const f=n[a].getAttribute("data-ln-filter-col");f&&(h[f]=a)}if(!e&&!t?this._filteredData=this._data.slice():this._filteredData=this._data.filter(function(a){if(e&&a.searchText.indexOf(e)===-1)return!1;if(t)for(const f in l){const _=h[f];if(_!==void 0&&l[f].indexOf(a.rawTexts[_])===-1)return!1}return!0}),this._sortCol<0||!this._sortDir)return;const s=this._sortCol,u=this._sortDir==="desc"?-1:1,o=this._sortType==="number"||this._sortType==="date",r=b?b.compare:function(a,f){return a<f?-1:a>f?1:0};this._filteredData.sort(function(a,f){const _=a.sortKeys[s],y=f.sortKeys[s];return o?(_-y)*u:r(_,y)*u})},c.prototype._lockColumnWidths=function(){if(!this.table||!this.thead||this._colgroup)return;const e=document.createElement("colgroup");this.ths.forEach(function(l){const t=document.createElement("col");t.style.width=l.offsetWidth+"px",e.appendChild(t)}),this.table.insertBefore(e,this.table.firstChild),this.table.style.tableLayout="fixed",this._colgroup=e},c.prototype._render=function(){if(!this.tbody)return;const e=this._filteredData.length;e===0&&(this._searchTerm||Object.keys(this._columnFilters).length>0)?(this._disableVirtualScroll(),this._showEmptyState()):e>200?(this._enableVirtualScroll(),this._renderVirtual()):(this._disableVirtualScroll(),this._renderAll())},c.prototype._renderAll=function(){const e=[],l=this._filteredData;for(let t=0;t<l.length;t++)e.push(l[t].html);this.tbody.innerHTML=e.join("")},c.prototype._enableVirtualScroll=function(){if(this._virtual)return;this._virtual=!0;const e=this;this._scrollHandler=function(){e._rafId||(e._rafId=requestAnimationFrame(function(){e._rafId=null,e._renderVirtual()}))},window.addEventListener("scroll",this._scrollHandler,{passive:!0}),window.addEventListener("resize",this._scrollHandler,{passive:!0})},c.prototype._disableVirtualScroll=function(){this._virtual&&(this._virtual=!1,this._scrollHandler&&(window.removeEventListener("scroll",this._scrollHandler),window.removeEventListener("resize",this._scrollHandler),this._scrollHandler=null),this._rafId&&(cancelAnimationFrame(this._rafId),this._rafId=null),this._vStart=-1,this._vEnd=-1)},c.prototype._renderVirtual=function(){const e=this._filteredData,l=e.length,t=this._rowHeight;if(!t||!l)return;const h=this.table.getBoundingClientRect().top+window.scrollY,s=this.thead?this.thead.offsetHeight:0,u=h+s,o=window.scrollY-u,r=Math.max(0,Math.floor(o/t)-15),a=Math.min(r+Math.ceil(window.innerHeight/t)+30,l);if(r===this._vStart&&a===this._vEnd)return;this._vStart=r,this._vEnd=a;const f=this.ths.length||1,_=r*t,y=(l-a)*t;let S="";_>0&&(S+='<tr class="ln-table__spacer" aria-hidden="true"><td colspan="'+f+'" style="height:'+_+'px;padding:0;border:none"></td></tr>');for(let E=r;E<a;E++)S+=e[E].html;y>0&&(S+='<tr class="ln-table__spacer" aria-hidden="true"><td colspan="'+f+'" style="height:'+y+'px;padding:0;border:none"></td></tr>'),this.tbody.innerHTML=S},c.prototype._showEmptyState=function(){const e=this.ths.length||1,l=this.dom.querySelector("template["+g+"]"),t=document.createElement("td");t.setAttribute("colspan",String(e)),l&&t.appendChild(document.importNode(l.content,!0));const n=document.createElement("tr");n.className="ln-table__empty",n.appendChild(t),this.tbody.innerHTML="",this.tbody.appendChild(n),m(this.dom,"ln-table:empty",{term:this._searchTerm,total:this._data.length})},c.prototype.destroy=function(){this.dom[i]&&(this._disableVirtualScroll(),this._emptyTbodyObserver&&(this._emptyTbodyObserver.disconnect(),this._emptyTbodyObserver=null),this.dom.removeEventListener("ln-search:change",this._onSearch),this.dom.removeEventListener("ln-table:sort",this._onSort),this.dom.removeEventListener("ln-filter:changed",this._onColumnFilter),this.dom.removeEventListener("click",this._onClear),this._colgroup&&(this._colgroup.remove(),this._colgroup=null),this.table&&(this.table.style.tableLayout=""),this._data=[],this._filteredData=[],delete this.dom[i])},A(d,i,c,"ln-table")})()})();
@@ -0,0 +1,11 @@
1
+ // ── JS state: ln-table structural/state overrides ──
2
+
3
+ // Fix: .content overflow blocks sticky positioning
4
+ .content:has([data-ln-table]) {
5
+ overflow: visible;
6
+ }
7
+
8
+ // Sort icon visibility toggled by JS (.hidden class)
9
+ [data-ln-table] th[data-ln-sort] [data-ln-sort-icon].hidden {
10
+ display: none;
11
+ }