@livenetworks/ashlar 1.4.1 → 1.6.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 (219) hide show
  1. package/README.md +189 -177
  2. package/js/COMPONENTS.md +55 -49
  3. package/js/index.js +22 -5
  4. package/js/ln-accordion/README.md +186 -137
  5. package/js/ln-accordion/ln-accordion.js +1 -1
  6. package/js/ln-ajax/README.md +73 -4
  7. package/js/ln-ajax/ln-ajax.js +1 -1
  8. package/js/ln-ajax/src/ln-ajax.js +13 -13
  9. package/js/ln-api-connector/README.md +261 -150
  10. package/js/ln-api-connector/ln-api-connector.js +1 -1
  11. package/js/ln-api-connector/src/ln-api-connector.js +368 -265
  12. package/js/ln-api-queue/README.md +219 -0
  13. package/js/ln-api-queue/ln-api-queue.js +1 -0
  14. package/js/ln-api-queue/src/ln-api-queue.js +320 -0
  15. package/js/ln-api-queue/src/queue-storage.js +455 -0
  16. package/js/ln-autoresize/README.md +47 -0
  17. package/js/ln-autoresize/ln-autoresize.js +1 -1
  18. package/js/ln-autosave/README.md +33 -1
  19. package/js/ln-autosave/ln-autosave.js +1 -1
  20. package/js/ln-autosave/src/ln-autosave.js +4 -4
  21. package/js/ln-circular-progress/README.md +27 -13
  22. package/js/ln-circular-progress/ln-circular-progress.js +1 -1
  23. package/js/ln-circular-progress/src/ln-circular-progress.js +18 -5
  24. package/js/ln-confirm/README.md +162 -86
  25. package/js/ln-confirm/_ln-confirm.scss +11 -0
  26. package/js/ln-confirm/ln-confirm.js +1 -1
  27. package/js/ln-confirm/src/ln-confirm.js +77 -41
  28. package/js/ln-core/README.md +896 -0
  29. package/js/ln-core/hash.js +92 -0
  30. package/js/ln-core/helpers.js +337 -8
  31. package/js/ln-core/index.js +4 -2
  32. package/js/ln-core/persist.js +29 -3
  33. package/js/ln-core/positioning.js +1 -35
  34. package/js/ln-core/window-cache.js +213 -0
  35. package/js/ln-couchdb-connector/README.md +177 -156
  36. package/js/ln-couchdb-connector/ln-couchdb-connector.js +1 -1
  37. package/js/ln-couchdb-connector/src/ln-couchdb-connector.js +398 -348
  38. package/js/ln-data-coordinator/README.md +536 -165
  39. package/js/ln-data-coordinator/ln-data-coordinator.js +1 -1
  40. package/js/ln-data-coordinator/src/data-read-policy.js +18 -0
  41. package/js/ln-data-coordinator/src/ln-data-coordinator.js +1042 -249
  42. package/js/ln-data-coordinator/src/mutation-receipts.js +46 -0
  43. package/js/ln-data-store/README.md +241 -94
  44. package/js/ln-data-store/ln-data-store.js +1 -1
  45. package/js/ln-data-store/src/ln-data-store.js +694 -699
  46. package/js/ln-date/README.md +39 -1
  47. package/js/ln-date/ln-date-dev.scss +14 -0
  48. package/js/ln-date/ln-date.js +1 -1
  49. package/js/ln-date/src/ln-date.js +236 -178
  50. package/js/ln-debug/README.md +58 -0
  51. package/js/ln-debug/ln-debug.js +1 -0
  52. package/js/ln-debug/src/ln-debug.js +19 -0
  53. package/js/ln-dropdown/README.md +31 -8
  54. package/js/ln-dropdown/ln-dropdown.js +1 -1
  55. package/js/ln-dropdown/src/ln-dropdown.js +30 -13
  56. package/js/ln-editor/README.md +274 -0
  57. package/js/ln-editor/ln-editor.js +1 -0
  58. package/js/ln-editor/ln-editor.scss +43 -0
  59. package/js/ln-editor/src/ln-editor.js +619 -0
  60. package/js/ln-external-links/README.md +44 -5
  61. package/js/ln-external-links/ln-external-links.js +1 -1
  62. package/js/ln-fill/README.md +199 -0
  63. package/js/ln-fill/ln-fill.js +1 -0
  64. package/js/ln-fill/src/ln-fill.js +87 -0
  65. package/js/ln-filter/README.md +189 -99
  66. package/js/ln-filter/ln-filter-dev.scss +8 -0
  67. package/js/ln-filter/ln-filter.js +1 -1
  68. package/js/ln-filter/src/ln-filter.js +22 -7
  69. package/js/ln-form/README.md +212 -49
  70. package/js/ln-form/ln-form.js +1 -1
  71. package/js/ln-form/src/ln-form.js +99 -199
  72. package/js/ln-http/README.md +125 -89
  73. package/js/ln-http/ln-http.js +1 -1
  74. package/js/ln-http/src/ln-http.js +32 -13
  75. package/js/ln-icon/README.md +130 -0
  76. package/js/ln-icon/ln-icon.js +1 -0
  77. package/js/{ln-icons/src/ln-icons.js → ln-icon/src/ln-icon.js} +20 -15
  78. package/js/ln-link/README.md +47 -9
  79. package/js/ln-link/ln-link.js +1 -1
  80. package/js/ln-link/src/ln-link.js +22 -6
  81. package/js/ln-list/README.md +129 -0
  82. package/js/ln-list/ln-list.js +1 -0
  83. package/js/ln-list/ln-list.scss +28 -0
  84. package/js/ln-list/src/ln-list.js +1216 -0
  85. package/js/ln-modal/README.md +185 -154
  86. package/js/ln-modal/ln-modal-dev.scss +13 -0
  87. package/js/ln-modal/ln-modal.js +1 -1
  88. package/js/ln-modal/ln-modal.scss +28 -4
  89. package/js/ln-modal/src/ln-modal.js +137 -201
  90. package/js/ln-modal-coordinator/README.md +99 -0
  91. package/js/ln-modal-coordinator/ln-modal-coordinator-dev.scss +8 -0
  92. package/js/ln-modal-coordinator/ln-modal-coordinator.js +1 -0
  93. package/js/ln-modal-coordinator/src/ln-modal-coordinator.js +345 -0
  94. package/js/ln-nav/README.md +46 -3
  95. package/js/ln-nav/ln-nav.js +1 -1
  96. package/js/ln-nav/src/ln-nav.js +92 -127
  97. package/js/ln-number/README.md +28 -1
  98. package/js/ln-number/ln-number-dev.scss +13 -0
  99. package/js/ln-number/ln-number.js +1 -1
  100. package/js/ln-number/src/ln-number.js +443 -350
  101. package/js/ln-options/README.md +66 -0
  102. package/js/ln-options/ln-options.js +1 -0
  103. package/js/ln-options/src/ln-options.js +74 -0
  104. package/js/ln-popover/README.md +40 -5
  105. package/js/ln-popover/ln-popover.js +1 -1
  106. package/js/ln-popover/src/ln-popover.js +38 -11
  107. package/js/ln-progress/README.md +413 -442
  108. package/js/ln-progress/ln-progress.js +1 -1
  109. package/js/ln-progress/src/ln-progress.js +98 -150
  110. package/js/ln-router/README.md +432 -0
  111. package/js/ln-router/ln-router.js +1 -0
  112. package/js/ln-router/src/ln-router.js +539 -0
  113. package/js/ln-search/README.md +158 -83
  114. package/js/ln-search/ln-search-dev.scss +8 -0
  115. package/js/ln-search/ln-search.js +1 -1
  116. package/js/ln-search/src/ln-search.js +12 -9
  117. package/js/ln-slug/README.md +110 -0
  118. package/js/ln-slug/ln-slug.js +1 -0
  119. package/js/ln-slug/src/ln-slug.js +74 -0
  120. package/js/ln-sort/README.md +170 -0
  121. package/js/ln-sort/ln-sort-dev.scss +8 -0
  122. package/js/ln-sort/ln-sort.js +1 -0
  123. package/js/ln-sort/src/ln-sort.js +178 -0
  124. package/js/ln-sortable/README.md +133 -95
  125. package/js/ln-sortable/ln-sortable.js +1 -1
  126. package/js/ln-stat/README.md +55 -0
  127. package/js/ln-stat/ln-stat.js +1 -0
  128. package/js/ln-stat/src/ln-stat.js +55 -0
  129. package/js/ln-table/README.md +285 -101
  130. package/js/ln-table/ln-table-dev.scss +17 -0
  131. package/js/ln-table/ln-table.js +1 -1
  132. package/js/ln-table/ln-table.scss +1 -1
  133. package/js/ln-table/src/ln-table.js +1493 -473
  134. package/js/ln-table-coordinator/README.md +73 -0
  135. package/js/ln-table-coordinator/ln-table-coordinator.js +1 -0
  136. package/js/ln-table-coordinator/src/ln-table-coordinator.js +160 -0
  137. package/js/ln-tabs/README.md +221 -137
  138. package/js/ln-tabs/ln-tabs-dev.scss +18 -0
  139. package/js/ln-tabs/ln-tabs.js +1 -1
  140. package/js/ln-tabs/src/ln-tabs.js +41 -24
  141. package/js/ln-time/README.md +14 -0
  142. package/js/ln-time/ln-time.js +1 -1
  143. package/js/ln-time/src/ln-time.js +223 -192
  144. package/js/ln-toast/README.md +178 -122
  145. package/js/ln-toast/ln-toast.js +1 -15
  146. package/js/ln-toast/src/ln-toast.js +232 -210
  147. package/js/ln-toast/template.html +16 -12
  148. package/js/ln-toggle/README.md +61 -2
  149. package/js/ln-toggle/ln-toggle-dev.scss +13 -0
  150. package/js/ln-toggle/ln-toggle.js +1 -1
  151. package/js/ln-toggle/src/ln-toggle.js +56 -43
  152. package/js/ln-tooltip/README.md +95 -58
  153. package/js/ln-tooltip/ln-tooltip-dev.scss +9 -0
  154. package/js/ln-tooltip/ln-tooltip.js +1 -1
  155. package/js/ln-tooltip/src/ln-tooltip.js +45 -6
  156. package/js/ln-translations/README.md +118 -96
  157. package/js/ln-translations/ln-translations.js +1 -1
  158. package/js/ln-translations/src/ln-translations.js +10 -5
  159. package/js/ln-upload/README.md +85 -10
  160. package/js/ln-upload/ln-upload.js +1 -1
  161. package/js/ln-upload/src/ln-upload.js +26 -10
  162. package/js/ln-validate/README.md +72 -0
  163. package/js/ln-validate/ln-validate-dev.scss +15 -0
  164. package/js/ln-validate/ln-validate.js +1 -1
  165. package/js/ln-validate/src/ln-validate.js +64 -0
  166. package/package.json +61 -55
  167. package/scss/base/_global.scss +2 -2
  168. package/scss/base/_typography.scss +1 -2
  169. package/scss/components/_date.scss +1 -0
  170. package/scss/components/_dropdown.scss +0 -5
  171. package/scss/components/_editor.scss +5 -0
  172. package/scss/components/_filter.scss +26 -0
  173. package/scss/components/_form.scss +7 -0
  174. package/scss/components/_ln-table.scss +14 -5
  175. package/scss/components/_sort.scss +10 -0
  176. package/scss/components/_table.scss +68 -1
  177. package/scss/components/_toast.scss +68 -41
  178. package/scss/components/_toggle.scss +16 -0
  179. package/scss/config/_density.scss +205 -117
  180. package/scss/config/_icons.scss +10 -9
  181. package/scss/config/_tokens.scss +69 -47
  182. package/scss/config/mixins/_accordion.scss +2 -7
  183. package/scss/config/mixins/_alert.scss +4 -4
  184. package/scss/config/mixins/_breadcrumbs.scss +2 -2
  185. package/scss/config/mixins/_btn.scss +1 -1
  186. package/scss/config/mixins/_card.scss +89 -14
  187. package/scss/config/mixins/_collapsible.scss +3 -0
  188. package/scss/config/mixins/_colors.scss +1 -1
  189. package/scss/config/mixins/_diagnostics.scss +48 -0
  190. package/scss/config/mixins/_display.scss +1 -0
  191. package/scss/config/mixins/_dropdown.scss +3 -5
  192. package/scss/config/mixins/_editor.scss +124 -0
  193. package/scss/config/mixins/_empty-state.scss +7 -0
  194. package/scss/config/mixins/_filter.scss +47 -0
  195. package/scss/config/mixins/_footer.scss +1 -2
  196. package/scss/config/mixins/_form.scss +207 -21
  197. package/scss/config/mixins/_index.scss +4 -1
  198. package/scss/config/mixins/_ln-table.scss +51 -93
  199. package/scss/config/mixins/_modal.scss +149 -66
  200. package/scss/config/mixins/_nav.scss +3 -4
  201. package/scss/config/mixins/_popover.scss +39 -0
  202. package/scss/config/mixins/_sidebar.scss +1 -1
  203. package/scss/config/mixins/_sort.scss +42 -0
  204. package/scss/config/mixins/_table.scss +232 -38
  205. package/scss/config/mixins/_toast.scss +179 -152
  206. package/scss/config/mixins/_tooltip.scss +17 -7
  207. package/scss/config/mixins/_typography.scss +4 -2
  208. package/scss/ln-ashlar-dev.scss +39 -0
  209. package/scss/ln-ashlar.scss +8 -2
  210. package/js/ln-data-table/README.md +0 -110
  211. package/js/ln-data-table/ln-data-table.js +0 -1
  212. package/js/ln-data-table/ln-data-table.scss +0 -10
  213. package/js/ln-data-table/src/ln-data-table.js +0 -1103
  214. package/js/ln-icons/README.md +0 -88
  215. package/js/ln-icons/ln-icons.js +0 -1
  216. package/js/ln-table/ln-table-sort.js +0 -1
  217. package/js/ln-table/src/ln-table-sort.js +0 -168
  218. package/scss/components/_data-table.scss +0 -83
  219. package/scss/config/mixins/_data-table.scss +0 -346
