@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,265 +1,368 @@
1
- import { registerComponent, dispatch, buildUrl, getHeaders, parseHeaders } from '../../ln-core';
2
-
3
- (function () {
4
- const DOM_SELECTOR = 'data-ln-api-connector';
5
- const DOM_ATTRIBUTE = 'lnApiConnector';
6
- const DOM_ALIAS = 'lnConnector';
7
-
8
- if (window[DOM_ATTRIBUTE] !== undefined) return;
9
-
10
- // ─── Component Constructor ─────────────────────────────
11
-
12
- function _component(dom) {
13
- this.dom = dom;
14
- dom[DOM_ATTRIBUTE] = this;
15
- dom[DOM_ALIAS] = this; // Set alias for compatibility
16
-
17
- this.refreshConfig();
18
- this._handlers = null;
19
- _bindEvents(this);
20
-
21
- return this;
22
- }
23
-
24
- // ─── Refresh Config from DOM Attributes ─────────────────
25
-
26
- _component.prototype.refreshConfig = function () {
27
- const dom = this.dom;
28
-
29
- this.baseUrl = dom.getAttribute('data-ln-api-base-url') || '';
30
- this.path = dom.getAttribute('data-ln-api-path') || '';
31
- this.credentials = 'same-origin';
32
-
33
- const rawHeaders = dom.getAttribute('data-ln-api-headers') || '';
34
- this.headers = parseHeaders(rawHeaders, 'ln-api-connector');
35
-
36
- if (rawHeaders.toLowerCase().includes('authorization') || rawHeaders.toLowerCase().includes('bearer') || rawHeaders.toLowerCase().includes('basic')) {
37
- console.warn('[ln-api-connector] Security Warning: Sensitive authorization credentials detected in data-ln-api-headers attribute. Storing secrets in HTML DOM attributes is highly discouraged and vulnerable to XSS credential extraction. Please use HttpOnly session cookies or a Backend Proxy Gateway instead.');
38
- }
39
-
40
- dispatch(dom, 'ln-api-connector:config-changed', {
41
- baseUrl: this.baseUrl,
42
- path: this.path,
43
- headers: this.headers
44
- });
45
- };
46
-
47
- // ─── JS API Methods (Promises) ──────────────────────────
48
-
49
- _component.prototype.fetchDelta = function (since) {
50
- const self = this;
51
- let url = buildUrl(self.baseUrl, self.path);
52
-
53
- if (since !== undefined && since !== null && since !== '') {
54
- url += (url.indexOf('?') !== -1 ? '&' : '?') + 'since=' + encodeURIComponent(since);
55
- }
56
-
57
- return window.fetch(url, { method: 'GET', headers: getHeaders(self.headers), credentials: self.credentials })
58
- .then(res => {
59
- if (!res.ok) throw new Error('HTTP ' + res.status + ': ' + res.statusText);
60
- return res.json();
61
- });
62
- };
63
-
64
- _component.prototype.create = function (payload) {
65
- const self = this;
66
- return window.fetch(buildUrl(self.baseUrl, self.path), {
67
- method: 'POST',
68
- headers: getHeaders(self.headers),
69
- credentials: self.credentials,
70
- body: JSON.stringify(payload)
71
- })
72
- .then(res => {
73
- if (!res.ok) throw new Error('HTTP ' + res.status + ': ' + res.statusText);
74
- return res.json();
75
- });
76
- };
77
-
78
- _component.prototype.update = function (id, payload) {
79
- const self = this;
80
- return window.fetch(buildUrl(self.baseUrl, self.path, id), {
81
- method: 'PUT',
82
- headers: getHeaders(self.headers),
83
- credentials: self.credentials,
84
- body: JSON.stringify(payload)
85
- })
86
- .then(res => {
87
- if (res.ok) return res.json();
88
- if (res.status === 409) return res.json().then(data => {
89
- const err = new Error('Conflict');
90
- err.status = 409;
91
- err.data = data;
92
- throw err;
93
- });
94
- throw new Error('HTTP ' + res.status + ': ' + res.statusText);
95
- });
96
- };
97
-
98
- _component.prototype.delete = function (id) {
99
- const self = this;
100
- return window.fetch(buildUrl(self.baseUrl, self.path, id), {
101
- method: 'DELETE',
102
- headers: getHeaders(self.headers),
103
- credentials: self.credentials
104
- })
105
- .then(res => {
106
- if (!res.ok) throw new Error('HTTP ' + res.status + ': ' + res.statusText);
107
- return res.json();
108
- });
109
- };
110
-
111
- _component.prototype.bulkDelete = function (ids) {
112
- const self = this;
113
- return window.fetch(buildUrl(self.baseUrl, self.path) + '/bulk-delete', {
114
- method: 'DELETE',
115
- headers: getHeaders(self.headers),
116
- credentials: self.credentials,
117
- body: JSON.stringify({ ids: ids })
118
- })
119
- .then(res => {
120
- if (!res.ok) throw new Error('HTTP ' + res.status + ': ' + res.statusText);
121
- return res.json();
122
- });
123
- };
124
-
125
- // ─── DOM Event Routing ────────────────────────────────────
126
-
127
- function _bindEvents(self) {
128
- self._handlers = {
129
- sync: function (e) {
130
- const detail = e.detail || {};
131
- self.fetchDelta(detail.since)
132
- .then(function (data) {
133
- dispatch(self.dom, 'ln-api-connector:fetched', { data: data, since: detail.since });
134
- })
135
- .catch(function (err) {
136
- dispatch(self.dom, 'ln-api-connector:error', {
137
- action: 'sync',
138
- error: err.message,
139
- status: err.status || 0,
140
- since: detail.since
141
- });
142
- });
143
- },
144
- create: function (e) {
145
- const detail = e.detail || {};
146
- self.create(detail.data)
147
- .then(function (record) {
148
- dispatch(self.dom, 'ln-api-connector:created', { record: record, tempId: detail.tempId });
149
- })
150
- .catch(function (err) {
151
- dispatch(self.dom, 'ln-api-connector:error', {
152
- action: 'create',
153
- error: err.message,
154
- status: err.status || 0,
155
- tempId: detail.tempId
156
- });
157
- });
158
- },
159
- update: function (e) {
160
- const detail = e.detail || {};
161
- // Merge expected_version into payload for conflict checking
162
- const payload = Object.assign({}, detail.data);
163
- if (detail.expected_version !== undefined) {
164
- payload.expected_version = detail.expected_version;
165
- }
166
- self.update(detail.id, payload)
167
- .then(function (record) {
168
- dispatch(self.dom, 'ln-api-connector:updated', { record: record, id: detail.id });
169
- })
170
- .catch(function (err) {
171
- dispatch(self.dom, 'ln-api-connector:error', {
172
- action: 'update',
173
- error: err.message,
174
- status: err.status || 0,
175
- id: detail.id,
176
- conflictData: err.status === 409 ? err.data : null
177
- });
178
- });
179
- },
180
- delete: function (e) {
181
- const detail = e.detail || {};
182
- self.delete(detail.id)
183
- .then(function (res) {
184
- dispatch(self.dom, 'ln-api-connector:deleted', { response: res, id: detail.id });
185
- })
186
- .catch(function (err) {
187
- dispatch(self.dom, 'ln-api-connector:error', {
188
- action: 'delete',
189
- error: err.message,
190
- status: err.status || 0,
191
- id: detail.id
192
- });
193
- });
194
- },
195
- bulkDelete: function (e) {
196
- const detail = e.detail || {};
197
- self.bulkDelete(detail.ids)
198
- .then(function (res) {
199
- dispatch(self.dom, 'ln-api-connector:bulk-deleted', { response: res, ids: detail.ids });
200
- })
201
- .catch(function (err) {
202
- dispatch(self.dom, 'ln-api-connector:error', {
203
- action: 'bulk-delete',
204
- error: err.message,
205
- status: err.status || 0,
206
- ids: detail.ids
207
- });
208
- });
209
- }
210
- };
211
-
212
- // Support both general and REST namespaces for incoming requests
213
- const namespaces = ['ln-api-connector', 'ln-rest-connector'];
214
- namespaces.forEach(function (ns) {
215
- self.dom.addEventListener(ns + ':request-sync', self._handlers.sync);
216
- self.dom.addEventListener(ns + ':request-fetch', self._handlers.sync);
217
- self.dom.addEventListener(ns + ':request-create', self._handlers.create);
218
- self.dom.addEventListener(ns + ':request-update', self._handlers.update);
219
- self.dom.addEventListener(ns + ':request-delete', self._handlers.delete);
220
- self.dom.addEventListener(ns + ':request-bulk-delete', self._handlers.bulkDelete);
221
- });
222
- }
223
-
224
- _component.prototype.destroy = function () {
225
- if (!this.dom[DOM_ATTRIBUTE]) return;
226
-
227
- const self = this;
228
- if (self._handlers) {
229
- const namespaces = ['ln-api-connector', 'ln-rest-connector'];
230
- namespaces.forEach(function (ns) {
231
- self.dom.removeEventListener(ns + ':request-sync', self._handlers.sync);
232
- self.dom.removeEventListener(ns + ':request-fetch', self._handlers.sync);
233
- self.dom.removeEventListener(ns + ':request-create', self._handlers.create);
234
- self.dom.removeEventListener(ns + ':request-update', self._handlers.update);
235
- self.dom.removeEventListener(ns + ':request-delete', self._handlers.delete);
236
- self.dom.removeEventListener(ns + ':request-bulk-delete', self._handlers.bulkDelete);
237
- });
238
- self._handlers = null;
239
- }
240
-
241
- dispatch(this.dom, 'ln-api-connector:destroyed', { target: this.dom });
242
-
243
- delete this.dom[DOM_ATTRIBUTE];
244
- delete this.dom[DOM_ALIAS];
245
- };
246
-
247
- // ─── Attribute Sync ────────────────────────────────────────
248
-
249
- function _syncAttribute(el) {
250
- const instance = el[DOM_ATTRIBUTE];
251
- if (!instance) return;
252
- instance.refreshConfig();
253
- }
254
-
255
- // ─── Registration ──────────────────────────────────────
256
-
257
- registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-api-connector', {
258
- extraAttributes: [
259
- 'data-ln-api-base-url',
260
- 'data-ln-api-path',
261
- 'data-ln-api-headers'
262
- ],
263
- onAttributeChange: _syncAttribute
264
- });
265
- })();
1
+ import { registerComponent, dispatch, buildUrl, getHeaders, parseHeaders } from '../../ln-core';
2
+
3
+ (function () {
4
+ const DOM_SELECTOR = 'data-ln-api-connector';
5
+ const DOM_ATTRIBUTE = 'lnApiConnector';
6
+ const DOM_ALIAS = 'lnConnector';
7
+
8
+ if (window[DOM_ATTRIBUTE] !== undefined) return;
9
+
10
+ // ─── Response Resolver ──────────────────────────────────
11
+
12
+ function _resolve(res) {
13
+ if (res.ok) return res.status === 204 ? null : res.json();
14
+ return res.json().catch(() => null).then(body => {
15
+ const err = new Error('HTTP ' + res.status + ': ' + res.statusText);
16
+ err.status = res.status;
17
+ err.data = body;
18
+ throw err;
19
+ });
20
+ }
21
+
22
+ // ─── Component Constructor ─────────────────────────────
23
+
24
+ function _component(dom) {
25
+ this.dom = dom;
26
+ dom[DOM_ATTRIBUTE] = this;
27
+ dom[DOM_ALIAS] = this; // Set alias for compatibility
28
+
29
+ this.refreshConfig();
30
+ this._handlers = null;
31
+ _bindEvents(this);
32
+
33
+ return this;
34
+ }
35
+
36
+ // ─── Refresh Config from DOM Attributes ─────────────────
37
+
38
+ _component.prototype.refreshConfig = function () {
39
+ const dom = this.dom;
40
+
41
+ this.baseUrl = dom.getAttribute('data-ln-api-base-url') || '';
42
+ this.path = dom.getAttribute('data-ln-api-path') || '';
43
+ this.credentials = 'same-origin';
44
+
45
+ this.paramKeys = {
46
+ offset: dom.getAttribute('data-ln-api-param-offset') || 'offset',
47
+ limit: dom.getAttribute('data-ln-api-param-limit') || 'limit',
48
+ search: dom.getAttribute('data-ln-api-param-search') || 'search',
49
+ sortField: dom.getAttribute('data-ln-api-param-sort-field') || 'sort_field',
50
+ sortDir: dom.getAttribute('data-ln-api-param-sort-dir') || 'sort_dir'
51
+ };
52
+
53
+ const rawHeaders = dom.getAttribute('data-ln-api-headers') || '';
54
+ this.headers = parseHeaders(rawHeaders, 'ln-api-connector');
55
+
56
+ if (rawHeaders.toLowerCase().includes('authorization') || rawHeaders.toLowerCase().includes('bearer') || rawHeaders.toLowerCase().includes('basic')) {
57
+ console.warn('[ln-api-connector] Security Warning: Sensitive authorization credentials detected in data-ln-api-headers attribute. Storing secrets in HTML DOM attributes is highly discouraged and vulnerable to XSS credential extraction. Please use HttpOnly session cookies or a Backend Proxy Gateway instead.');
58
+ }
59
+
60
+ dispatch(dom, 'ln-api-connector:config-changed', {
61
+ baseUrl: this.baseUrl,
62
+ path: this.path,
63
+ headers: this.headers,
64
+ paramKeys: this.paramKeys
65
+ });
66
+ };
67
+
68
+ // ─── Request Headers (forces X-LN-Response) ─────────────
69
+
70
+ _component.prototype._reqHeaders = function (idempotencyKey) {
71
+ const headers = Object.assign({}, getHeaders(this.headers), { 'X-LN-Response': 'data' });
72
+ if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
73
+ return headers;
74
+ };
75
+
76
+ // ─── JS API Methods (Promises) ──────────────────────────
77
+
78
+ _component.prototype.fetchDelta = function (since) {
79
+ const self = this;
80
+ let url = buildUrl(self.baseUrl, self.path);
81
+
82
+ if (since !== undefined && since !== null && since !== '') {
83
+ url += (url.indexOf('?') !== -1 ? '&' : '?') + 'since=' + encodeURIComponent(since);
84
+ }
85
+
86
+ return window.fetch(url, { method: 'GET', headers: self._reqHeaders(), credentials: self.credentials })
87
+ .then(_resolve);
88
+ };
89
+
90
+ _component.prototype.query = function (queryParams) {
91
+ const self = this;
92
+ queryParams = queryParams || {};
93
+ let url = buildUrl(self.baseUrl, self.path);
94
+
95
+ const keys = self.paramKeys || {
96
+ offset: 'offset',
97
+ limit: 'limit',
98
+ search: 'search',
99
+ sortField: 'sort_field',
100
+ sortDir: 'sort_dir'
101
+ };
102
+
103
+ const searchParams = new URLSearchParams();
104
+ if (queryParams.search) {
105
+ searchParams.append(keys.search, queryParams.search);
106
+ }
107
+ if (queryParams.offset != null) {
108
+ searchParams.append(keys.offset, queryParams.offset);
109
+ }
110
+ if (queryParams.limit != null) {
111
+ searchParams.append(keys.limit, queryParams.limit);
112
+ }
113
+ if (queryParams.sort && queryParams.sort.field && queryParams.sort.direction) {
114
+ searchParams.append(keys.sortField, queryParams.sort.field);
115
+ searchParams.append(keys.sortDir, queryParams.sort.direction);
116
+ }
117
+ if (queryParams.filters && typeof queryParams.filters === 'object') {
118
+ Object.keys(queryParams.filters).forEach(key => {
119
+ const vals = queryParams.filters[key];
120
+ if (Array.isArray(vals) && vals.length > 0) {
121
+ searchParams.append(key, vals.join(','));
122
+ }
123
+ });
124
+ }
125
+
126
+ const qs = searchParams.toString();
127
+ if (qs) {
128
+ url += (url.indexOf('?') !== -1 ? '&' : '?') + qs;
129
+ }
130
+
131
+ return window.fetch(url, { method: 'GET', headers: self._reqHeaders(), credentials: self.credentials })
132
+ .then(_resolve);
133
+ };
134
+
135
+ _component.prototype.create = function (payload, url, idempotencyKey) {
136
+ const self = this;
137
+ return window.fetch(buildUrl(self.baseUrl, url || self.path), {
138
+ method: 'POST',
139
+ headers: self._reqHeaders(idempotencyKey),
140
+ credentials: self.credentials,
141
+ body: JSON.stringify(payload)
142
+ })
143
+ .then(_resolve);
144
+ };
145
+
146
+ _component.prototype.update = function (id, payload, expectedVersion, url, idempotencyKey) {
147
+ const self = this;
148
+ if (expectedVersion !== undefined && expectedVersion !== null) {
149
+ payload = Object.assign({}, payload, { expected_version: expectedVersion });
150
+ }
151
+ // An explicit `url` is the COMPLETE resource URL — it already carries the
152
+ // id (e.g. the form's resolved action `/documents/42`). Do NOT re-append
153
+ // id, symmetric with create(). Only the path fallback needs id appended.
154
+ const target = url ? buildUrl(self.baseUrl, url) : buildUrl(self.baseUrl, self.path, id);
155
+ return window.fetch(target, {
156
+ method: 'PUT',
157
+ headers: self._reqHeaders(idempotencyKey),
158
+ credentials: self.credentials,
159
+ body: JSON.stringify(payload)
160
+ })
161
+ .then(_resolve);
162
+ };
163
+
164
+ _component.prototype.delete = function (id, url, idempotencyKey) {
165
+ const self = this;
166
+ return window.fetch(buildUrl(self.baseUrl, url || self.path, id), {
167
+ method: 'DELETE',
168
+ headers: self._reqHeaders(idempotencyKey),
169
+ credentials: self.credentials
170
+ })
171
+ .then(_resolve);
172
+ };
173
+
174
+ _component.prototype.bulkDelete = function (ids, url, idempotencyKey) {
175
+ const self = this;
176
+ return window.fetch(buildUrl(self.baseUrl, url || self.path) + '/bulk-delete', {
177
+ method: 'DELETE',
178
+ headers: self._reqHeaders(idempotencyKey),
179
+ credentials: self.credentials,
180
+ body: JSON.stringify({ ids: ids })
181
+ })
182
+ .then(_resolve);
183
+ };
184
+
185
+ // ─── DOM Event Routing ────────────────────────────────────
186
+
187
+ function _bindEvents(self) {
188
+ self._handlers = {
189
+ sync: function (e) {
190
+ const detail = e.detail || {};
191
+ self.fetchDelta(detail.since)
192
+ .then(function (data) {
193
+ dispatch(self.dom, 'ln-api-connector:fetched', { data: data, since: detail.since, meta: detail.meta || null });
194
+ })
195
+ .catch(function (err) {
196
+ dispatch(self.dom, 'ln-api-connector:error', {
197
+ action: 'sync',
198
+ error: err.message,
199
+ status: err.status || 0,
200
+ data: err.data || null,
201
+ since: detail.since,
202
+ meta: detail.meta || null
203
+ });
204
+ });
205
+ },
206
+ query: function (e) {
207
+ const detail = e.detail || {};
208
+ const queryParams = detail.query || detail;
209
+ self.query(queryParams)
210
+ .then(function (res) {
211
+ const payload = res || {};
212
+ dispatch(self.dom, 'ln-api-connector:fetched', {
213
+ data: payload.data || (Array.isArray(payload) ? payload : []),
214
+ total: payload.total,
215
+ filtered: payload.filtered,
216
+ offset: queryParams.offset,
217
+ queryGen: queryParams.queryGen,
218
+ meta: detail.meta || null
219
+ });
220
+ })
221
+ .catch(function (err) {
222
+ dispatch(self.dom, 'ln-api-connector:error', {
223
+ action: 'query',
224
+ error: err.message,
225
+ status: err.status || 0,
226
+ data: err.data || null,
227
+ meta: detail.meta || null
228
+ });
229
+ });
230
+ },
231
+ create: function (e) {
232
+ const detail = e.detail || {};
233
+ self.create(detail.data, detail.url, detail.idempotencyKey)
234
+ .then(function (body) {
235
+ const record = (body && body.content !== undefined) ? body.content : body;
236
+ const message = (body && body.message) ? body.message : null;
237
+ dispatch(self.dom, 'ln-api-connector:created', { record: record, tempId: detail.tempId, message: message, meta: detail.meta || null });
238
+ })
239
+ .catch(function (err) {
240
+ dispatch(self.dom, 'ln-api-connector:error', {
241
+ action: 'create',
242
+ error: err.message,
243
+ status: err.status || 0,
244
+ data: err.data || null,
245
+ tempId: detail.tempId,
246
+ meta: detail.meta || null
247
+ });
248
+ });
249
+ },
250
+ update: function (e) {
251
+ const detail = e.detail || {};
252
+ self.update(detail.id, detail.data, detail.expected_version, detail.url, detail.idempotencyKey)
253
+ .then(function (body) {
254
+ const record = (body && body.content !== undefined) ? body.content : body;
255
+ const message = (body && body.message) ? body.message : null;
256
+ dispatch(self.dom, 'ln-api-connector:updated', { record: record, id: detail.id, message: message, meta: detail.meta || null });
257
+ })
258
+ .catch(function (err) {
259
+ dispatch(self.dom, 'ln-api-connector:error', {
260
+ action: 'update',
261
+ error: err.message,
262
+ status: err.status || 0,
263
+ data: err.data || null,
264
+ id: detail.id,
265
+ conflictData: err.status === 409 ? err.data : null,
266
+ meta: detail.meta || null
267
+ });
268
+ });
269
+ },
270
+ delete: function (e) {
271
+ const detail = e.detail || {};
272
+ self.delete(detail.id, detail.url, detail.idempotencyKey)
273
+ .then(function (body) {
274
+ const message = (body && body.message) ? body.message : null;
275
+ dispatch(self.dom, 'ln-api-connector:deleted', { response: body, id: detail.id, message: message, meta: detail.meta || null });
276
+ })
277
+ .catch(function (err) {
278
+ dispatch(self.dom, 'ln-api-connector:error', {
279
+ action: 'delete',
280
+ error: err.message,
281
+ status: err.status || 0,
282
+ data: err.data || null,
283
+ id: detail.id,
284
+ meta: detail.meta || null
285
+ });
286
+ });
287
+ },
288
+ bulkDelete: function (e) {
289
+ const detail = e.detail || {};
290
+ self.bulkDelete(detail.ids, detail.url, detail.idempotencyKey)
291
+ .then(function (body) {
292
+ const message = (body && body.message) ? body.message : null;
293
+ dispatch(self.dom, 'ln-api-connector:bulk-deleted', { response: body, ids: detail.ids, message: message, meta: detail.meta || null });
294
+ })
295
+ .catch(function (err) {
296
+ dispatch(self.dom, 'ln-api-connector:error', {
297
+ action: 'bulk-delete',
298
+ error: err.message,
299
+ status: err.status || 0,
300
+ data: err.data || null,
301
+ ids: detail.ids,
302
+ meta: detail.meta || null
303
+ });
304
+ });
305
+ }
306
+ };
307
+
308
+ // Support both general and REST namespaces for incoming requests
309
+ const namespaces = ['ln-api-connector', 'ln-rest-connector'];
310
+ namespaces.forEach(function (ns) {
311
+ self.dom.addEventListener(ns + ':request-sync', self._handlers.sync);
312
+ self.dom.addEventListener(ns + ':request-query', self._handlers.query);
313
+ self.dom.addEventListener(ns + ':request-fetch', self._handlers.query);
314
+ self.dom.addEventListener(ns + ':request-create', self._handlers.create);
315
+ self.dom.addEventListener(ns + ':request-update', self._handlers.update);
316
+ self.dom.addEventListener(ns + ':request-delete', self._handlers.delete);
317
+ self.dom.addEventListener(ns + ':request-bulk-delete', self._handlers.bulkDelete);
318
+ });
319
+ }
320
+
321
+ _component.prototype.destroy = function () {
322
+ if (!this.dom[DOM_ATTRIBUTE]) return;
323
+
324
+ const self = this;
325
+ if (self._handlers) {
326
+ const namespaces = ['ln-api-connector', 'ln-rest-connector'];
327
+ namespaces.forEach(function (ns) {
328
+ self.dom.removeEventListener(ns + ':request-sync', self._handlers.sync);
329
+ self.dom.removeEventListener(ns + ':request-query', self._handlers.query);
330
+ self.dom.removeEventListener(ns + ':request-fetch', self._handlers.query);
331
+ self.dom.removeEventListener(ns + ':request-create', self._handlers.create);
332
+ self.dom.removeEventListener(ns + ':request-update', self._handlers.update);
333
+ self.dom.removeEventListener(ns + ':request-delete', self._handlers.delete);
334
+ self.dom.removeEventListener(ns + ':request-bulk-delete', self._handlers.bulkDelete);
335
+ });
336
+ self._handlers = null;
337
+ }
338
+
339
+ dispatch(this.dom, 'ln-api-connector:destroyed', { target: this.dom });
340
+
341
+ delete this.dom[DOM_ATTRIBUTE];
342
+ delete this.dom[DOM_ALIAS];
343
+ };
344
+
345
+ // ─── Attribute Sync ────────────────────────────────────────
346
+
347
+ function _syncAttribute(el) {
348
+ const instance = el[DOM_ATTRIBUTE];
349
+ if (!instance) return;
350
+ instance.refreshConfig();
351
+ }
352
+
353
+ // ─── Registration ──────────────────────────────────────
354
+
355
+ registerComponent(DOM_SELECTOR, DOM_ATTRIBUTE, _component, 'ln-api-connector', {
356
+ extraAttributes: [
357
+ 'data-ln-api-base-url',
358
+ 'data-ln-api-path',
359
+ 'data-ln-api-headers',
360
+ 'data-ln-api-param-offset',
361
+ 'data-ln-api-param-limit',
362
+ 'data-ln-api-param-search',
363
+ 'data-ln-api-param-sort-field',
364
+ 'data-ln-api-param-sort-dir'
365
+ ],
366
+ onAttributeChange: _syncAttribute
367
+ });
368
+ })();