@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,201 +1,137 @@
1
- import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../../ln-core';
2
-
3
- (function () {
4
- const DOM_SELECTOR = 'data-ln-modal';
5
- const DOM_ATTRIBUTE = 'lnModal';
6
-
7
- if (window[DOM_ATTRIBUTE] !== undefined) return;
8
-
9
-
10
- function _attachTriggers(root) {
11
- const triggers = Array.from(root.querySelectorAll('[data-ln-modal-for]'));
12
- if (root.hasAttribute && root.hasAttribute('data-ln-modal-for')) {
13
- triggers.push(root);
14
- }
15
- for (const btn of triggers) {
16
- if (btn[DOM_ATTRIBUTE + 'Trigger']) continue;
17
- const handler = function (e) {
18
- if (e.ctrlKey || e.metaKey || e.button === 1) return;
19
- e.preventDefault();
20
- const modalId = btn.getAttribute('data-ln-modal-for');
21
- const target = document.getElementById(modalId);
22
- if (!target) {
23
- console.warn('[ln-modal] No modal found for data-ln-modal-for="' + modalId + '"');
24
- return;
25
- }
26
- if (!target[DOM_ATTRIBUTE]) return;
27
- const current = target.getAttribute(DOM_SELECTOR);
28
- target.setAttribute(DOM_SELECTOR, current === 'open' ? 'close' : 'open');
29
- };
30
- btn.addEventListener('click', handler);
31
- btn[DOM_ATTRIBUTE + 'Trigger'] = handler;
32
- }
33
- }
34
-
35
- // ─── Component ─────────────────────────────────────────────
36
-
37
- function _component(dom) {
38
- this.dom = dom;
39
- this.isOpen = dom.getAttribute(DOM_SELECTOR) === 'open';
40
-
41
- const self = this;
42
-
43
- this._onEscape = function (e) {
44
- if (e.key === 'Escape') self.dom.setAttribute(DOM_SELECTOR, 'close');
45
- };
46
-
47
- this._onFocusTrap = function (e) {
48
- if (e.key !== 'Tab') return;
49
- // Focuses in DOM order — positive tabindex not supported (anti-pattern per WCAG)
50
- const focusable = Array.prototype.filter.call(
51
- self.dom.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'),
52
- isVisible
53
- );
54
- if (focusable.length === 0) return;
55
- const first = focusable[0];
56
- const last = focusable[focusable.length - 1];
57
- if (e.shiftKey) {
58
- if (document.activeElement === first) { e.preventDefault(); last.focus(); }
59
- } else {
60
- if (document.activeElement === last) { e.preventDefault(); first.focus(); }
61
- }
62
- };
63
-
64
- this._onClose = function (e) {
65
- e.preventDefault();
66
- self.dom.setAttribute(DOM_SELECTOR, 'close');
67
- };
68
-
69
- _attachCloseButtons(this);
70
-
71
- // Apply initial state if open
72
- if (this.isOpen) {
73
- this.dom.setAttribute('aria-modal', 'true');
74
- this.dom.setAttribute('role', 'dialog');
75
- document.body.classList.add('ln-modal-open');
76
- document.addEventListener('keydown', this._onEscape);
77
- document.addEventListener('keydown', this._onFocusTrap);
78
- }
79
-
80
- return this;
81
- }
82
-
83
- _component.prototype.destroy = function () {
84
- if (!this.dom[DOM_ATTRIBUTE]) return;
85
-
86
- if (this.isOpen) {
87
- this.dom.removeAttribute('aria-modal');
88
- document.removeEventListener('keydown', this._onEscape);
89
- document.removeEventListener('keydown', this._onFocusTrap);
90
- this._returnFocusEl = null;
91
- if (!document.querySelector('[' + DOM_SELECTOR + '="open"]')) {
92
- document.body.classList.remove('ln-modal-open');
93
- }
94
- }
95
-
96
- const closeButtons = this.dom.querySelectorAll('[data-ln-modal-close]');
97
- for (const btn of closeButtons) {
98
- if (btn[DOM_ATTRIBUTE + 'Close']) {
99
- btn.removeEventListener('click', btn[DOM_ATTRIBUTE + 'Close']);
100
- delete btn[DOM_ATTRIBUTE + 'Close'];
101
- }
102
- }
103
-
104
- const triggers = document.querySelectorAll('[data-ln-modal-for="' + this.dom.id + '"]');
105
- for (const btn of triggers) {
106
- if (btn[DOM_ATTRIBUTE + 'Trigger']) {
107
- btn.removeEventListener('click', btn[DOM_ATTRIBUTE + 'Trigger']);
108
- delete btn[DOM_ATTRIBUTE + 'Trigger'];
109
- }
110
- }
111
-
112
- dispatch(this.dom, 'ln-modal:destroyed', { modalId: this.dom.id, target: this.dom });
113
- delete this.dom[DOM_ATTRIBUTE];
114
- };
115
-
116
- // ─── Attribute Sync ────────────────────────────────────────
117
-
118
- function _syncAttribute(el) {
119
- const instance = el[DOM_ATTRIBUTE];
120
- if (!instance) return;
121
-
122
- const value = el.getAttribute(DOM_SELECTOR);
123
- const shouldBeOpen = value === 'open';
124
-
125
- if (shouldBeOpen === instance.isOpen) return;
126
-
127
- if (shouldBeOpen) {
128
- const before = dispatchCancelable(el, 'ln-modal:before-open', { modalId: el.id, target: el });
129
- if (before.defaultPrevented) {
130
- el.setAttribute(DOM_SELECTOR, 'close');
131
- return;
132
- }
133
- instance.isOpen = true;
134
- el.setAttribute('aria-modal', 'true');
135
- el.setAttribute('role', 'dialog');
136
- document.body.classList.add('ln-modal-open');
137
- document.addEventListener('keydown', instance._onEscape);
138
- document.addEventListener('keydown', instance._onFocusTrap);
139
-
140
- const previouslyFocused = document.activeElement;
141
- instance._returnFocusEl = (previouslyFocused && previouslyFocused !== document.body) ? previouslyFocused : null;
142
-
143
- const autoFocusEl = el.querySelector('[autofocus]');
144
- if (autoFocusEl && isVisible(autoFocusEl)) {
145
- autoFocusEl.focus();
146
- } else {
147
- const inputs = el.querySelectorAll('input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled])');
148
- const firstInput = Array.prototype.find.call(inputs, isVisible);
149
- if (firstInput) firstInput.focus();
150
- else {
151
- const buttons = el.querySelectorAll('a[href], button:not([disabled])');
152
- const firstFocusable = Array.prototype.find.call(buttons, isVisible);
153
- if (firstFocusable) firstFocusable.focus();
154
- }
155
- }
156
-
157
- dispatch(el, 'ln-modal:open', { modalId: el.id, target: el });
158
- } else {
159
- const before = dispatchCancelable(el, 'ln-modal:before-close', { modalId: el.id, target: el });
160
- if (before.defaultPrevented) {
161
- el.setAttribute(DOM_SELECTOR, 'open');
162
- return;
163
- }
164
- instance.isOpen = false;
165
- el.removeAttribute('aria-modal');
166
- document.removeEventListener('keydown', instance._onEscape);
167
- document.removeEventListener('keydown', instance._onFocusTrap);
168
- dispatch(el, 'ln-modal:close', { modalId: el.id, target: el });
169
-
170
- if (instance._returnFocusEl
171
- && document.contains(instance._returnFocusEl)
172
- && typeof instance._returnFocusEl.focus === 'function') {
173
- instance._returnFocusEl.focus();
174
- }
175
- instance._returnFocusEl = null;
176
-
177
- if (!document.querySelector('[' + DOM_SELECTOR + '="open"]')) {
178
- document.body.classList.remove('ln-modal-open');
179
- }
180
- }
181
- }
182
-
183
- // ─── Helpers ───────────────────────────────────────────────
184
-
185
- function _attachCloseButtons(instance) {
186
- const closeButtons = instance.dom.querySelectorAll('[data-ln-modal-close]');
187
- for (const btn of closeButtons) {
188
- if (btn[DOM_ATTRIBUTE + 'Close']) continue;
189
- btn.addEventListener('click', instance._onClose);
190
- btn[DOM_ATTRIBUTE + 'Close'] = instance._onClose;
191
- }
192
- }
193
-
194
- // ─── Init ──────────────────────────────────────────────────
195
-
196
- registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-modal', {
197
- extraAttributes: ['data-ln-modal-for'],
198
- onAttributeChange: _syncAttribute,
199
- onInit: _attachTriggers
200
- });
201
- })();
1
+ import { registerComponent, dispatch, dispatchCancelable, isVisible } from '../../ln-core';
2
+
3
+ (function () {
4
+ const DOM_SELECTOR = 'data-ln-modal';
5
+ const DOM_ATTRIBUTE = 'lnModal';
6
+
7
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
8
+
9
+ // ─── Component Constructor ─────────────────────────────
10
+
11
+ function _component(dom) {
12
+ this.dom = dom;
13
+ this.isOpen = dom.getAttribute(DOM_SELECTOR) === 'open';
14
+
15
+ const self = this;
16
+
17
+ // Command listeners for request events
18
+ this._onRequestOpen = function () {
19
+ self.dom.setAttribute(DOM_SELECTOR, 'open');
20
+ };
21
+
22
+ this._onRequestClose = function () {
23
+ self.dom.setAttribute(DOM_SELECTOR, 'close');
24
+ };
25
+
26
+ // Native dialog ESC cancel interception
27
+ this._onCancel = function (e) {
28
+ e.preventDefault();
29
+ self.dom.setAttribute(DOM_SELECTOR, 'close');
30
+ };
31
+
32
+ // Dismiss trigger buttons inside modal [data-ln-modal-close]
33
+ this._onClickClose = function (e) {
34
+ const closeBtn = e.target.closest('[data-ln-modal-close]');
35
+ if (closeBtn && self.dom.contains(closeBtn)) {
36
+ e.preventDefault();
37
+ self.dom.setAttribute(DOM_SELECTOR, 'close');
38
+ }
39
+ };
40
+
41
+ this.dom.addEventListener('ln-modal:request-open', this._onRequestOpen);
42
+ this.dom.addEventListener('ln-modal:request-close', this._onRequestClose);
43
+ this.dom.addEventListener('cancel', this._onCancel);
44
+ this.dom.addEventListener('click', this._onClickClose);
45
+
46
+ // Apply initial state if rendered with open attribute
47
+ if (this.isOpen) {
48
+ if (typeof this.dom.showModal === 'function') this.dom.showModal();
49
+ document.body.classList.add('ln-modal-open');
50
+ }
51
+
52
+ return this;
53
+ }
54
+
55
+ _component.prototype.destroy = function () {
56
+ if (!this.dom[DOM_ATTRIBUTE]) return;
57
+
58
+ this.dom.removeEventListener('ln-modal:request-open', this._onRequestOpen);
59
+ this.dom.removeEventListener('ln-modal:request-close', this._onRequestClose);
60
+ this.dom.removeEventListener('cancel', this._onCancel);
61
+ this.dom.removeEventListener('click', this._onClickClose);
62
+
63
+ if (this.isOpen) {
64
+ const dom = this.dom;
65
+ const otherOpen = Array.prototype.some.call(
66
+ document.querySelectorAll('[' + DOM_SELECTOR + '="open"]'),
67
+ function (el) { return el !== dom; }
68
+ );
69
+ if (!otherOpen) {
70
+ document.body.classList.remove('ln-modal-open');
71
+ }
72
+ }
73
+
74
+ dispatch(this.dom, 'ln-modal:destroyed', { modalId: this.dom.id, target: this.dom });
75
+ delete this.dom[DOM_ATTRIBUTE];
76
+ };
77
+
78
+ // ─── Attribute Sync (Single Source of Truth) ───────────
79
+
80
+ function _syncAttribute(el) {
81
+ const instance = el[DOM_ATTRIBUTE];
82
+ if (!instance) return;
83
+
84
+ const value = el.getAttribute(DOM_SELECTOR);
85
+ const shouldBeOpen = value === 'open';
86
+
87
+ if (shouldBeOpen === instance.isOpen) return;
88
+
89
+ if (shouldBeOpen) {
90
+ const before = dispatchCancelable(el, 'ln-modal:before-open', { modalId: el.id, target: el });
91
+ if (before.defaultPrevented) {
92
+ el.setAttribute(DOM_SELECTOR, 'close');
93
+ return;
94
+ }
95
+ instance.isOpen = true;
96
+ document.body.classList.add('ln-modal-open');
97
+ if (typeof el.showModal === 'function') el.showModal();
98
+
99
+ // Focus placement
100
+ const autoFocusEl = el.querySelector('[autofocus]');
101
+ if (autoFocusEl && isVisible(autoFocusEl)) {
102
+ autoFocusEl.focus();
103
+ } else {
104
+ const inputs = el.querySelectorAll('input:not([disabled]):not([type="hidden"]), textarea:not([disabled]), select:not([disabled])');
105
+ const firstInput = Array.prototype.find.call(inputs, isVisible);
106
+ if (firstInput) firstInput.focus();
107
+ else {
108
+ const buttons = el.querySelectorAll('a[href], button:not([disabled])');
109
+ const firstFocusable = Array.prototype.find.call(buttons, isVisible);
110
+ if (firstFocusable) firstFocusable.focus();
111
+ }
112
+ }
113
+
114
+ dispatch(el, 'ln-modal:open', { modalId: el.id, target: el });
115
+ } else {
116
+ const before = dispatchCancelable(el, 'ln-modal:before-close', { modalId: el.id, target: el });
117
+ if (before.defaultPrevented) {
118
+ el.setAttribute(DOM_SELECTOR, 'open');
119
+ return;
120
+ }
121
+ instance.isOpen = false;
122
+ dispatch(el, 'ln-modal:close', { modalId: el.id, target: el });
123
+
124
+ if (typeof el.close === 'function') el.close();
125
+
126
+ if (!document.querySelector('[' + DOM_SELECTOR + '="open"]')) {
127
+ document.body.classList.remove('ln-modal-open');
128
+ }
129
+ }
130
+ }
131
+
132
+ // ─── Init ──────────────────────────────────────────────
133
+
134
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-modal', {
135
+ onAttributeChange: _syncAttribute
136
+ });
137
+ })();
@@ -0,0 +1,99 @@
1
+ # ln-modal-coordinator
2
+
3
+ A zero-instance, document-level singleton that wires `[data-ln-modal-for]` triggers, hash navigation, native-submit resume, and form-completion auto-close across every `ln-modal` on the page. `ln-modal` itself only knows `open`/`close`; this coordinator is the Layer-2 policy that decides *when* and *with what data*.
4
+
5
+ ---
6
+
7
+ ## 🧭 Philosophy & Architecture
8
+
9
+ 1. **Layer separation.** `ln-modal` is a Layer 1 primitive: it manages the `data-ln-modal="open|close"` attribute and native `<dialog>` mechanics only — no trigger resolution, no record filling, no submit handling. `ln-modal-coordinator` is the Layer 2 coordinator that adds all of that on top, without `ln-modal` ever being aware it exists.
10
+ 2. **No per-element instance.** There is nothing to construct per modal. The whole file is one IIFE guarded by `window.lnModalCoordinator`, attaching a handful of document-level listeners once per page load.
11
+ 3. **Command events, not direct calls.** The coordinator never flips `data-ln-modal` itself — it dispatches `ln-modal:request-open` / `ln-modal:request-close` and lets `ln-modal`'s own attribute-sync logic own the transition (and any cancellation via `ln-modal:before-open`/`before-close`).
12
+
13
+ ---
14
+
15
+ ## 📦 Minimal Blueprint
16
+
17
+ No markup is required to opt in — the coordinator listens on `document` and activates for any `[data-ln-modal-for]` trigger or `[data-ln-modal]` panel already on the page.
18
+
19
+ ```html
20
+ <!-- Trigger carries the record as a dataset — becomes the fill payload -->
21
+ <button data-ln-modal-for="user-modal" data-ln-modal-id="42" data-ln-modal-name="Jane Doe">
22
+ Edit Jane Doe
23
+ </button>
24
+
25
+ <dialog class="ln-modal" data-ln-modal id="user-modal">
26
+ <form data-ln-field="name">
27
+ <!-- ... -->
28
+ </form>
29
+ </dialog>
30
+ ```
31
+
32
+ Clicking the trigger sets `data-ln-modal-mode="edit"` (a record was harvested), fills the modal via `lnCore.fill`, and dispatches `ln-modal:request-open`.
33
+
34
+ ---
35
+
36
+ ## 🛠️ Declarative API Contract
37
+
38
+ ### HTML Attributes
39
+
40
+ | Attribute | Elements | Description |
41
+ | :--- | :--- | :--- |
42
+ | `data-ln-modal-for="modalId"` | Trigger (`<button>`, `<a>`) | Opens/closes the `[data-ln-modal][id="modalId"]` panel on click. |
43
+ | `data-ln-modal-mode="edit\|new"` | Trigger (optional) | Forces the mode instead of letting it be inferred from whether a record was harvested. |
44
+ | `data-ln-modal-{field}` | Trigger | Record fields harvested off the trigger's dataset (excluding `data-ln-modal-for`/`-close`/`-mode`) and passed to `lnCore.fill`. |
45
+ | `<a href="#modalId">` / `<a href="#modalId:param">` | Anchor | Opens the modal by hash; `param` (if present) is passed as the fill id via `ln-fill:request`. |
46
+
47
+ Cross-link: see [`js/ln-modal/README.md`](../ln-modal/README.md) for the primitive's own `data-ln-modal`/`data-ln-modal-close` contract.
48
+
49
+ ---
50
+
51
+ ## ⚡ DOM Events
52
+
53
+ ### Consumed
54
+
55
+ | Event | Source | Behavior |
56
+ | :--- | :--- | :--- |
57
+ | `click` | `document` | Resolves `[data-ln-modal-for]` triggers and `<a href="#modalId">` hash anchors. |
58
+ | `submit` | `document` | Inside a modal, stores a `sessionStorage` pending-flag and clears the hash so a native (non-AJAX) submit + reload can resume. |
59
+ | `hashchange` | `window` | Re-syncs every `[data-ln-modal][id]`'s open/close state against the URL hash. |
60
+ | `ln-modal:before-open` | `document` | Mode `new` → resets the modal's forms before it opens. |
61
+ | `ln-modal:open` | `document` | Reads the hash param for the modal's id and either dispatches `ln-fill:request` (`edit`) or resets the form (`new`, empty param). |
62
+ | `ln-modal:close` | `document` | Clears the pending flag and hash; mode `new` → resets the modal's forms. |
63
+ | `ln-form:success` / `ln-ajax:success` | `document` | Closes the modal and resets its forms. |
64
+
65
+ ### Dispatched
66
+
67
+ | Event | Target | Payload (`detail`) | Description |
68
+ | :--- | :--- | :--- | :--- |
69
+ | `ln-modal:request-open` | The modal | `{}` | Sent after trigger/hash resolution to open the modal. |
70
+ | `ln-modal:request-close` | The modal | `{}` | Sent to close the modal (trigger toggle, form success, pending-resume with no errors). |
71
+ | `ln-fill:request` | The modal | `{ id }` | Sent when a hash param (or a live hash change while open) identifies the record to fill. |
72
+
73
+ ---
74
+
75
+ ## 🔧 Internals
76
+
77
+ Source: `js/ln-modal-coordinator/src/ln-modal-coordinator.js`. One IIFE, no `_component` constructor, no `MutationObserver` — every mechanism below is a single document/window-level listener bound once at load, guarded by `window.lnModalCoordinator`.
78
+
79
+ ### Trigger delegation
80
+
81
+ The `click` listener resolves `e.target.closest('[data-ln-modal-for]')`, looks up the target modal by id, and bails if it isn't an initialized `ln-modal` (`target.lnModal` missing). It harvests a record from the trigger's `dataset`, walking every `lnModal*` key, skipping the reserved `lnModalFor`/`lnModalClose`/`lnModalMode`, and lower-casing the first letter of the remaining suffix (`data-ln-modal-name` → `record.name`). Mode resolution: an explicit `data-ln-modal-mode` on the trigger wins; otherwise a non-empty harvested record implies `edit`, an empty one implies `new`. `edit` fills via `window.lnCore.fill(target, record)`; `new` resets the modal's forms via `_resetModalForm`. Finally it toggles based on the modal's current `data-ln-modal` value: `open` → dispatch `request-close`, anything else → dispatch `request-open`.
82
+
83
+ ### Hash navigation
84
+
85
+ A click on `a[href^="#"]` is parsed with `hashParse` into a `{ namespace: param }` map; if any namespace matches an initialized modal's `id`, `hashLinkClick(e)` gates the default navigation and `hashSet(ns, param)` writes the hash — the actual open/close is driven by the `hashchange` listener below, not the click handler itself.
86
+
87
+ `_syncHashModals` (bound to `hashchange`, and run once on load after `DOMContentLoaded`/`setTimeout(0)`) walks every `[data-ln-modal][id]`. It first resolves any pending native-submit resume (see below), then compares `hashGet(hashNs)` against the modal's current open state: hash present + closed → `request-open` (mode `edit` if the param is non-empty, else `new`); hash present + already open with a changed param → re-dispatch `ln-fill:request` (or reset if the param went empty); hash absent + open → `request-close`. A re-entrancy guard (`_inSync`) prevents the listener from recursing on the `hashSet` calls it triggers indirectly through `ln-modal`'s own attribute sync.
88
+
89
+ ### Native-submit resume
90
+
91
+ On `submit` inside a `[data-ln-modal]` with an `id`, the coordinator writes `sessionStorage['ln-modal-pending:{id}'] = 'true'` and clears the hash (`hashSet(modal.id, null)`) *before* the native submit/reload happens, so the browser reloads on a clean URL. On the next load, `_syncHashModals` checks the pending flag first: if cleared and no error markers are found (`.has-error`, `[data-ln-validate-error]`, `.form-error`, `.alert-danger` — checked both document-wide and inside the modal) it removes the flag, clears the hash, dispatches `request-close`, and resets the form; if error markers are present, it reopens in `edit` mode instead so the user sees their errors in-context.
92
+
93
+ ### Form-completion auto-close
94
+
95
+ `ln-form:success` and `ln-ajax:success` share one handler: resolve the closest `[data-ln-modal]` ancestor of the event target, clear the pending flag and hash, dispatch `request-close`, and reset the form. `ln-modal:close` performs the same pending/hash cleanup and additionally resets forms when the mode is `new` (an `edit` close leaves the just-submitted data visible rather than blanking it).
96
+
97
+ ### `_resetModalForm`
98
+
99
+ Shared by every reset path above: clears `textContent` on every `[data-ln-field]` descendant, then for every `<form>` in the modal calls `window.lnCore.lnFill(form, null)` if available, falling back to the native `form.reset()` otherwise.
@@ -0,0 +1,8 @@
1
+ @use '../../scss/config/mixins' as *;
2
+
3
+ [data-ln-debug] {
4
+ // 1. Non-dialog coordinator check: data-ln-modal-coordinator must be on a <dialog> element
5
+ [data-ln-modal-coordinator]:not(dialog) {
6
+ @include dev-inline-error("data-ln-modal-coordinator must be on a <dialog> element");
7
+ }
8
+ }
@@ -0,0 +1 @@
1
+ (function(){"use strict";if(typeof window<"u"){const e=console.warn;console.warn=function(...n){typeof n[0]=="string"&&(n[0].startsWith("[ln-")||n[0].startsWith("[lnCore"))&&!(document.documentElement.hasAttribute("data-ln-debug")||document.body&&document.body.hasAttribute("data-ln-debug"))||e.apply(console,n)}}const q={};function I(e,n){q[e]||(q[e]=document.querySelector('[data-ln-template="'+e+'"]'));const r=q[e];return r?r.content.cloneNode(!0):(console.warn("["+(n||"ln-core")+'] Template "'+e+'" not found'),null)}function A(e,n,r){e.dispatchEvent(new CustomEvent(n,{bubbles:!0,detail:r||{}}))}function O(e,n){if(!e||!n)return e;const r=e.querySelectorAll("[data-ln-field]");for(let i=0;i<r.length;i++){const s=r[i],d=s.getAttribute("data-ln-field");n[d]!=null&&(s.textContent=n[d])}const p=e.querySelectorAll("[data-ln-attr]");for(let i=0;i<p.length;i++){const s=p[i],d=s.getAttribute("data-ln-attr").split(",");for(let f=0;f<d.length;f++){const h=d[f].trim().split(":");if(h.length!==2)continue;const l=h[0].trim(),t=h[1].trim();n[t]!=null&&s.setAttribute(l,n[t])}}const m=e.querySelectorAll("[data-ln-show]");for(let i=0;i<m.length;i++){const s=m[i],d=s.getAttribute("data-ln-show");d in n&&s.classList.toggle("hidden",!n[d])}const u=e.querySelectorAll("[data-ln-class]");for(let i=0;i<u.length;i++){const s=u[i],d=s.getAttribute("data-ln-class").split(",");for(let f=0;f<d.length;f++){const h=d[f].trim().split(":");if(h.length!==2)continue;const l=h[0].trim(),t=h[1].trim();t in n&&s.classList.toggle(l,!!n[t])}}return e}function W(e,n){e.matches&&e.matches("[data-ln-form], [data-ln-fillable]")&&e.dispatchEvent(new CustomEvent("ln-fill",{detail:n??null,bubbles:!0}));const r=e.querySelectorAll("[data-ln-form], [data-ln-fillable]");for(let p=0;p<r.length;p++)r[p].dispatchEvent(new CustomEvent("ln-fill",{detail:n??null,bubbles:!0}));return e}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore._fillBound||(window.lnCore._fillBound=!0,document.addEventListener("ln-fill",function(e){if(!(!e.target.matches||!e.target.matches("[data-ln-fillable]")))if(e.detail)O(e.target,e.detail);else{const n=e.target.querySelectorAll("[data-ln-field]");for(let r=0;r<n.length;r++)n[r].textContent=""}})));function _(e,n){if(!e||!n)return e;const r=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;r.nextNode();){const u=r.currentNode;u.textContent.indexOf("{{")!==-1&&(u.textContent=u.textContent.replace(/\{\{\s*(\w+)\s*\}\}/g,function(i,s){return n[s]!==void 0?n[s]:""}))}const p=function(u,i){return n[i]!==void 0?n[i]:""},m=Array.from(e.querySelectorAll("*"));e.nodeType===1&&m.push(e);for(let u=0;u<m.length;u++){const i=m[u],s=i.attributes;for(let d=0;d<s.length;d++){const f=s[d];f.value.indexOf("{{")!==-1&&i.setAttribute(f.name,f.value.replace(/\{\{\s*(\w+)\s*\}\}/g,p))}}return e}function B(e,n,r,p,m,u){const i={};for(let d=0;d<e.children.length;d++){const f=e.children[d],h=f.getAttribute("data-ln-key");h&&(i[h]=f)}const s=document.createDocumentFragment();for(let d=0;d<n.length;d++){const f=n[d],h=String(p(f));let l=i[h];if(l)m(l,f,d);else{const t=I(r,u);if(!t||(_(t,f),l=t.firstElementChild,!l))continue;l.setAttribute("data-ln-key",h),m(l,f,d)}s.appendChild(l)}e.textContent="",e.appendChild(s)}function k(e,n){if(!document.body){document.addEventListener("DOMContentLoaded",function(){k(e,n)}),console.warn("["+n+'] Script loaded before <body> — add "defer" to your <script> tag');return}e()}function E(e,n,r,p){if(e.nodeType!==1)return;const u=n.indexOf("[")!==-1||n.indexOf(".")!==-1||n.indexOf("#")!==-1?n:"["+n+"]",i=Array.from(e.querySelectorAll(u));e.matches&&e.matches(u)&&i.push(e);for(const s of i)s[r]||(s[r]=new p(s))}function K(e,n,r,p,m={}){const u=m.extraAttributes||[],i=m.onAttributeChange||null,s=m.onInit||null;function d(f){const h=f||document.body;E(h,e,n,r),s&&s(h)}return k(function(){const f=new MutationObserver(function(l){for(let t=0;t<l.length;t++){const o=l[t];if(o.type==="childList"){for(let c=0;c<o.addedNodes.length;c++){const a=o.addedNodes[c];a.nodeType===1&&(E(a,e,n,r),s&&s(a))}for(let c=0;c<o.removedNodes.length;c++){const a=o.removedNodes[c];if(a.nodeType===1){const w=e.indexOf("[")!==-1||e.indexOf(".")!==-1||e.indexOf("#")!==-1?e:"["+e+"]",M=Array.from(a.querySelectorAll(w));a.matches&&a.matches(w)&&M.push(a);for(let y=0;y<M.length;y++){const S=M[y];if(!document.contains(S)){const b=S[n];b&&typeof b.destroy=="function"&&b.destroy()}}}}}else o.type==="attributes"&&(i&&o.target[n]?i(o.target,o.attributeName):(E(o.target,e,n,r),s&&s(o.target)))}});let h=[];if(e.indexOf("[")!==-1){const l=/\[([\w-]+)/g;let t;for(;(t=l.exec(e))!==null;)h.push(t[1])}else h.push(e);f.observe(document.body,{childList:!0,subtree:!0,attributes:!0,attributeFilter:h.concat(u)})},p),window[n]=d,document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){d(document.body)}):d(document.body),d}const T={};function R(e,n){T[e]=n}function P(e){return T[e]||{ingress:n=>n,egress:n=>n}}const D={};function F(e,n){if(!e||typeof n!="object")return;const r=e.toLowerCase().split("-")[0];D[r]=n}function U(e){if(!e)return null;const n=e.toLowerCase().split("-")[0];return D[n]||null}F("mk",{monthsLong:["јануари","февруари","март","април","мај","јуни","јули","август","септември","октомври","ноември","декември"],monthsShort:["јан","фев","мар","апр","мај","јун","јул","авг","септ","окт","ноем","дек"],daysLong:["недела","понеделник","вторник","среда","четврток","петок","сабота"],daysShort:["нед","пон","вт","ср","чет","пет","саб"]}),typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.registerDataMapper=R,window.lnCore.getDataMapper=P,window.lnCore.registerLocaleFallback=F,window.lnCore.getLocaleFallback=U,window.lnCore.fillTemplate=_,window.lnCore.fill=O,window.lnCore.lnFill=W,window.lnCore.renderList=B);function j(e){return(e||"").replace(/^#/,"")}function L(e){const n=e===void 0?location.hash:e,r={},p=j(n);if(!p)return r;const m=p.split("&");for(let u=0;u<m.length;u++){const i=m[u];if(!i)continue;const s=i.indexOf(":"),d=s>-1?i.slice(0,s):i,f=s>-1?i.slice(s+1):"";if(d)try{r[d]=decodeURIComponent(f)}catch{r[d]=f}}return r}function v(e){if(!e)return null;const n=L();return e in n?n[e]:null}function g(e,n){if(!e)return;const r=L();n==null?delete r[e]:r[e]=String(n);const m=Object.keys(r).map(function(u){const i=r[u];return i===""?u:u+":"+encodeURIComponent(i)}).join("&");j(location.hash)!==m&&(location.hash=m)}function N(e){return e.button===1||e.ctrlKey||e.metaKey||e.shiftKey?!1:(e.preventDefault(),!0)}typeof window<"u"&&(window.lnCore=window.lnCore||{},window.lnCore.hashParse=L,window.lnCore.hashGet=v,window.lnCore.hashSet=g,window.lnCore.hashLinkClick=N),(function(){const e="data-ln-modal-coordinator",n="lnModalCoordinator";if(window[n]!==void 0)return;function r(l,t){if(t){if(l){const c=l.closest("["+e+"]");if(c){if(c.id===t&&c.hasAttribute("data-ln-modal"))return c;const a=c.querySelector("#"+CSS.escape(t)+'[data-ln-modal], [data-ln-modal="'+t+'"]');if(a)return a}}const o=document.getElementById(t)||document.querySelector('[data-ln-modal="'+t+'"]');if(o)return o}if(l){const o=l.closest("["+e+"]");if(o){if(o.hasAttribute("data-ln-modal"))return o;const a=o.querySelector("[data-ln-modal]");if(a)return a}const c=l.closest("[data-ln-modal]");if(c)return c}return document.querySelector("[data-ln-modal]")}function p(l,t){if(l!=="edit")return"";if(t){const o=t.getAttribute("data-ln-fill-id");if(o)return o}return"edit"}function m(l){if(!l)return;const t=l.querySelectorAll("[data-ln-field]");for(let c=0;c<t.length;c++)t[c].textContent="";const o=l.querySelectorAll("form");for(let c=0;c<o.length;c++)window.lnCore&&typeof window.lnCore.lnFill=="function"?window.lnCore.lnFill(o[c],null):o[c].reset()}document.addEventListener("submit",function(l){if(l.defaultPrevented)return;const o=l.target.closest("[data-ln-modal]");if(o&&o.id){try{sessionStorage.setItem("ln-modal-pending:"+o.id,"true")}catch{}g(o.id,null)}}),document.addEventListener("click",function(l){if(l.ctrlKey||l.metaKey||l.button===1)return;const t=l.target.closest("[data-ln-modal-for]");if(t){const c=t.getAttribute("data-ln-modal-for"),a=r(t,c);if(a&&a.lnModal){l.preventDefault();const C={lnModalFor:!0,lnModalClose:!0,lnModalMode:!0},w={},M=t.dataset;for(const b in M){if(!b.startsWith("lnModal")||C[b])continue;const x=b.slice(7);x&&(w[x.charAt(0).toLowerCase()+x.slice(1)]=M[b])}const y=Object.keys(w).length>0;t.hasAttribute("data-ln-modal-mode")?a.dataset.lnModalMode=t.getAttribute("data-ln-modal-mode"):a.dataset.lnModalMode=y?"edit":"new",y&&window.lnCore&&typeof window.lnCore.fill=="function"?window.lnCore.fill(a,w):a.dataset.lnModalMode==="new"&&m(a),a.getAttribute("data-ln-modal")==="open"?A(a,"ln-modal:request-close",{}):(a.id&&g(a.id,p(a.dataset.lnModalMode,t)),A(a,"ln-modal:request-open",{}))}return}const o=l.target.closest('a[href^="#"]');if(o){const c=L(o.getAttribute("href"));for(const a in c){const C=document.getElementById(a);if(C&&C.lnModal){if(!N(l))return;g(a,c[a]);return}}}}),document.addEventListener("ln-modal:before-open",function(l){const t=l.target;if(!t||!t.lnModal)return;(t.dataset.lnModalMode||"new")==="new"&&m(t)}),document.addEventListener("ln-modal:open",function(l){const t=l.target;if(!t||!t.lnModal||!t.id)return;let o=v(t.id);o===null&&(o=p(t.dataset.lnModalMode,null),g(t.id,o)),o?(t.dataset.lnModalMode="edit",t.dispatchEvent(new CustomEvent("ln-fill:request",{bubbles:!0,detail:{id:o}}))):(t.dataset.lnModalMode="new",m(t))});let u=!1;function i(){if(!u){u=!0;try{const l=document.querySelectorAll("[data-ln-modal][id]");for(let t=0;t<l.length;t++){const o=l[t];if(!o.lnModal)continue;const c=o.id,a="ln-modal-pending:"+c;let C=!1;try{C=sessionStorage.getItem(a)==="true"}catch{}if(C){try{sessionStorage.removeItem(a)}catch{}if(!!(document.querySelector(".has-error, [data-ln-validate-error], .form-error, .alert-danger")||o.querySelector(".has-error, [data-ln-validate-error], .form-error, .alert-danger"))){o.dataset.lnModalMode="edit",A(o,"ln-modal:request-open",{});continue}else{g(c,null),A(o,"ln-modal:request-close",{}),m(o);continue}}const w=v(c),M=w!==null,y=o.lnModal.isOpen;if(M){const S=w?"edit":"new";o.dataset.lnModalMode=S,y?w?o.dispatchEvent(new CustomEvent("ln-fill:request",{bubbles:!0,detail:{id:w}})):m(o):A(o,"ln-modal:request-open",{})}else y&&A(o,"ln-modal:request-close",{})}}finally{u=!1}}}function s(){const l=document.querySelectorAll('[data-ln-modal="open"][id]');for(let t=0;t<l.length;t++){const o=l[t];o.lnModal&&v(o.id)===null&&g(o.id,p(o.dataset.lnModalMode,null))}}window.addEventListener("hashchange",i);function d(){s(),i()}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",function(){setTimeout(d,0)}):setTimeout(d,0);function f(l){const t=l.target.closest("[data-ln-modal]");if(!(!t||!t.lnModal)){if(t.id){try{sessionStorage.removeItem("ln-modal-pending:"+t.id)}catch{}g(t.id,null)}A(t,"ln-modal:request-close",{}),m(t)}}document.addEventListener("ln-form:success",f),document.addEventListener("ln-ajax:success",f),document.addEventListener("ln-modal:close",function(l){const t=l.target;if(!(!t||!t.lnModal)){if(t.id){try{sessionStorage.removeItem("ln-modal-pending:"+t.id)}catch{}v(t.id)!==null&&g(t.id,null)}t.dataset.lnModalMode==="new"&&m(t)}});function h(l){return this.dom=l,this}h.prototype.destroy=function(){this.dom[n]&&delete this.dom[n]},K(e,n,h,"ln-modal-coordinator")})()})();