@@ -6,14 +6,21 @@
6
6
  @use 'motion' as *;
7
7
 
8
8
  // Shared dark-bubble chrome for ::after pseudo-tooltips (tooltip, confirm)
9
- // and JS-portal tooltip-element. Semantic token pair — in dark mode the
10
- // neutral scale inversion naturally flips the bubble to light-on-dark.
9
+ // and JS-portal tooltip-element. Reads the theme-root token pair
10
+ // (--fg-default / --bg-base) NOT the rebindable --color-fg / --color-bg:
11
+ // the ::after inherits custom properties from its trigger, and many
12
+ // components locally rebind --color-fg/--color-bg (btn, accordion,
13
+ // avatar, app-shell, …). Reading those would make the bubble's text
14
+ // inherit a context-specific value — e.g. dark-on-dark = invisible.
15
+ // --fg-default / --bg-base are only ever set at :root (and flipped at
16
+ // the dark-theme :root), so the dark-mode inversion still works while
17
+ // the bubble stays immune to component-scope rebinds.
11
18
  @mixin tooltip-bubble {
12
19
  --padding-y: var(--size-xs);
13
20
  --padding-x: var(--size-sm);
14
21
  padding: var(--padding-y) var(--padding-x);
15
- background: var(--color-fg);
16
- color: var(--color-bg);
22
+ background: var(--fg-default);
23
+ color: var(--bg-base);
17
24
  @include typography(caption);
18
25
  --radius: var(--radius-sm);
19
26
  border-radius: var(--radius);
@@ -89,15 +96,18 @@
89
96
  }
