@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
@@ -1,210 +1,232 @@
1
- /* Live Networks — ln-toast (side-accent with icons) */
2
- import { guardBody, cloneTemplateScoped, fill } from '../../ln-core';
3
- import DEFAULT_TEMPLATE_HTML from '../template.html?raw';
4
-
5
- (function () {
6
- const DOM_SELECTOR = "data-ln-toast";
7
- const DOM_ATTRIBUTE = "lnToast";
8
- const TEMPLATE_NAME = "ln-toast-item";
9
-
10
- const TYPE_ICON = { success: 'circle-check', error: 'circle-x', warn: 'alert-triangle', info: 'info-circle' };
11
-
12
- const STATUS_CLASS = { success: 'success', error: 'error', warn: 'warning', info: 'info' };
13
-
14
- const DEFAULT_TITLES = { success: 'Success', error: 'Error', warn: 'Warning', info: 'Information' };
15
-
16
- if (window.__lnToastLoaded) return;
17
- window.__lnToastLoaded = true;
18
-
19
- function _ensureTemplate() {
20
- if (document.querySelector('[data-ln-template="ln-toast-item"]')) return;
21
- if (!document.body) return;
22
- const tmpl = document.createElement('template');
23
- // Trust boundary: DEFAULT_TEMPLATE_HTML is a hardcoded library constant resolved at build time via ?raw import.
24
- tmpl.setAttribute('data-ln-template', 'ln-toast-item');
25
- tmpl.innerHTML = DEFAULT_TEMPLATE_HTML;
26
- document.body.appendChild(tmpl);
27
- }
28
-
29
- function _findContainers(root) {
30
- if (!root || root.nodeType !== 1) return;
31
- const items = Array.from(root.querySelectorAll("[" + DOM_SELECTOR + "]"));
32
- if (root.hasAttribute && root.hasAttribute(DOM_SELECTOR)) items.push(root);
33
- for (const el of items) {
34
- if (!el[DOM_ATTRIBUTE]) new _Component(el);
35
- }
36
- }
37
-
38
- function _Component(dom) {
39
- this.dom = dom;
40
- dom[DOM_ATTRIBUTE] = this;
41
- this.timeoutDefault = parseInt(dom.getAttribute("data-ln-toast-timeout") || "6000", 10);
42
- this.max = parseInt(dom.getAttribute("data-ln-toast-max") || "5", 10);
43
-
44
- for (const li of Array.from(dom.querySelectorAll("[data-ln-toast-item]"))) {
45
- _hydrateLI(li, dom);
46
- }
47
- return this;
48
- }
49
-
50
- _Component.prototype.destroy = function () {
51
- if (!this.dom[DOM_ATTRIBUTE]) return;
52
- for (const li of Array.from(this.dom.children)) {
53
- _dismiss(li);
54
- }
55
- delete this.dom[DOM_ATTRIBUTE];
56
- };
57
-
58
- function _buildItem(opts, container) {
59
- const type = ((opts.type || "info") + "").toLowerCase();
60
- const fragment = cloneTemplateScoped(container, TEMPLATE_NAME, 'ln-toast');
61
- if (!fragment) {
62
- console.warn('[ln-toast] Template "' + TEMPLATE_NAME + '" not found');
63
- return null;
64
- }
65
- const li = fragment.firstElementChild;
66
- if (!li) return null;
67
-
68
- const hasBody = !!(opts.message || (opts.data && opts.data.errors));
69
-
70
- fill(li, {
71
- title: opts.title || DEFAULT_TITLES[type] || DEFAULT_TITLES.info,
72
- role: type === 'error' ? 'alert' : 'status',
73
- ariaLive: type === 'error' ? 'assertive' : 'polite',
74
- hasBody: hasBody
75
- });
76
-
77
- const card = li.querySelector('.ln-toast__card');
78
- if (card) card.classList.add(STATUS_CLASS[type] || 'info');
79
-
80
- const side = li.querySelector('.ln-toast__side');
81
- if (side) {
82
- const useEl = side.querySelector('use');
83
- if (useEl) useEl.setAttribute('href', '#ln-' + (TYPE_ICON[type] || TYPE_ICON.info));
84
- }
85
-
86
- const bodyEl = li.querySelector('.ln-toast__body');
87
- if (bodyEl && hasBody) _renderBody(bodyEl, opts);
88
-
89
- const closeBtn = li.querySelector('.ln-toast__close');
90
- if (closeBtn) closeBtn.addEventListener('click', function () { _dismiss(li); });
91
-
92
- return li;
93
- }
94
-
95
- function _renderBody(bodyEl, opts) {
96
- if (opts.message) {
97
- if (Array.isArray(opts.message)) {
98
- const ul = document.createElement("ul");
99
- for (const msg of opts.message) {
100
- const lie = document.createElement("li");
101
- lie.textContent = msg;
102
- ul.appendChild(lie);
103
- }
104
- bodyEl.appendChild(ul);
105
- } else {
106
- const p = document.createElement("p");
107
- p.textContent = opts.message;
108
- bodyEl.appendChild(p);
109
- }
110
- }
111
- if (opts.data && opts.data.errors) {
112
- const ul = document.createElement("ul");
113
- for (const err of Object.values(opts.data.errors).flat()) {
114
- const lie = document.createElement("li");
115
- lie.textContent = err;
116
- ul.appendChild(lie);
117
- }
118
- bodyEl.appendChild(ul);
119
- }
120
- }
121
-
122
- function _append(cmp, li) {
123
- while (cmp.dom.children.length >= cmp.max) cmp.dom.removeChild(cmp.dom.firstElementChild);
124
- cmp.dom.appendChild(li);
125
- requestAnimationFrame(() => li.classList.add("ln-toast__item--in"));
126
- }
127
-
128
- function _dismiss(li) {
129
- if (!li || !li.parentNode) return;
130
- clearTimeout(li._timer);
131
- li.classList.remove("ln-toast__item--in");
132
- li.classList.add("ln-toast__item--out");
133
- setTimeout(() => { li.parentNode && li.parentNode.removeChild(li); }, 200);
134
- }
135
-
136
- function _resolveContainer(detail) {
137
- let container = detail && detail.container;
138
- if (typeof container === "string") container = document.querySelector(container);
139
- if (!(container instanceof HTMLElement)) {
140
- container = document.querySelector("[" + DOM_SELECTOR + "]") || document.getElementById("ln-toast-container");
141
- }
142
- return container || null;
143
- }
144
-
145
- function _hydrateLI(li, container) {
146
- const type = ((li.getAttribute("data-type") || "info") + "").toLowerCase();
147
- const titleA = li.getAttribute("data-title");
148
- const msgText = (li.innerText || li.textContent || "").trim();
149
-
150
- const built = _buildItem({
151
- type: type,
152
- title: titleA,
153
- message: msgText || undefined
154
- }, container);
155
-
156
- if (!built) return;
157
-
158
- li.parentNode && li.parentNode.replaceChild(built, li);
159
- requestAnimationFrame(() => built.classList.add("ln-toast__item--in"));
160
- }
161
-
162
- function _onEnqueue(e) {
163
- const detail = e.detail || {};
164
- const container = _resolveContainer(detail);
165
- if (!container) {
166
- console.warn('[ln-toast] No toast container found');
167
- return;
168
- }
169
- const cmp = container[DOM_ATTRIBUTE] || new _Component(container);
170
- const li = _buildItem(detail, container);
171
- if (!li) return;
172
- const timeout = Number.isFinite(detail.timeout) ? detail.timeout : cmp.timeoutDefault;
173
- _append(cmp, li);
174
- if (timeout > 0) li._timer = setTimeout(() => _dismiss(li), timeout);
175
- }
176
-
177
- function _onClear(e) {
178
- const detail = (e && e.detail) || {};
179
- if (detail.container) {
180
- const el = _resolveContainer(detail);
181
- if (el) {
182
- for (const child of Array.from(el.children)) _dismiss(child);
183
- }
184
- } else {
185
- const containers = document.querySelectorAll("[" + DOM_SELECTOR + "]");
186
- for (const el of Array.from(containers)) {
187
- for (const child of Array.from(el.children)) _dismiss(child);
188
- }
189
- }
190
- }
191
-
192
- guardBody(function () {
193
- _ensureTemplate();
194
-
195
- window.addEventListener('ln-toast:enqueue', _onEnqueue);
196
- window.addEventListener('ln-toast:clear', _onClear);
197
-
198
- const observer = new MutationObserver(function (muts) {
199
- for (const m of muts) {
200
- if (m.type === 'attributes') { _findContainers(m.target); continue; }
201
- for (const n of m.addedNodes) {
202
- _findContainers(n);
203
- }
204
- }
205
- });
206
- observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [DOM_SELECTOR] });
207
-
208
- _findContainers(document.body);
209
- }, 'ln-toast');
210
- })();
1
+ /* Live Networks — ln-toast (side-accent with icons) */
2
+ import { guardBody, cloneTemplateScoped, fill } from '../../ln-core';
3
+
4
+ (function () {
5
+ const DOM_SELECTOR = "data-ln-toast";
6
+ const DOM_ATTRIBUTE = "lnToast";
7
+ const TEMPLATE_NAME = "ln-toast-item";
8
+
9
+ if (window.__lnToastLoaded) return;
10
+ window.__lnToastLoaded = true;
11
+
12
+ function _promoteTopLayer(container) {
13
+ if (!container || !(container instanceof HTMLElement)) return;
14
+ if (!container.hasAttribute('popover')) {
15
+ container.setAttribute('popover', 'manual');
16
+ }
17
+ if (typeof container.showPopover === 'function') {
18
+ if (container.matches(':popover-open')) {
19
+ try { container.hidePopover(); } catch (_) {}
20
+ }
21
+ try { container.showPopover(); } catch (_) {}
22
+ }
23
+ }
24
+
25
+ function _demoteTopLayerIfEmpty(container) {
26
+ if (!container || !(container instanceof HTMLElement)) return;
27
+ const items = container.querySelectorAll("[data-ln-toast-item]");
28
+ if (items.length === 0) {
29
+ if (typeof container.hidePopover === 'function' && container.matches(':popover-open')) {
30
+ try { container.hidePopover(); } catch (_) {}
31
+ }
32
+ }
33
+ }
34
+
35
+ function _findContainers(root) {
36
+ if (!root || root.nodeType !== 1) return;
37
+ const items = Array.from(root.querySelectorAll("[" + DOM_SELECTOR + "]"));
38
+ if (root.hasAttribute && root.hasAttribute(DOM_SELECTOR)) items.push(root);
39
+ for (const el of items) {
40
+ if (!el[DOM_ATTRIBUTE]) new _Component(el);
41
+ }
42
+ }
43
+
44
+ function _Component(dom) {
45
+ this.dom = dom;
46
+ dom[DOM_ATTRIBUTE] = this;
47
+ this.timeoutDefault = parseInt(dom.getAttribute("data-ln-toast-timeout") || "6000", 10);
48
+ this.max = parseInt(dom.getAttribute("data-ln-toast-max") || "5", 10);
49
+
50
+ const items = Array.from(dom.querySelectorAll("[data-ln-toast-item]"));
51
+ while (items.length > this.max) dom.removeChild(items.shift());
52
+ for (const li of items) _hydrateLI(li, this);
53
+
54
+ if (items.length > 0) {
55
+ _promoteTopLayer(dom);
56
+ }
57
+
58
+ return this;
59
+ }
60
+
61
+ _Component.prototype.destroy = function () {
62
+ if (!this.dom[DOM_ATTRIBUTE]) return;
63
+ for (const li of Array.from(this.dom.querySelectorAll("[data-ln-toast-item]"))) {
64
+ _dismiss(li);
65
+ }
66
+ _demoteTopLayerIfEmpty(this.dom);
67
+ delete this.dom[DOM_ATTRIBUTE];
68
+ };
69
+
70
+ function _buildItem(opts, container) {
71
+ const type = ((opts.type || "") + "").trim().toLowerCase();
72
+ const fragment = cloneTemplateScoped(container, TEMPLATE_NAME, 'ln-toast');
73
+ if (!fragment) {
74
+ console.warn('[ln-toast] Template "' + TEMPLATE_NAME + '" not found');
75
+ return null;
76
+ }
77
+
78
+ // fill() runs on the DocumentFragment, BEFORE extracting the <li>.
79
+ // data-ln-attr="class:type" sits on the template root <li>; fill()'s
80
+ // querySelectorAll never matches its own root, and here the fragment is
81
+ // the root, so the <li> (its child) is a matched descendant and gets
82
+ // class="{type}". title/message data-ln-field targets are descendants
83
+ // too, so one call resolves all three. See plan Finding: fill-on-fragment.
84
+ fill(fragment, {
85
+ type: type,
86
+ title: opts.title,
87
+ message: typeof opts.message === 'string' ? opts.message : undefined
88
+ });
89
+
90
+ const li = fragment.firstElementChild;
91
+ if (!li) return null;
92
+
93
+ // Items are addressed by [data-ln-toast-item] everywhere (eviction / clear / destroy / hydration) — stamp it if the consumer template omitted it.
94
+ if (!li.hasAttribute('data-ln-toast-item')) li.setAttribute('data-ln-toast-item', '');
95
+
96
+ // ORDERING: classList.add MUST follow fill(). fill set class via
97
+ // setAttribute('class', type), which clobbers the whole attribute — a
98
+ // pre-added .ln-enter would be wiped. Add the transient enter flag now.
99
+ li.classList.add('ln-enter');
100
+
101
+ const bodyEl = li.querySelector('.body');
102
+ if (bodyEl) _renderBody(bodyEl, opts);
103
+
104
+ const closeBtn = li.querySelector('[data-ln-toast-close]');
105
+ if (closeBtn) closeBtn.addEventListener('click', function () { _dismiss(li); });
106
+
107
+ return li;
108
+ }
109
+
110
+ // Runtime DATA rendering only (arrays / error maps) — plain string
111
+ // messages are already handled by fill()'s data-ln-field above.
112
+ function _renderBody(bodyEl, opts) {
113
+ if (Array.isArray(opts.message)) {
114
+ const ul = document.createElement("ul");
115
+ for (const msg of opts.message) {
116
+ const lie = document.createElement("li");
117
+ lie.textContent = msg;
118
+ ul.appendChild(lie);
119
+ }
120
+ bodyEl.appendChild(ul);
121
+ }
122
+ if (opts.data && opts.data.errors) {
123
+ const ul = document.createElement("ul");
124
+ for (const err of Object.values(opts.data.errors).flat()) {
125
+ const lie = document.createElement("li");
126
+ lie.textContent = err;
127
+ ul.appendChild(lie);
128
+ }
129
+ bodyEl.appendChild(ul);
130
+ }
131
+ }
132
+
133
+ function _append(cmp, li) {
134
+ // Evict by [data-ln-toast-item] — raw children include the nested <template> (recommended placement).
135
+ const items = Array.from(cmp.dom.querySelectorAll("[data-ln-toast-item]"));
136
+ while (items.length >= cmp.max && items.length > 0) cmp.dom.removeChild(items.shift());
137
+ cmp.dom.appendChild(li);
138
+ _promoteTopLayer(cmp.dom);
139
+ requestAnimationFrame(() => li.classList.remove("ln-enter"));
140
+ }
141
+
142
+ function _dismiss(li) {
143
+ if (!li || !li.parentNode) return;
144
+ const container = li.parentNode;
145
+ clearTimeout(li._timer);
146
+ li.classList.remove("ln-enter");
147
+ li.classList.add("ln-out");
148
+ setTimeout(() => {
149
+ if (li.parentNode) {
150
+ li.parentNode.removeChild(li);
151
+ _demoteTopLayerIfEmpty(container);
152
+ }
153
+ }, 200);
154
+ }
155
+
156
+ function _resolveContainer(detail) {
157
+ let container = detail && detail.container;
158
+ if (typeof container === "string") container = document.querySelector(container);
159
+ if (!(container instanceof HTMLElement)) {
160
+ container = document.querySelector("[" + DOM_SELECTOR + "]") || document.getElementById("ln-toast-container");
161
+ }
162
+ return container || null;
163
+ }
164
+
165
+ function _hydrateLI(li, cmp) {
166
+ if (li._lnToastHydrated) return;
167
+ li._lnToastHydrated = true;
168
+
169
+ const closeBtn = li.querySelector('[data-ln-toast-close]');
170
+ if (closeBtn) closeBtn.addEventListener('click', function () { _dismiss(li); });
171
+
172
+ const raw = li.getAttribute('data-ln-toast-timeout');
173
+ const parsed = raw !== null ? parseInt(raw, 10) : NaN;
174
+ const timeout = Number.isFinite(parsed) ? parsed : cmp.timeoutDefault;
175
+ if (timeout > 0) li._timer = setTimeout(function () { _dismiss(li); }, timeout);
176
+ }
177
+
178
+ function _onEnqueue(e) {
179
+ const detail = e.detail || {};
180
+ const container = _resolveContainer(detail);
181
+ if (!container) {
182
+ console.warn('[ln-toast] No toast container found');
183
+ return;
184
+ }
185
+ const cmp = container[DOM_ATTRIBUTE] || new _Component(container);
186
+ const li = _buildItem(detail, container);
187
+ if (!li) return;
188
+ const timeout = Number.isFinite(detail.timeout) ? detail.timeout : cmp.timeoutDefault;
189
+ _append(cmp, li);
190
+ if (timeout > 0) li._timer = setTimeout(() => _dismiss(li), timeout);
191
+ }
192
+
193
+ function _onClear(e) {
194
+ const detail = (e && e.detail) || {};
195
+ if (detail.container) {
196
+ const el = _resolveContainer(detail);
197
+ if (el) {
198
+ for (const child of Array.from(el.querySelectorAll("[data-ln-toast-item]"))) _dismiss(child);
199
+ }
200
+ } else {
201
+ const containers = document.querySelectorAll("[" + DOM_SELECTOR + "]");
202
+ for (const el of Array.from(containers)) {
203
+ for (const child of Array.from(el.querySelectorAll("[data-ln-toast-item]"))) _dismiss(child);
204
+ }
205
+ }
206
+ }
207
+
208
+ guardBody(function () {
209
+ window.addEventListener('ln-toast:enqueue', _onEnqueue);
210
+ window.addEventListener('ln-toast:clear', _onClear);
211
+ window.addEventListener('ln-modal:open', function () {
212
+ const containers = document.querySelectorAll("[" + DOM_SELECTOR + "]");
213
+ for (const container of Array.from(containers)) {
214
+ if (container.querySelectorAll("[data-ln-toast-item]").length > 0) {
215
+ _promoteTopLayer(container);
216
+ }
217
+ }
218
+ });
219
+
220
+ const observer = new MutationObserver(function (muts) {
221
+ for (const m of muts) {
222
+ if (m.type === 'attributes') { _findContainers(m.target); continue; }
223
+ for (const n of m.addedNodes) {
224
+ _findContainers(n);
225
+ }
226
+ }
227
+ });
228
+ observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: [DOM_SELECTOR] });
229
+
230
+ _findContainers(document.body);
231
+ }, 'ln-toast');
232
+ })();
@@ -1,14 +1,18 @@
1
- <li class="ln-toast__item">
2
- <div class="ln-toast__card" data-ln-attr="role:role, aria-live:ariaLive">
3
- <div class="ln-toast__side">
4
- <svg class="ln-icon" aria-hidden="true"><use href=""></use></svg>
5
- </div>
6
- <div class="ln-toast__content">
7
- <div class="ln-toast__head">
8
- <strong class="ln-toast__title" data-ln-field="title"></strong>
9
- </div>
10
- <button type="button" class="ln-toast__close" aria-label="Close"><svg class="ln-icon" aria-hidden="true"><use href="#ln-x"></use></svg></button>
11
- <div class="ln-toast__body" data-ln-show="hasBody"></div>
12
- </div>
1
+ <!-- Developer example — copy into your page markup inside <template data-ln-template="ln-toast-item">. Not a runtime artifact. -->
2
+ <li data-ln-toast-item data-ln-attr="class:type">
3
+ <div class="icon">
4
+ <ul>
5
+ <li data-ln-toast-when="success"><svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-circle-check"></use></svg></li>
6
+ <li data-ln-toast-when="error"><svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-circle-x"></use></svg></li>
7
+ <li data-ln-toast-when="warn"><svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-alert-triangle"></use></svg></li>
8
+ <li data-ln-toast-when="info"><svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-info-circle"></use></svg></li>
9
+ </ul>
13
10
  </div>
11
+ <section class="content">
12
+ <header>
13
+ <strong class="title" data-ln-field="title"></strong>
14
+ <button type="button" data-ln-toast-close aria-label="Close"><svg class="ln-icon" aria-hidden="true"><use href="#ln-icon-x"></use></svg></button>
15
+ </header>
16
+ <main class="body" data-ln-field="message"></main>
17
+ </section>
14
18
  </li>
@@ -133,5 +133,64 @@ Combine the alert card with `data-ln-persist` so that once the user closes the a
133
133
 
134
134
  ## Related
135
135
  - **[`ln-accordion`](../ln-accordion/README.md)** — Single-open coordinator for toggle panels.
136
- - **[`ln-dropdown`](../ln-dropdown/README.md)** — Menu wrapper adding click-outside/teleportation.
137
- - **Architecture deep-dive** — [`docs/js/toggle.md`](../../docs/js/toggle.md).
136
+ - **[`ln-dropdown`](../ln-dropdown/README.md)** — Menu wrapper adding click-outside/top-layer promotion.
137
+
138
+ ---
139
+
140
+ ## 🔧 Internals
141
+
142
+ Source: `js/ln-toggle/ln-toggle.js`. Imports `registerComponent`, `dispatch`, `dispatchCancelable` from `ln-core/helpers.js` and `persistGet`/`persistSet` from `ln-core/persist.js` — no other library component.
143
+
144
+ ### Instance state
145
+
146
+ | Field | Description |
147
+ |---|---|
148
+ | `dom` | The panel element |
149
+ | `isOpen` | Cache of the attribute, kept current by `_syncAttribute`; used only to skip no-op transitions and as a read-only convenience getter |
150
+
151
+ No cached trigger list, no saved previous-value for revert, no timer/queue — every attribute change runs the pipeline immediately. Triggers are re-queried document-wide on each state change; cheap because the matching set is typically 1-3 elements.
152
+
153
+ ### Init
154
+
155
+ `registerComponent` scans for `[data-ln-toggle]`, watches `data-ln-toggle`/`data-ln-toggle-for` mutations, and calls `_attachTriggers(document.body)` on init. `_component(dom)`:
156
+ 1. **Persistence restore** — if `data-ln-persist` is present, `persistGet('toggle', dom)`; a saved value is applied via `setAttribute` *inside the constructor*. The observer does fire for that write, but `el[DOM_ATTRIBUTE]` is still `undefined` at that point (assigned only after the constructor returns), so `_syncAttribute`'s instance guard catches it and the pipeline does not run — no spurious `:before-open`/`:open` during restore.
157
+ 2. Reads the (possibly restored) attribute into `isOpen`, adds `.open` if true, calls `_syncTriggerAria` for the initial ARIA state.
158
+
159
+ No `:open`/`:close` event fires at init — the attribute is already in its final state; there's been no transition.
160
+
161
+ ### Trigger attachment
162
+
163
+ `_attachTriggers(root)` finds every `[data-ln-toggle-for]` under `root` (skipping ones already wired via a stashed-handler guard) and binds `click`: ignores modifier-key clicks, `preventDefault`s, resolves the target by ID, then writes `data-ln-toggle` — `"open"`/`"close"` for an explicit `data-ln-toggle-action`, or the inverse of the current value for the default toggle action. The handler reference is stashed on the trigger for `destroy` to remove later. Newly attached triggers also get their `aria-expanded` set immediately from the target's current state (no waiting for the next transition).
164
+
165
+ ### `_syncAttribute` — the transition pipeline
166
+
167
+ Runs only when the mutated element already has an `lnToggle` instance (a brand-new element getting the attribute takes the init/upgrade path instead, via `findElements`, not a transition). Order, for the "open" direction (close mirrors it):
168
+
169
+ 1. `dispatchCancelable('ln-toggle:before-open')` — listeners see `isOpen` at its **pre-transition** value.
170
+ 2. If canceled: revert the attribute to `'close'` and return. The revert re-fires the observer, but `isOpen` was never changed, so the second pass is a no-op — no loop, no duplicate event.
171
+ 3. `isOpen = true`, `.open` class added.
172
+ 4. `_syncTriggerAria(el, true)` — before the post-event, so `:open` listeners see settled ARIA.
173
+ 5. `dispatch('ln-toggle:open')`.
174
+ 6. If `data-ln-persist`: `persistSet('toggle', el, 'open')` — last, so a listener that removes `data-ln-persist` synchronously during `:open` skips the write.
175
+
176
+ This ordering (cancelable → class → aria → event → persist) is the same shape for every transition in the library that follows the attribute-as-contract pattern.
177
+
178
+ ### `_syncTriggerAria`
179
+
180
+ Document-scoped `querySelectorAll('[data-ln-toggle-for="<id>"]')`, sets `aria-expanded` on each match — document-scoped because triggers can live anywhere relative to their panel. Drives both screen-reader state and the CSS chevron rotation rule in `scss/components/_toggle.scss`. Called from init, every transition, and `_attachTriggers` (so late-added triggers inherit current state).
181
+
182
+ ### Persistence
183
+
184
+ Key: `ln:toggle:{pagePath}:{id}` (`_resolveKey` in `ln-core/persist.js`); path-scoped and non-overrideable, so the same panel `id` on two routes stores independently. `persistGet`/`persistSet` wrap `localStorage` in `try/catch` and swallow failures silently (private browsing, quota, disabled storage) — toggle keeps working without persistence, no error surfaces.
185
+
186
+ ### Destroy
187
+
188
+ Guards double-destroy, dispatches `ln-toggle:destroyed`, removes the click listener from every matching trigger (using the stashed handler ref), deletes the instance. Does **not** remove `data-ln-toggle` or the `.open` class — only the JS coupling is severed; the consumer removes markup state separately if a full teardown is wanted.
189
+
190
+ ### What it deliberately does not do
191
+
192
+ No keyboard handling (ESC, Space/Enter on the panel), no outside-click, no focus management, no resize listener. Each is a separate concern owned by a wrapping component: `ln-dropdown` (outside-click/resize/teleport), `ln-modal` (focus trap), or project code.
193
+
194
+ ### Cross-component contract
195
+
196
+ `ln-accordion` and `ln-dropdown` both reach in only through the public contract — listening for `ln-toggle:open`/`:close`, and writing `data-ln-toggle="..."` directly (never an instance method). `ln-accordion` writes `'close'` on siblings from its wrapper listener; `ln-dropdown` listens on its inner toggle and teleports/wires outside-click on `:open`, reverses on `:close`. Because the attribute is the only mutation path, cancelable events and persistence fire identically regardless of which caller changed the state — there is no private mutator to bypass the pipeline.
@@ -0,0 +1,13 @@
1
+ @use '../../scss/config/mixins' as *;
2
+
3
+ [data-ln-debug] {
4
+ // 1. Toggle Panel Missing ID: panels controlled by data-ln-toggle must have an id for triggering/ARIA linking
5
+ [data-ln-toggle]:not([id]) {
6
+ @include dev-dom-error("[data-ln-toggle] is missing a required 'id' attribute for trigger linking.");
7
+ }
8
+
9
+ // 2. Toggle Trigger Empty ID: trigger elements must point to a target panel ID
10
+ [data-ln-toggle-for=""] {
11
+ @include dev-inline-error("data-ln-toggle-for target ID cannot be empty");
12
+ }
13
+ }
@@ -1 +1 @@
1
- (function(){"use strict";function h(e,t,o){e.dispatchEvent(new CustomEvent(t,{bubbles:!0,detail:o||{}}))}function w(e,t,o){const i=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:o||{}});return e.dispatchEvent(i),i}function O(e,t){if(!document.body){document.addEventListener("DOMContentLoaded",function(){O(e,t)}),console.warn("["+t+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}function y(e,t,o,i){if(e.nodeType!==1)return;const b=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",n=Array.from(e.querySelectorAll(b));e.matches&&e.matches(b)&&n.push(e);for(const r of n)r[o]||(r[o]=new i(r))}function I(e,t,o,i,g={}){const b=g.extraAttributes||[],n=g.onAttributeChange||null,r=g.onInit||null;function s(d){const f=d||document.body;y(f,e,t,o),r&&r(f)}return O(function(){const d=new MutationObserver(function(p){for(let l=0;l<p.length;l++){const u=p[l];if(u.type==="childList"){for(let a=0;a<u.addedNodes.length;a++){const c=u.addedNodes[a];c.nodeType===1&&(y(c,e,t,o),r&&r(c))}for(let a=0;a<u.removedNodes.length;a++){const c=u.removedNodes[a];if(c.nodeType===1){const L=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",m=Array.from(c.querySelectorAll(L));c.matches&&c.matches(L)&&m.push(c);for(let A=0;A<m.length;A++){const S=m[A];if(!document.contains(S)){const v=S[t];v&&typeof v.destroy=="function"&&v.destroy()}}}}}else u.type==="attributes"&&(n&&u.target[t]?n(u.target,u.attributeName):(y(u.target,e,t,o),r&&r(u.target)))}});let f=[];if(e.indexOf("[")!==-1){const p=/\[([\w-]+)/g;let l;for(;(l=p.exec(e))!==null;)f.push(l[1])}else f.push(e);d.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:f.concat(b)})},i),window[t]=s,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){s(document.body)}):s(document.body),s}const x={};function M(e,t){x[e]=t}function D(e){return x[e]||{ingress:t=>t,egress:t=>t}}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=M,window.lnCore.getDataMapper=D);const _="ln:";function q(){return location.pathname.replace(/\/+$/,"").toLowerCase()||"/"}function C(e,t){const o=t.getAttribute("data-ln-persist"),i=o!==null&&o!==""?o:t.id;return i?_+e+":"+q()+":"+i:(console.warn('[ln-persist] Element requires id or data-ln-persist="key"',t),null)}function N(e,t){const o=C(e,t);if(!o)return null;try{const i=localStorage.getItem(o);return i!==null?JSON.parse(i):null}catch{return null}}function E(e,t,o){const i=C(e,t);if(i)try{localStorage.setItem(i,JSON.stringify(o))}catch{}}(function(){const e="data-ln-toggle",t="lnToggle";if(window[t]!==void 0)return;function o(n){const r=Array.from(n.querySelectorAll("[data-ln-toggle-for]"));n.hasAttribute&&n.hasAttribute("data-ln-toggle-for")&&r.push(n);for(const s of r){if(s[t+"Trigger"])continue;const d=function(l){if(l.ctrlKey||l.metaKey||l.button===1)return;l.preventDefault();const u=s.getAttribute("data-ln-toggle-for"),a=document.getElementById(u);if(!a||!a[t])return;const c=s.getAttribute("data-ln-toggle-action")||"toggle";if(c==="open")a.setAttribute(e,"open");else if(c==="close")a.setAttribute(e,"close");else if(c==="toggle"){const T=a.getAttribute(e);a.setAttribute(e,T==="open"?"close":"open")}};s.addEventListener("click",d),s[t+"Trigger"]=d;const f=s.getAttribute("data-ln-toggle-for"),p=document.getElementById(f);p&&p[t]&&s.setAttribute("aria-expanded",p[t].isOpen?"true":"false")}}function i(n,r){const s=document.querySelectorAll('[data-ln-toggle-for="'+n.id+'"]');for(const d of s)d.setAttribute("aria-expanded",r?"true":"false")}function g(n){if(this.dom=n,n.hasAttribute("data-ln-persist")){const r=N("toggle",n);r!==null&&n.setAttribute(e,r)}return this.isOpen=n.getAttribute(e)==="open",this.isOpen&&n.classList.add("open"),i(n,this.isOpen),this}g.prototype.destroy=function(){if(!this.dom[t])return;h(this.dom,"ln-toggle:destroyed",{target:this.dom});const n=document.querySelectorAll('[data-ln-toggle-for="'+this.dom.id+'"]');for(const r of n)r[t+"Trigger"]&&(r.removeEventListener("click",r[t+"Trigger"]),delete r[t+"Trigger"]);delete this.dom[t]};function b(n){const r=n[t];if(!r)return;const d=n.getAttribute(e)==="open";if(d!==r.isOpen)if(d){if(w(n,"ln-toggle:before-open",{target:n}).defaultPrevented){n.setAttribute(e,"close");return}r.isOpen=!0,n.classList.add("open"),i(n,!0),h(n,"ln-toggle:open",{target:n}),n.hasAttribute("data-ln-persist")&&E("toggle",n,"open")}else{if(w(n,"ln-toggle:before-close",{target:n}).defaultPrevented){n.setAttribute(e,"open");return}r.isOpen=!1,n.classList.remove("open"),i(n,!1),h(n,"ln-toggle:close",{target:n}),n.hasAttribute("data-ln-persist")&&E("toggle",n,"close")}}I(e,t,g,"ln-toggle",{extraAttributes:["data-ln-toggle-for"],onAttributeChange:b,onInit:o})})()})();
1
+ (function(){"use strict";if(typeof window<"u"){const t=console.warn;console.warn=function(...e){typeof e[0]=="string"&&(e[0].startsWith("[ln-")||e[0].startsWith("[lnCore"))&&!(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))||t.apply(console,e)}}const b={};function K(t,e){b[t]||(b[t]=document.querySelector('[data-ln-template="'+t+'"]'));const o=b[t];return o?o.content.cloneNode(!0):(console.warn("["+(e||"ln-core")+'] Template "'+t+'" not found'),null)}function y(t,e,o){t.dispatchEvent(new CustomEvent(e,{bubbles:!0,detail:o||{}}))}function _(t,e,o){const l=new CustomEvent(e,{bubbles:!0,cancelable:!0,detail:o||{}});return t.dispatchEvent(l),l}function S(t,e){if(!t||!e)return t;const o=t.querySelectorAll("[data-ln-field]");for(let i=0;i<o.length;i++){const s=o[i],r=s.getAttribute("data-ln-field");e[r]!=null&&(s.textContent=e[r])}const l=t.querySelectorAll("[data-ln-attr]");for(let i=0;i<l.length;i++){const s=l[i],r=s.getAttribute("data-ln-attr").split(",");for(let c=0;c<r.length;c++){const a=r[c].trim().split(":");if(a.length!==2)continue;const d=a[0].trim(),f=a[1].trim();e[f]!=null&&s.setAttribute(d,e[f])}}const u=t.querySelectorAll("[data-ln-show]");for(let i=0;i<u.length;i++){const s=u[i],r=s.getAttribute("data-ln-show");r in e&&s.classList.toggle("hidden",!e[r])}const n=t.querySelectorAll("[data-ln-class]");for(let i=0;i<n.length;i++){const s=n[i],r=s.getAttribute("data-ln-class").split(",");for(let c=0;c<r.length;c++){const a=r[c].trim().split(":");if(a.length!==2)continue;const d=a[0].trim(),f=a[1].trim();f in e&&s.classList.toggle(d,!!e[f])}}return t}function j(t,e){t.matches&&t.matches("[data-ln-form], [data-ln-fillable]")&&t.dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));const o=t.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let l=0;l<o.length;l++)o[l].dispatchEvent(new CustomEvent("ln-fill",{detail:e??null,bubbles:!0}));return t}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(t){if(!(!t.target.matches||!t.target.matches("[data-ln-fillable]")))if(t.detail)S(t.target,t.detail);else{const e=t.target.querySelectorAll("[data-ln-field]");for(let o=0;o<e.length;o++)e[o].textContent=""}})));function O(t,e){if(!t||!e)return t;const o=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(;o.nextNode();){const n=o.currentNode;n.textContent.indexOf("{{")!==-1&&(n.textContent=n.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(i,s){return e[s]!==void 0?e[s]:""}))}const l=function(n,i){return e[i]!==void 0?e[i]:""},u=Array.from(t.querySelectorAll("*"));t.nodeType===1&&u.push(t);for(let n=0;n<u.length;n++){const i=u[n],s=i.attributes;for(let r=0;r<s.length;r++){const c=s[r];c.value.indexOf("{{")!==-1&&i.setAttribute(c.name,c.value.replace(/\{\{\s*(\w+)\s*\}\}/g,l))}}return t}function B(t,e,o,l,u,n){const i={};for(let r=0;r<t.children.length;r++){const c=t.children[r],a=c.getAttribute("data-ln-key");a&&(i[a]=c)}const s=document.createDocumentFragment();for(let r=0;r<e.length;r++){const c=e[r],a=String(l(c));let d=i[a];if(d)u(d,c,r);else{const f=K(o,n);if(!f||(O(f,c),d=f.firstElementChild,!d))continue;d.setAttribute("data-ln-key",a),u(d,c,r)}s.appendChild(d)}t.textContent="",t.appendChild(s)}function q(t,e){if(!document.body){document.addEventListener("DOMContentLoaded",function(){q(t,e)}),console.warn("["+e+'] Script loaded before <body> — add "defer" to your <script> tag');return}t()}function w(t,e,o,l){if(t.nodeType!==1)return;const n=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",i=Array.from(t.querySelectorAll(n));t.matches&&t.matches(n)&&i.push(t);for(const s of i)s[o]||(s[o]=new l(s))}function F(t,e,o,l,u={}){const n=u.extraAttributes||[],i=u.onAttributeChange||null,s=u.onInit||null;function r(c){const a=c||document.body;w(a,t,e,o),s&&s(a)}return q(function(){const c=new MutationObserver(function(d){for(let f=0;f<d.length;f++){const g=d[f];if(g.type==="childList"){for(let h=0;h<g.addedNodes.length;h++){const p=g.addedNodes[h];p.nodeType===1&&(w(p,t,e,o),s&&s(p))}for(let h=0;h<g.removedNodes.length;h++){const p=g.removedNodes[h];if(p.nodeType===1){const N=t.indexOf("[")!==-1||t.indexOf(".")!==-1||t.indexOf("#")!==-1?t:"["+t+"]",A=Array.from(p.querySelectorAll(N));p.matches&&p.matches(N)&&A.push(p);for(let v=0;v<A.length;v++){const M=A[v];if(!document.contains(M)){const L=M[e];L&&typeof L.destroy=="function"&&L.destroy()}}}}}else g.type==="attributes"&&(i&&g.target[e]?i(g.target,g.attributeName):(w(g.target,t,e,o),s&&s(g.target)))}});let a=[];if(t.indexOf("[")!==-1){const d=/\[([\w-]+)/g;let f;for(;(f=d.exec(t))!==null;)a.push(f[1])}else a.push(t);c.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:a.concat(n)})},l),window[e]=r,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){r(document.body)}):r(document.body),r}const x={};function W(t,e){x[t]=e}function P(t){return x[t]||{ingress:e=>e,egress:e=>e}}const E={};function k(t,e){if(!t||typeof e!="object")return;const o=t.toLowerCase().split("-")[0];E[o]=e}function G(t){if(!t)return null;const e=t.toLowerCase().split("-")[0];return E[e]||null}k("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=W,window.lnCore.getDataMapper=P,window.lnCore.registerLocaleFallback=k,window.lnCore.getLocaleFallback=G,window.lnCore.fillTemplate=O,window.lnCore.fill=S,window.lnCore.lnFill=j,window.lnCore.renderList=B);const U="ln:";let m=null;function T(){if(m!==null)return m;try{if(typeof localStorage>"u")return m=!1,!1;const t="__ln_test__";localStorage.setItem(t,t),localStorage.removeItem(t),m=!0}catch{m=!1}return m}function J(){return location.pathname.replace(/\/+$/,"").toLowerCase()||"/"}function R(t,e){const o=e.getAttribute("data-ln-persist"),l=o!==null&&o!==""?o:e.id;return l?U+t+":"+J()+":"+l:(console.warn('[ln-persist] Element requires id or data-ln-persist="key"',e),null)}function X(t,e){if(!T())return null;const o=R(t,e);if(!o)return null;try{const l=localStorage.getItem(o);return l!==null?JSON.parse(l):null}catch{return null}}function D(t,e,o){if(!T())return;const l=R(t,e);if(l)try{o==null?localStorage.removeItem(l):localStorage.setItem(l,JSON.stringify(o))}catch{}}function I(t){return(t||"").replace(/^#/,"")}function C(t){const e=t===void 0?location.hash:t,o={},l=I(e);if(!l)return o;const u=l.split("&");for(let n=0;n<u.length;n++){const i=u[n];if(!i)continue;const s=i.indexOf(":"),r=s>-1?i.slice(0,s):i,c=s>-1?i.slice(s+1):"";if(r)try{o[r]=decodeURIComponent(c)}catch{o[r]=c}}return o}function H(t){if(!t)return null;const e=C();return t in e?e[t]:null}function $(t,e){if(!t)return;const o=C();e==null?delete o[t]:o[t]=String(e);const u=Object.keys(o).map(function(n){const i=o[n];return i===""?n:n+":"+encodeURIComponent(i)}).join("&");I(location.hash)!==u&&(location.hash=u)}function z(t){return t.button===1||t.ctrlKey||t.metaKey||t.shiftKey?!1:(t.preventDefault(),!0)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=C,window.lnCore.hashGet=H,window.lnCore.hashSet=$,window.lnCore.hashLinkClick=z),(function(){const t="data-ln-toggle",e="lnToggle";if(window[e]!==void 0)return;function o(n,i){const s=document.querySelectorAll('[data-ln-toggle-for="'+n.id+'"]');for(const r of s)r.setAttribute("aria-expanded",i?"true":"false")}function l(n){this.dom=n;const i=this;if(this._onRequestOpen=function(){i.open()},this._onRequestClose=function(){i.close()},this._onRequestToggle=function(){i.toggle()},this.dom.addEventListener("ln-toggle:request-open",this._onRequestOpen),this.dom.addEventListener("ln-toggle:request-close",this._onRequestClose),this.dom.addEventListener("ln-toggle:request-toggle",this._onRequestToggle),n.hasAttribute("data-ln-persist")){const s=X("toggle",n);s!==null&&n.setAttribute(t,s)}return this.isOpen=n.getAttribute(t)==="open",this.isOpen&&n.classList.add("open"),o(n,this.isOpen),this}l.prototype.open=function(){this.dom.setAttribute(t,"open")},l.prototype.close=function(){this.dom.setAttribute(t,"close")},l.prototype.toggle=function(){const n=this.dom.getAttribute(t);this.dom.setAttribute(t,n==="open"?"close":"open")},l.prototype.destroy=function(){this.dom[e]&&(this.dom.removeEventListener("ln-toggle:request-open",this._onRequestOpen),this.dom.removeEventListener("ln-toggle:request-close",this._onRequestClose),this.dom.removeEventListener("ln-toggle:request-toggle",this._onRequestToggle),y(this.dom,"ln-toggle:destroyed",{target:this.dom}),delete this.dom[e])};function u(n){const i=n[e];if(!i)return;const r=n.getAttribute(t)==="open";if(r!==i.isOpen)if(r){if(_(n,"ln-toggle:before-open",{target:n}).defaultPrevented){n.setAttribute(t,"close");return}i.isOpen=!0,n.classList.add("open"),o(n,!0),y(n,"ln-toggle:open",{target:n}),n.hasAttribute("data-ln-persist")&&D("toggle",n,"open")}else{if(_(n,"ln-toggle:before-close",{target:n}).defaultPrevented){n.setAttribute(t,"open");return}i.isOpen=!1,n.classList.remove("open"),o(n,!1),y(n,"ln-toggle:close",{target:n}),n.hasAttribute("data-ln-persist")&&D("toggle",n,"close")}}document.addEventListener("click",function(n){if(n.ctrlKey||n.metaKey||n.button===1)return;const i=n.target.closest("[data-ln-toggle-for]");if(i){const s=i.getAttribute("data-ln-toggle-for"),r=document.getElementById(s);if(r&&r[e]){n.preventDefault();const c=i.getAttribute("data-ln-toggle-action")||"toggle";if(c==="open")r.setAttribute(t,"open");else if(c==="close")r.setAttribute(t,"close");else if(c==="toggle"){const a=r.getAttribute(t);r.setAttribute(t,a==="open"?"close":"open")}}}}),F(t,e,l,"ln-toggle",{onAttributeChange:u})})()})();