90
97
 
91
98
  // ─── JS Portal (real DOM tooltip) ──────────────────────
92
- // When JS enhance is active, tooltips render as positioned
93
- // DOM elements inside #ln-tooltip-portal instead of ::after.
99
+ // When JS enhance is active, tooltips render as positioned DOM elements
100
+ // inside #ln-tooltip-portal, a shared popover="manual" container promoted
101
+ // to the browser's top layer (showPopover()/hidePopover() in ln-tooltip.js)
102
+ // instead of ::after.
94
103
 
95
104
  @mixin tooltip-portal {
96
105
  position: fixed;
106
+ inset: auto;
107
+ margin: 0;
97
108
  top: 0;
98
109
  left: 0;
99
110
  pointer-events: none;
100
- z-index: var(--z-toast);
101
111
  }
102
112
 
103
113
  @mixin tooltip-element {
@@ -49,8 +49,10 @@
49
49
  @error "typography(): unknown role '#{$role}'. Valid roles: #{$valid-roles}";
50
50
  }
51
51
 
52
- font-size: var(--text-#{$role});
53
- line-height: var(--lh-#{$role});
52
+ --font-size: var(--text-#{$role});
53
+ font-size: var(--font-size);
54
+ --line-height: var(--lh-#{$role});
55
+ line-height: var(--line-height);
54
56
  }
55
57
 
56
58
  @mixin tracking-tight { letter-spacing: var(--tracking-tight); }
@@ -0,0 +1,39 @@
1
+ // ==========================================================================
2
+ // Dev & Validation Diagnostics — ln-ashlar
3
+ // ==========================================================================
4
+ // This stylesheet contains diagnostic CSS tools to validate correct HTML
5
+ // structure, missing integration attributes, and accessibility warnings during
6
+ // development. Do not compile or deploy this file to production.
7
+ // ==========================================================================
8
+
9
+ @use 'config/mixins' as *;
10
+
11
+ // --- Modular Component Diagnostics ---
12
+ @use '../js/ln-toggle/ln-toggle-dev';
13
+ @use '../js/ln-table/ln-table-dev';
14
+ @use '../js/ln-modal/ln-modal-dev';
15
+ @use '../js/ln-modal-coordinator/ln-modal-coordinator-dev';
16
+ @use '../js/ln-validate/ln-validate-dev';
17
+ @use '../js/ln-date/ln-date-dev';
18
+ @use '../js/ln-number/ln-number-dev';
19
+ @use '../js/ln-tabs/ln-tabs-dev';
20
+ @use '../js/ln-search/ln-search-dev';
21
+ @use '../js/ln-filter/ln-filter-dev';
22
+ @use '../js/ln-sort/ln-sort-dev';
23
+ @use '../js/ln-tooltip/ln-tooltip-dev';
24
+
25
+ // --- General HTML/Accessibility Diagnostics ---
26
+ [data-ln-debug] {
27
+ // 1. Image Missing Alt: accessibility warning
28
+ // Flags an <img> with NO alt attribute (empty alt="" is the valid decorative pattern and is intentionally NOT flagged)
29
+ img:not([alt]) {
30
+ @include dev-replaced-error;
31
+ }
32
+
33
+ // 2. Abbr Missing Title: accessibility warning
34
+ // Flags an <abbr> with no title — the abbreviation has no expansion and is meaningless to readers
35
+ abbr:not([title]) {
36
+ @include dev-inline-error("abbr is missing a title attribute");
37
+ }
38
+ }
39
+
@@ -13,6 +13,7 @@
13
13
  @use 'components/date';
14
14
  @use 'components/layout';
15
15
  @use 'components/table';
16
+ @use 'components/sort';
16
17
  @use 'components/tabs';
17
18
  @use 'components/toggle';
18
19
  @use 'components/accordion';
@@ -31,9 +32,11 @@
31
32
  @use 'components/timeline';
32
33
  @use 'components/stat-card';
33
34
  @use 'components/prose';
35
+ @use 'components/editor';
34
36
  @use 'components/scrollbar';
35
37
  @use 'components/tooltip';
36
38
  @use 'components/popover';
39
+ @use 'components/filter';
37
40
  @use 'components/chip';
38
41
  @use 'components/loader';
39
42
  @use 'components/progress';
@@ -45,7 +48,6 @@
45
48
  @use 'components/toast';
46
49
  @use 'components/upload';
47
50
  @use 'components/translations';
48
- @use 'components/data-table';
49
51
  @use 'components/ln-table';
50
52
  @use 'components/ajax';
51
53
 
@@ -60,4 +62,8 @@
60
62
  @use '../js/ln-filter/ln-filter';
61
63
  @use '../js/ln-search/ln-search';
62
64
  @use '../js/ln-table/ln-table' as ln-table-state;
63
- @use '../js/ln-data-table/ln-data-table';
65
+ @use '../js/ln-list/ln-list';
66
+ @use '../js/ln-editor/ln-editor';
67
+ @use '../js/ln-confirm/ln-confirm';
68
+
69
+
@@ -1,110 +0,0 @@
1
- # ln-data-table
2
-
3
- A zero-dependency, event-driven **Data Presentation Shell** that manages row template rendering, interactive sorting, column filtering, search states, multi-row selections, virtual scrolling (10,000+ items), and keyboard layouts.
4
-
5
- It decouples data presentation from data fetching: announcing query intents via request events and awaiting rendered datasets asynchronously.
6
-
7
- ---
8
-
9
- ## 🧭 Philosophy & Architecture
10
-
11
- 1. **Presenter Separation:** The component possesses no fetching capabilities. It translates column and search interactions into unified query events and delegates execution to a coordinator (such as `ln-store` or direct server fetchers).
12
- 2. **Dynamic Template Scoping:** Row designs are written cleanly inside standard HTML `<template>` slots. Values are safely bound using double-curly brackets (`{{ field }}`) using browser-native `textContent` for robust XSS protection.
13
- 3. **Flicker-Free Virtual Rendering:** Massive tables auto-activate high-performance virtual scrolling. Only the visible portion of rows plus a buffer are mounted, keeping the DOM extremely small.
14
-
15
- ---
16
-
17
- ## 📦 Minimal Blueprint
18
-
19
- ```html
20
- <section data-ln-data-table="documents" data-ln-data-table-selectable id="documents-table">
21
- <header class="ln-table__toolbar">
22
- <h3>Documents</h3>
23
- <input type="search" placeholder="Search..." data-ln-data-table-search>
24
- </header>
25
-
26
- <table>
27
- <thead>
28
- <tr>
29
- <th data-ln-col-select></th>
30
- <th data-ln-col="title">
31
- Title
32
- <button data-ln-col-sort aria-label="Sort">
33
- <svg class="ln-icon" data-ln-sort-icon="none"><use href="#ln-arrows-sort"></use></svg>
34
- <svg class="ln-icon" data-ln-sort-icon="asc"><use href="#ln-arrow-up"></use></svg>
35
- <svg class="ln-icon" data-ln-sort-icon="desc"><use href="#ln-arrow-down"></use></svg>
36
- </button>
37
- </th>
38
- </tr>
39
- </thead>
40
- <tbody data-ln-data-table-body></tbody>
41
- </table>
42
-
43
- <!-- Row Template -->
44
- <template data-ln-template="documents-row">
45
- <tr data-ln-row>
46
- <td><input type="checkbox" data-ln-row-select></td>
47
- <td>{{ title }}</td>
48
- </tr>
49
- </template>
50
- </section>
51
- ```
52
-
53
- > [!IMPORTANT]
54
- > Directly nested `<table>` elements must not be wrapped in scroll-trapping `overflow-y: auto` containers. The sticky header and virtual scroll calculations automatically bind to the nearest scrollable page-level container.
55
-
56
- ---
57
-
58
- ## 🛠️ Declarative API Contract
59
-
60
- ### HTML Attributes
61
-
62
- | Attribute | Elements | Description |
63
- | :--- | :--- | :--- |
64
- | `data-ln-data-table` | `<section>` | Component root and namespace. |
65
- | `data-ln-data-table-selectable` | `<section>` | Opt-in. Activates checkbox columns and selection tracking. |
66
- | `data-ln-col="field"` | `<th>` | Binds column interactions (sort, filter) to a data property. |
67
- | `data-ln-data-table-search` | `<input>` | Binds keystroke entries to instant search events. |
68
-
69
- ### JS API
70
-
71
- Access the presentation state directly via the `lnDataTable` property on the section container:
72
-
73
- ```javascript
74
- const table = document.getElementById('documents-table');
75
-
76
- // 1. Inspect state
77
- const selection = table.lnDataTable.selectedIds; // Set of active string IDs
78
- const sortedBy = table.lnDataTable.currentSort; // { field, direction }
79
-
80
- // 2. Tear down listeners
81
- table.lnDataTable.destroy();
82
- ```
83
-
84
- ---
85
-
86
- ## ⚡ DOM Events
87
-
88
- ### Emitted (Telemetry & Intent)
89
-
90
- | Event | Payload | Description |
91
- | :--- | :--- | :--- |
92
- | `ln-data-table:request-data` | `{ table, sort, filters, search }` | Intent event. Fired whenever query states change. |
93
- | `ln-data-table:row-click` | `{ table, id, record }` | Fired when clicking a row (ignored on inputs/links). |
94
- | `ln-data-table:row-action` | `{ table, id, action, record }` | Fired on clicking `[data-ln-row-action="action"]` slots. |
95
- | `ln-data-table:select` | `{ table, selectedIds, count }` | Fired on selecting row checkboxes. |
96
-
97
- ### Received (State Synchronization)
98
-
99
- | Event | Payload | Description |
100
- | :--- | :--- | :--- |
101
- | `ln-data-table:set-data` | `{ data: Array, total, filtered }` | Populates table, rebuilds filters, formats counts. |
102
- | `ln-data-table:set-loading` | `{ loading: Boolean }` | Toggles dim loading state (pointer-events disabled). |
103
-
104
- ---
105
-
106
- ## ⚠️ Common Pitfalls
107
-
108
- - **Bypassing the Template Slot:** Putting static row markup directly inside `<tbody>` will result in it being immediately purged on the first data render event. Always build row cells within `<template>`.
109
- - **Horizontal Overflow Traps:** Attempting to wrap tables in scroll layers breaks sticky header positioning. Let layout views manage full viewport boundaries instead.
110
- - **Direct JS string injections:** Attempting to render raw HTML inside templates bypasses the safe `textContent` parser, opening XSS risks. Use custom action dispatchers for complex fields.
@@ -1 +0,0 @@
1
- (function(){"use strict";const x={};function F(o,r){x[o]||(x[o]=document.querySelector('[data-ln-template="'+o+'"]'));const _=x[o];return _?_.content.cloneNode(!0):(console.warn("["+r+'] Template "'+o+'" not found'),null)}function m(o,r,_){o.dispatchEvent(new CustomEvent(r,{bubbles:!0,detail:_||{}}))}function D(o,r){if(!o||!r)return o;const _=o.querySelectorAll("[data-ln-field]");for(let u=0;u<_.length;u++){const a=_[u],g=a.getAttribute("data-ln-field");r[g]!=null&&(a.textContent=r[g])}const y=o.querySelectorAll("[data-ln-attr]");for(let u=0;u<y.length;u++){const a=y[u],g=a.getAttribute("data-ln-attr").split(",");for(let e=0;e<g.length;e++){const t=g[e].trim().split(":");if(t.length!==2)continue;const n=t[0].trim(),l=t[1].trim();r[l]!=null&&a.setAttribute(n,r[l])}}const C=o.querySelectorAll("[data-ln-show]");for(let u=0;u<C.length;u++){const a=C[u],g=a.getAttribute("data-ln-show");g in r&&a.classList.toggle("hidden",!r[g])}const S=o.querySelectorAll("[data-ln-class]");for(let u=0;u<S.length;u++){const a=S[u],g=a.getAttribute("data-ln-class").split(",");for(let e=0;e<g.length;e++){const t=g[e].trim().split(":");if(t.length!==2)continue;const n=t[0].trim(),l=t[1].trim();l in r&&a.classList.toggle(n,!!r[l])}}return o}function T(o,r){if(!o||!r)return o;const _=document.createTreeWalker(o,NodeFilter.SHOW_TEXT);for(;_.nextNode();){const y=_.currentNode;y.textContent.indexOf("{{")!==-1&&(y.textContent=y.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(C,S){return r[S]!==void 0?r[S]:""}))}return o}function R(o,r){if(!document.body){document.addEventListener("DOMContentLoaded",function(){R(o,r)}),console.warn("["+r+'] Script loaded before <body> — add "defer" to your <script> tag');return}o()}function E(o,r,_){if(o){const y=o.querySelector('[data-ln-template="'+r+'"]');if(y)return y.content.cloneNode(!0)}return F(r,_)}function I(o,r,_,y){if(o.nodeType!==1)return;const S=r.indexOf("[")!==-1||r.indexOf(".")!==-1||r.indexOf("#")!==-1?r:"["+r+"]",u=Array.from(o.querySelectorAll(S));o.matches&&o.matches(S)&&u.push(o);for(const a of u)a[_]||(a[_]=new y(a))}function O(o,r,_,y,C={}){const S=C.extraAttributes||[],u=C.onAttributeChange||null,a=C.onInit||null;function g(e){const t=e||document.body;I(t,o,r,_),a&&a(t)}return R(function(){const e=new MutationObserver(function(n){for(let l=0;l<n.length;l++){const i=n[l];if(i.type==="childList"){for(let s=0;s<i.addedNodes.length;s++){const c=i.addedNodes[s];c.nodeType===1&&(I(c,o,r,_),a&&a(c))}for(let s=0;s<i.removedNodes.length;s++){const c=i.removedNodes[s];if(c.nodeType===1){const f=o.indexOf("[")!==-1||o.indexOf(".")!==-1||o.indexOf("#")!==-1?o:"["+o+"]",p=Array.from(c.querySelectorAll(f));c.matches&&c.matches(f)&&p.push(c);for(let A=0;A<p.length;A++){const v=p[A];if(!document.contains(v)){const w=v[r];w&&typeof w.destroy=="function"&&w.destroy()}}}}}else i.type==="attributes"&&(u&&i.target[r]?u(i.target,i.attributeName):(I(i.target,o,r,_),a&&a(i.target)))}});let t=[];if(o.indexOf("[")!==-1){const n=/\[([\w-]+)/g;let l;for(;(l=n.exec(o))!==null;)t.push(l[1])}else t.push(o);e.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:t.concat(S)})},y),window[r]=g,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){g(document.body)}):g(document.body),g}const q={};function H(o,r){q[o]=r}function N(o){return q[o]||{ingress:r=>r,egress:r=>r}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=H,window.lnCore.getDataMapper=N),(function(){const o="data-ln-data-table",r="lnDataTable";if(window[r]!==void 0)return;const C=typeof Intl<"u"?new Intl.NumberFormat(document.documentElement.lang||void 0):null;function S(e){return C?C.format(e):String(e)}function u(e){let t=e.parentElement;for(;t&&t!==document.body&&t!==document.documentElement;){const l=getComputedStyle(t).overflowY;if(l==="auto"||l==="scroll")return t;t=t.parentElement}return null}function a(e){this.dom=e,this.name=e.getAttribute(o)||"",this.table=e.querySelector("table"),this.tbody=e.querySelector("[data-ln-data-table-body]")||e.querySelector("tbody"),this.thead=e.querySelector("thead"),this.ths=this.thead?Array.from(this.thead.querySelectorAll("th")):[],this.isLoaded=!1,this.totalCount=0,this.visibleCount=0,this.currentSort=null,this.currentFilters={},this.currentSearch="",this.selectedIds=new Set,this._data=[],this._lastTotal=0,this._lastFiltered=0,this._filterOptions={},this._filterableFields=this.ths.filter(function(n){return n.getAttribute("data-ln-col")&&n.querySelector("[data-ln-col-filter]")}).map(function(n){return n.getAttribute("data-ln-col")}),this._virtual=!1,this._rowHeight=0,this._vStart=-1,this._vEnd=-1,this._rafId=null,this._scrollHandler=null,this._scrollContainer=null,this._totalSpan=e.querySelector("[data-ln-data-table-total]"),this._filteredSpan=e.querySelector("[data-ln-data-table-filtered]"),this._filteredSpan&&(this._filteredWrap=this._filteredSpan.parentElement!==e?this._filteredSpan.closest("[data-ln-data-table-filtered-wrap]")||this._filteredSpan.parentNode:null),this._selectedSpan=e.querySelector("[data-ln-data-table-selected]"),this._selectedSpan&&(this._selectedWrap=this._selectedSpan.parentElement!==e?this._selectedSpan.closest("[data-ln-data-table-selected-wrap]")||this._selectedSpan.parentNode:null);const t=this;return this._onSetData=function(n){const l=n.detail||{};t._data=l.data||[],t._lastTotal=l.total!=null?l.total:t._data.length,t._lastFiltered=l.filtered!=null?l.filtered:t._data.length,t.totalCount=t._lastTotal,t.visibleCount=t._lastFiltered,t.isLoaded=!0,t._updateFilterOptions(l.filterOptions),t._vStart=-1,t._vEnd=-1,t._renderRows(),t._updateFooter(),m(e,"ln-data-table:rendered",{table:t.name,total:t.totalCount,visible:t.visibleCount})},e.addEventListener("ln-data-table:set-data",this._onSetData),this._onSetLoading=function(n){const l=n.detail&&n.detail.loading;e.classList.toggle("ln-data-table--loading",!!l),l&&(t.isLoaded=!1)},e.addEventListener("ln-data-table:set-loading",this._onSetLoading),this._sortButtons=Array.from(e.querySelectorAll("[data-ln-col-sort]")),this._onSortClick=function(n){const l=n.target.closest("[data-ln-col-sort]");if(!l)return;const i=l.closest("th");if(!i)return;const s=i.getAttribute("data-ln-col");s&&t._handleSort(s,i)},this.thead&&this.thead.addEventListener("click",this._onSortClick),this._activeDropdown=null,this._onFilterClick=function(n){const l=n.target.closest("[data-ln-col-filter]");if(!l)return;n.stopPropagation();const i=l.closest("th");if(!i)return;const s=i.getAttribute("data-ln-col");if(s){if(t._activeDropdown&&t._activeDropdown.field===s){t._closeFilterDropdown();return}t._openFilterDropdown(s,i,l)}},this.thead&&this.thead.addEventListener("click",this._onFilterClick),this._onDocClick=function(){t._activeDropdown&&t._closeFilterDropdown()},document.addEventListener("click",this._onDocClick),this._onClearAll=function(n){n.target.closest("[data-ln-data-table-clear-all]")&&(t.currentFilters={},t._updateFilterIndicators(),m(e,"ln-data-table:clear-filters",{table:t.name}),t._requestData())},e.addEventListener("click",this._onClearAll),this._selectable=e.hasAttribute("data-ln-data-table-selectable"),this._selectableActive=!1,this._selectable&&this._enableSelection(),this._onRowClick=function(n){if(n.target.closest("[data-ln-row-select]")||n.target.closest("[data-ln-row-action]")||n.target.closest("a")||n.target.closest("button")||n.ctrlKey||n.metaKey||n.button===1)return;const l=n.target.closest("[data-ln-row]");if(!l)return;const i=l.getAttribute("data-ln-row-id"),s=l._lnRecord||{};m(e,"ln-data-table:row-click",{table:t.name,id:i,record:s})},this.tbody&&this.tbody.addEventListener("click",this._onRowClick),this._onRowAction=function(n){const l=n.target.closest("[data-ln-row-action]");if(!l)return;n.stopPropagation();const i=l.closest("[data-ln-row]");if(!i)return;const s=l.getAttribute("data-ln-row-action"),c=i.getAttribute("data-ln-row-id"),d=i._lnRecord||{};m(e,"ln-data-table:row-action",{table:t.name,id:c,action:s,record:d})},this.tbody&&this.tbody.addEventListener("click",this._onRowAction),this._searchInput=e.querySelector("[data-ln-data-table-search]"),this._searchInput&&(this._onSearchInput=function(){t.currentSearch=t._searchInput.value,m(e,"ln-data-table:search",{table:t.name,query:t.currentSearch}),t._requestData()},this._searchInput.addEventListener("input",this._onSearchInput)),this._focusedRowIndex=-1,this._onKeydown=function(n){if(!e.contains(document.activeElement)&&document.activeElement!==document.body||document.activeElement&&(document.activeElement.tagName==="INPUT"||document.activeElement.tagName==="TEXTAREA"))return;if(n.key==="/"){t._searchInput&&(n.preventDefault(),t._searchInput.focus());return}const l=t.tbody?Array.from(t.tbody.querySelectorAll("[data-ln-row]")):[];if(l.length)switch(n.key){case"ArrowDown":n.preventDefault(),t._focusedRowIndex=Math.min(t._focusedRowIndex+1,l.length-1),t._focusRow(l);break;case"ArrowUp":n.preventDefault(),t._focusedRowIndex=Math.max(t._focusedRowIndex-1,0),t._focusRow(l);break;case"Home":n.preventDefault(),t._focusedRowIndex=0,t._focusRow(l);break;case"End":n.preventDefault(),t._focusedRowIndex=l.length-1,t._focusRow(l);break;case"Enter":if(t._focusedRowIndex>=0&&t._focusedRowIndex<l.length){n.preventDefault();const i=l[t._focusedRowIndex];m(e,"ln-data-table:row-click",{table:t.name,id:i.getAttribute("data-ln-row-id"),record:i._lnRecord||{}})}break;case" ":if(t._selectable&&t._focusedRowIndex>=0&&t._focusedRowIndex<l.length){n.preventDefault();const i=l[t._focusedRowIndex].querySelector("[data-ln-row-select]");i&&(i.checked=!i.checked,i.dispatchEvent(new Event("change",{bubbles:!0})))}break;case"Escape":t._activeDropdown&&t._closeFilterDropdown();break}},document.addEventListener("keydown",this._onKeydown),m(e,"ln-data-table:request-data",{table:this.name,sort:this.currentSort,filters:this.currentFilters,search:this.currentSearch}),this}a.prototype._handleSort=function(e,t){let n;!this.currentSort||this.currentSort.field!==e?n="asc":this.currentSort.direction==="asc"?n="desc":n=null;for(let l=0;l<this.ths.length;l++)this.ths[l].classList.remove("ln-sort-asc","ln-sort-desc");n?(this.currentSort={field:e,direction:n},t.classList.add(n==="asc"?"ln-sort-asc":"ln-sort-desc")):this.currentSort=null,m(this.dom,"ln-data-table:sort",{table:this.name,field:e,direction:n}),this._requestData()},a.prototype._requestData=function(){m(this.dom,"ln-data-table:request-data",{table:this.name,sort:this.currentSort,filters:this.currentFilters,search:this.currentSearch})},a.prototype._updateSelectAll=function(){if(!this._selectAllCheckbox||!this.tbody)return;const e=this.tbody.querySelectorAll("[data-ln-row]");let t=e.length>0;for(let n=0;n<e.length;n++){const l=e[n].getAttribute("data-ln-row-id");if(l!=null&&!this.selectedIds.has(l)){t=!1;break}}this._selectAllCheckbox.checked=t},Object.defineProperty(a.prototype,"selectedCount",{get:function(){return this.selectedIds.size},set:function(){}}),a.prototype._enableSelection=function(){if(this._selectableActive)return;this._selectableActive=!0;const e=this;if(this._onSelectionChange=function(t){const n=t.target.closest("[data-ln-row-select]");if(!n)return;const l=n.closest("[data-ln-row]");if(!l)return;const i=l.getAttribute("data-ln-row-id");i!=null&&(n.checked?(e.selectedIds.add(i),l.classList.add("ln-row-selected")):(e.selectedIds.delete(i),l.classList.remove("ln-row-selected")),e.selectedCount=e.selectedIds.size,e._updateSelectAll(),e._updateFooter(),m(e.dom,"ln-data-table:select",{table:e.name,selectedIds:e.selectedIds,count:e.selectedCount}))},this.tbody&&this.tbody.addEventListener("change",this._onSelectionChange),this._selectAllCheckbox=this.dom.querySelector('[data-ln-col-select] input[type="checkbox"]')||this.dom.querySelector("[data-ln-col-select]"),this._selectAllCheckbox&&this._selectAllCheckbox.tagName==="TH"){const t=document.createElement("input");t.type="checkbox",t.setAttribute("aria-label","Select all"),this._selectAllCheckbox.appendChild(t),this._selectAllCheckbox=t}if(this._selectAllCheckbox&&(this._onSelectAll=function(){const t=e._selectAllCheckbox.checked,n=e.tbody?e.tbody.querySelectorAll("[data-ln-row]"):[];for(let l=0;l<n.length;l++){const i=n[l].getAttribute("data-ln-row-id"),s=n[l].querySelector("[data-ln-row-select]");i!=null&&(t?(e.selectedIds.add(i),n[l].classList.add("ln-row-selected")):(e.selectedIds.delete(i),n[l].classList.remove("ln-row-selected")),s&&(s.checked=t))}e.selectedCount=e.selectedIds.size,m(e.dom,"ln-data-table:select-all",{table:e.name,selected:t}),m(e.dom,"ln-data-table:select",{table:e.name,selectedIds:e.selectedIds,count:e.selectedCount}),e._updateFooter()},this._selectAllCheckbox.addEventListener("change",this._onSelectAll)),this.tbody){const t=this.tbody.querySelectorAll("[data-ln-row]");for(let n=0;n<t.length;n++){const l=t[n].querySelector("[data-ln-row-select]"),i=t[n].getAttribute("data-ln-row-id");l&&l.checked&&i!=null&&(this.selectedIds.add(i),t[n].classList.add("ln-row-selected"))}this.selectedCount=this.selectedIds.size,this.selectedCount>0&&this._updateSelectAll()}},a.prototype._disableSelection=function(){if(!this._selectableActive)return;this._selectableActive=!1,this.tbody&&this._onSelectionChange&&this.tbody.removeEventListener("change",this._onSelectionChange),this._selectAllCheckbox&&this._onSelectAll&&this._selectAllCheckbox.removeEventListener("change",this._onSelectAll);const e=this.dom.querySelector("[data-ln-col-select]");if(e){const t=e.querySelector('input[type="checkbox"]');t&&t.remove()}if(this._selectAllCheckbox=null,this.selectedIds.clear(),this.selectedCount=0,this.tbody){const t=this.tbody.querySelectorAll("[data-ln-row]");for(let n=0;n<t.length;n++){t[n].classList.remove("ln-row-selected");const l=t[n].querySelector("[data-ln-row-select]");l&&(l.checked=!1)}}this._updateFooter()},a.prototype._focusRow=function(e){for(let t=0;t<e.length;t++)e[t].classList.remove("ln-row-focused"),e[t].removeAttribute("tabindex");if(this._focusedRowIndex>=0&&this._focusedRowIndex<e.length){const t=e[this._focusedRowIndex];t.classList.add("ln-row-focused"),t.setAttribute("tabindex","0"),t.focus(),t.scrollIntoView({block:"nearest"})}},a.prototype._openFilterDropdown=function(e,t,n){this._closeFilterDropdown();const l=E(this.dom,this.name+"-column-filter","ln-data-table")||E(this.dom,"column-filter","ln-data-table");if(!l)return;const i=l.firstElementChild;if(!i)return;const s=this._getUniqueValues(e),c=i.querySelector("[data-ln-filter-options]"),d=i.querySelector("[data-ln-filter-search]"),f=this.currentFilters[e]||[],p=this;if(d&&s.length<=8&&d.classList.add("hidden"),c){const v=c.querySelector("[data-ln-filter-reset]");v&&(v.checked=f.length===0);const w=E(i,this.name+"-column-filter-item","ln-data-table")||E(i,"column-filter-item","ln-data-table");if(w)for(let h=0;h<s.length;h++){const b=s[h],k=w.cloneNode(!0);D(k,{value:b});const L=k.querySelector('input[type="checkbox"]');L&&(L.value=b,L.checked=f.length>0&&f.indexOf(b)!==-1),c.appendChild(k)}c.addEventListener("change",function(h){h.target.type==="checkbox"&&(p._applyFilterMutualExclusion(h.target,c),p._onFilterChange(e,c))})}d&&d.addEventListener("input",function(){const v=d.value.toLowerCase(),w=c.querySelectorAll("li");for(let h=0;h<w.length;h++){const b=w[h].textContent.toLowerCase();w[h].classList.toggle("hidden",v&&b.indexOf(v)===-1)}});const A=i.querySelector("[data-ln-filter-clear]");A&&A.addEventListener("click",function(){delete p.currentFilters[e],p._closeFilterDropdown(),p._updateFilterIndicators(),m(p.dom,"ln-data-table:filter",{table:p.name,field:e,values:[]}),p._requestData()}),t.appendChild(i),this._activeDropdown={field:e,th:t,el:i},i.addEventListener("click",function(v){v.stopPropagation()})},a.prototype._closeFilterDropdown=function(){this._activeDropdown&&(this._activeDropdown.el&&this._activeDropdown.el.parentNode&&this._activeDropdown.el.parentNode.removeChild(this._activeDropdown.el),this._activeDropdown=null)},a.prototype._applyFilterMutualExclusion=function(e,t){const n=e.hasAttribute("data-ln-filter-reset"),l=t.querySelector("[data-ln-filter-reset]"),i=t.querySelectorAll('input[type="checkbox"]:not([data-ln-filter-reset])');if(n){e.checked=!0;for(let s=0;s<i.length;s++)i[s].checked=!1}else if(e.checked)l&&(l.checked=!1);else{let s=!1;for(let c=0;c<i.length;c++)if(i[c].checked){s=!0;break}!s&&l&&(l.checked=!0)}},a.prototype._onFilterChange=function(e,t){const n=t.querySelector("[data-ln-filter-reset]"),l=t.querySelectorAll('input[type="checkbox"]:not([data-ln-filter-reset])'),i=[];for(let c=0;c<l.length;c++)l[c].checked&&i.push(l[c].value);const s=n&&n.checked||i.length===0;s?delete this.currentFilters[e]:this.currentFilters[e]=i,this._updateFilterIndicators(),m(this.dom,"ln-data-table:filter",{table:this.name,field:e,values:s?[]:i}),this._requestData()},a.prototype._updateFilterOptions=function(e){if(e!==null&&typeof e=="object"&&!Array.isArray(e)){const t=Object.keys(e);for(let n=0;n<t.length;n++){const l=t[n],i=e[l];if(!Array.isArray(i))continue;const s={},c=[];for(let d=0;d<i.length;d++){const f=String(i[d]);s[f]||(s[f]=!0,c.push(f))}this._filterOptions[l]=c.sort()}}else{const t=this._filterableFields,n=this._data;for(let l=0;l<t.length;l++){const i=t[l];this._filterOptions[i]||(this._filterOptions[i]=[]);const s=this._filterOptions[i],c={};for(let d=0;d<s.length;d++)c[s[d]]=!0;for(let d=0;d<n.length;d++){const f=n[d][i];if(f!=null){const p=String(f);c[p]||(c[p]=!0,s.push(p))}}s.sort()}}},a.prototype._getUniqueValues=function(e){return(this._filterOptions[e]||[]).slice().sort()},a.prototype._updateFilterIndicators=function(){const e=this.ths;for(let t=0;t<e.length;t++){const n=e[t],l=n.getAttribute("data-ln-col");if(!l)continue;const i=n.querySelector("[data-ln-col-filter]");if(!i)continue;const s=this.currentFilters[l]&&this.currentFilters[l].length>0;i.classList.toggle("ln-filter-active",!!s)}},a.prototype._renderRows=function(){if(!this.tbody)return;const e=this._data,t=this._lastTotal,n=this._lastFiltered;if(t===0){this._disableVirtualScroll(),this._showEmptyState(this.name+"-empty");return}if(e.length===0||n===0){this._disableVirtualScroll(),this._showEmptyState(this.name+"-empty-filtered");return}e.length>200?(this._enableVirtualScroll(),this._renderVirtual()):(this._disableVirtualScroll(),this._renderAll())},a.prototype._renderAll=function(){const e=this._data,t=document.createDocumentFragment();for(let n=0;n<e.length;n++){const l=this._buildRow(e[n]);if(!l)break;t.appendChild(l)}this.tbody.textContent="",this.tbody.appendChild(t),this._selectable&&this._updateSelectAll()},a.prototype._buildRow=function(e){const t=E(this.dom,this.name+"-row","ln-data-table");if(!t)return null;const n=t.querySelector("[data-ln-row]")||t.firstElementChild;if(!n)return null;if(this._fillRow(n,e),n._lnRecord=e,e.id!=null&&n.setAttribute("data-ln-row-id",e.id),this._selectable&&e.id!=null&&this.selectedIds.has(String(e.id))){n.classList.add("ln-row-selected");const l=n.querySelector("[data-ln-row-select]");l&&(l.checked=!0)}return n},a.prototype._enableVirtualScroll=function(){if(this._virtual)return;this._virtual=!0,this._vStart=-1,this._vEnd=-1;const e=this;if(!this._rowHeight){const n=this._buildRow(this._data[0]);n&&(this.tbody.textContent="",this.tbody.appendChild(n),this._rowHeight=n.offsetHeight||40,this.tbody.textContent="")}this._scrollContainer=u(this.dom);const t=this._scrollContainer||window;this._scrollHandler=function(){e._rafId||(e._rafId=requestAnimationFrame(function(){e._rafId=null,e._renderVirtual()}))},t.addEventListener("scroll",this._scrollHandler,{passive:!0}),window.addEventListener("resize",this._scrollHandler,{passive:!0})},a.prototype._disableVirtualScroll=function(){this._virtual&&(this._virtual=!1,this._scrollHandler&&((this._scrollContainer||window).removeEventListener("scroll",this._scrollHandler),window.removeEventListener("resize",this._scrollHandler),this._scrollHandler=null),this._scrollContainer=null,this._rafId&&(cancelAnimationFrame(this._rafId),this._rafId=null),this._vStart=-1,this._vEnd=-1)},a.prototype._renderVirtual=function(){const e=this._data,t=e.length,n=this._rowHeight;if(!n||!t)return;const l=this.thead?this.thead.offsetHeight:0,i=this._scrollContainer;let s,c;if(i){const h=this.table.getBoundingClientRect(),b=i.getBoundingClientRect(),k=h.top-b.top+i.scrollTop+l;s=i.scrollTop-k,c=i.clientHeight}else{const k=this.table.getBoundingClientRect().top+window.scrollY+l;s=window.scrollY-k,c=window.innerHeight}let d=Math.max(0,Math.floor(s/n)-15);d=Math.min(d,t);const f=Math.min(d+Math.ceil(c/n)+30,t);if(d===this._vStart&&f===this._vEnd)return;this._vStart=d,this._vEnd=f;const p=this.ths.length||1,A=d*n,v=(t-f)*n,w=document.createDocumentFragment();if(A>0){const h=document.createElement("tr");h.className="ln-data-table__spacer",h.setAttribute("aria-hidden","true");const b=document.createElement("td");b.setAttribute("colspan",p),b.style.height=A+"px",h.appendChild(b),w.appendChild(h)}for(let h=d;h<f;h++){const b=this._buildRow(e[h]);b&&w.appendChild(b)}if(v>0){const h=document.createElement("tr");h.className="ln-data-table__spacer",h.setAttribute("aria-hidden","true");const b=document.createElement("td");b.setAttribute("colspan",p),b.style.height=v+"px",h.appendChild(b),w.appendChild(h)}this.tbody.textContent="",this.tbody.appendChild(w),this._selectable&&this._updateSelectAll()},a.prototype._fillRow=function(e,t){T(e,t);const n=e.querySelectorAll("[data-ln-cell-attr]");for(let l=0;l<n.length;l++){const i=n[l],s=i.getAttribute("data-ln-cell-attr").split(",");for(let c=0;c<s.length;c++){const d=s[c].trim().split(":");if(d.length!==2)continue;const f=d[0].trim(),p=d[1].trim();t[f]!=null&&i.setAttribute(p,t[f])}}},a.prototype._showEmptyState=function(e){const t=E(this.dom,e,"ln-data-table");this.tbody.textContent="",t&&this.tbody.appendChild(t)},a.prototype._updateFooter=function(){const e=this._lastTotal,t=this._lastFiltered,n=t<e;if(this._totalSpan&&(this._totalSpan.textContent=S(e)),this._filteredSpan&&(this._filteredSpan.textContent=n?S(t):""),this._filteredWrap&&this._filteredWrap.classList.toggle("hidden",!n),this._selectedSpan){const l=this.selectedIds.size;this._selectedSpan.textContent=l>0?S(l):"",this._selectedWrap&&this._selectedWrap.classList.toggle("hidden",l===0)}},a.prototype.destroy=function(){this.dom[r]&&(this.dom.removeEventListener("ln-data-table:set-data",this._onSetData),this.dom.removeEventListener("ln-data-table:set-loading",this._onSetLoading),this.thead&&(this.thead.removeEventListener("click",this._onSortClick),this.thead.removeEventListener("click",this._onFilterClick)),document.removeEventListener("click",this._onDocClick),document.removeEventListener("keydown",this._onKeydown),this._searchInput&&this._searchInput.removeEventListener("input",this._onSearchInput),this.tbody&&(this.tbody.removeEventListener("click",this._onRowClick),this.tbody.removeEventListener("click",this._onRowAction)),this._onSelectionChange&&this.tbody&&this.tbody.removeEventListener("change",this._onSelectionChange),this._selectAllCheckbox&&this._onSelectAll&&this._selectAllCheckbox.removeEventListener("change",this._onSelectAll),this.dom.removeEventListener("click",this._onClearAll),this._closeFilterDropdown(),this._disableVirtualScroll(),this._data=[],delete this.dom[r])};function g(e,t){const n=e[r];if(n&&t==="data-ln-data-table-selectable"){const l=e.hasAttribute("data-ln-data-table-selectable");l!==n._selectable&&(n._selectable=l,l?n._enableSelection():n._disableSelection())}}O(o,r,a,"ln-data-table",{extraAttributes:["data-ln-data-table-selectable"],onAttributeChange:g})})()})();
@@ -1,10 +0,0 @@
1
- // ── JS state: data table loading ──
2
- // Loading-class added by JS while async data fetches; visual stays here
3
- // because it's a transient JS-controlled state transition.
4
-
5
- .ln-data-table--loading {
6
- tbody {
7
- opacity: 0.5;
8
- pointer-events: none;
9
- }
10
- }