@lengkapp/edge 0.0.3 → 0.0.5

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.
package/edge-client.js CHANGED
@@ -1,279 +1,287 @@
1
1
  (() => {
2
- 'use strict';
2
+ 'use strict';
3
3
 
4
- const d = document,
5
- body = d.body,
6
- head = d.head;
4
+ const d = document,
5
+ body = d.body,
6
+ head = d.head;
7
7
 
8
- // ---------- Constants and caches ----------
9
- const VALID_EVENTS = new Set([
10
- 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
11
- 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu',
12
- 'focus', 'blur', 'focusin', 'focusout',
13
- 'keydown', 'keyup', 'keypress',
14
- 'change', 'input', 'submit', 'reset',
15
- 'load', 'DOMContentLoaded', 'ready',
16
- 'scroll', 'resize', 'wheel', 'touchstart', 'touchend', 'touchmove',
17
- 'visible', 'intersect'
18
- ]);
8
+ // ---------- Constants and caches ----------
9
+ const VALID_EVENTS = new Set([
10
+ 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
11
+ 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu',
12
+ 'focus', 'blur', 'focusin', 'focusout',
13
+ 'keydown', 'keyup', 'keypress',
14
+ 'change', 'input', 'submit', 'reset',
15
+ 'load', 'DOMContentLoaded', 'ready',
16
+ 'scroll', 'resize', 'wheel', 'touchstart', 'touchend', 'touchmove',
17
+ 'visible', 'intersect'
18
+ ]);
19
19
 
20
- // WeakMaps for per‑element caches
21
- const triggerCache = new WeakMap(); // element -> Set of event names (lowercase)
22
- const targetCache = new WeakMap(); // element -> target DOM node (resolved)
23
- const resourceCache = new Set(); // already injected CSS/JS URLs
20
+ // WeakMaps for per‑element caches
21
+ const triggerCache = new WeakMap(); // element -> Set of event names (lowercase)
22
+ const targetCache = new WeakMap(); // element -> target DOM node (resolved)
23
+ const resourceCache = new Set(); // already injected CSS/JS URLs
24
+
25
+ // ---------- Injected base styles ----------
26
+ const injectBaseStyles = () => {
27
+ if (d.getElementById('df-style')) return;
28
+ const style = d.createElement('style');
29
+ style.id = 'df-style';
30
+ style.textContent = `
31
+ .df-skeleton {
32
+ position: absolute;
33
+ inset: 0;
34
+ background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
35
+ background-size: 200% 100%;
36
+ animation: df-shimmer 1.5s ease-in-out infinite;
37
+ border-radius: inherit;
38
+ pointer-events: none;
39
+ box-sizing: border-box;
40
+ }
41
+ @keyframes df-shimmer {
42
+ 0% {
43
+ background-position: -200% 0;
44
+ }
45
+ 100% {
46
+ background-position: 200% 0;
47
+ }
48
+ }
24
49
 
25
- // ---------- Injected base styles ----------
26
- const injectBaseStyles = () => {
27
- if (d.getElementById('df-style')) return;
28
- const style = d.createElement('style');
29
- style.id = 'df-style';
30
- style.textContent = `
31
- .df-skeleton { display:flex; flex-direction:column; gap:8px; padding:10px; }
32
- .df-skeleton .df-bar {
33
- height:12px; background:linear-gradient(90deg,#e0e0e0 25%,#f0f0f0 50%,#e0e0e0 75%);
34
- background-size:200% 100%; animation:df-shimmer 1.5s infinite; border-radius:4px;
35
- }
36
- @keyframes df-shimmer { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
37
50
  .df-retry {
38
51
  display:inline-block; padding:8px 16px; background:#007bff; color:#fff;
39
52
  border-radius:4px; cursor:pointer; text-decoration:none; font-size:14px;
40
53
  }
41
54
  .df-retry:hover { background:#0056b3; }
42
55
  `;
43
- head.appendChild(style);
44
- };
45
- injectBaseStyles();
56
+ head.appendChild(style);
57
+ };
58
+ injectBaseStyles();
46
59
 
47
- // ---------- Pre‑built templates (cloned on use) ----------
48
- const skeletonTemplate = d.createElement('template');
49
- skeletonTemplate.innerHTML = `
50
- <div class="df-skeleton">
51
- <div class="df-bar"></div>
52
- <div class="df-bar"></div>
53
- <div class="df-bar"></div>
54
- </div>`;
55
- const retryTemplate = d.createElement('template');
56
- retryTemplate.innerHTML = `<span class="df-retry">Retry</span>`;
60
+ // ---------- Pre‑built templates (cloned on use) ----------
61
+ const skeletonTemplate = d.createElement('template');
62
+ skeletonTemplate.innerHTML = `
63
+ <div class="df-skeleton"></div>`;
64
+ const retryTemplate = d.createElement('template');
65
+ retryTemplate.innerHTML = `<span class="df-retry">Retry</span>`;
57
66
 
58
- // ---------- Resource helpers ----------
59
- const parseHeaderList = (str) => {
60
- if (!str) return [];
61
- return str.replace(/^\[|\]$/g, '').split(',')
62
- .map(s => s.trim().replace(/^['"]|['"]$/g, ''))
63
- .filter(Boolean);
64
- };
67
+ // ---------- Resource helpers ----------
68
+ const parseHeaderList = (str) => {
69
+ if (!str) return [];
70
+ return str.replace(/^\[|\]$/g, '').split(',')
71
+ .map(s => s.trim().replace(/^['"]|['"]$/g, ''))
72
+ .filter(Boolean);
73
+ };
65
74
 
66
- const injectResources = (resources, type) => {
67
- resources.forEach(url => {
68
- if (resourceCache.has(url)) return;
69
- resourceCache.add(url);
70
- if (type === 'css') {
71
- const link = d.createElement('link');
72
- link.rel = 'stylesheet'; link.href = url;
73
- head.appendChild(link);
74
- } else if (type === 'js') {
75
- const script = d.createElement('script');
76
- script.src = url;
77
- body.appendChild(script);
78
- }
79
- });
80
- };
75
+ const injectResources = (resources, type) => {
76
+ resources.forEach(url => {
77
+ if (resourceCache.has(url)) return;
78
+ resourceCache.add(url);
79
+ if (type === 'css') {
80
+ const link = d.createElement('link');
81
+ link.rel = 'stylesheet'; link.href = url;
82
+ head.appendChild(link);
83
+ } else if (type === 'js') {
84
+ const script = d.createElement('script');
85
+ script.src = url;
86
+ body.appendChild(script);
87
+ }
88
+ });
89
+ };
81
90
 
82
- // ---------- Visibility helpers ----------
83
- const isElementActuallyVisible = (el) => {
84
- const style = getComputedStyle(el);
85
- if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
86
- const rect = el.getBoundingClientRect();
87
- return rect.width > 0 && rect.height > 0;
88
- };
91
+ // ---------- Visibility helpers ----------
92
+ const isElementActuallyVisible = (el) => {
93
+ const style = getComputedStyle(el);
94
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
95
+ const rect = el.getBoundingClientRect();
96
+ return rect.width > 0 && rect.height > 0;
97
+ };
89
98
 
90
- // Shared IntersectionObserver
91
- let intersectionObserver = null;
92
- const getIO = () => {
93
- if (!intersectionObserver) {
94
- intersectionObserver = new IntersectionObserver((entries) => {
95
- entries.forEach(entry => {
96
- const el = entry.target;
97
- const visible = entry.isIntersecting && isElementActuallyVisible(el);
98
- if (visible && el.dataset.wasVisible !== 'true') {
99
- run(el);
100
- }
101
- el.dataset.wasVisible = visible ? 'true' : 'false';
102
- });
103
- }, { threshold: 0 });
104
- }
105
- return intersectionObserver;
106
- };
99
+ // Shared IntersectionObserver
100
+ let intersectionObserver = null;
101
+ const getIO = () => {
102
+ if (!intersectionObserver) {
103
+ intersectionObserver = new IntersectionObserver((entries) => {
104
+ entries.forEach(entry => {
105
+ const el = entry.target;
106
+ const visible = entry.isIntersecting && isElementActuallyVisible(el);
107
+ if (visible && el.dataset.wasVisible !== 'true') {
108
+ run(el);
109
+ }
110
+ el.dataset.wasVisible = visible ? 'true' : 'false';
111
+ });
112
+ }, { threshold: 0 });
113
+ }
114
+ return intersectionObserver;
115
+ };
107
116
 
108
- // ---------- Core run function ----------
109
- const run = async (el) => {
110
- if (el.dataset.running === 'true') return;
111
- el.dataset.running = 'true';
117
+ // ---------- Core run function ----------
118
+ const run = async (el) => {
119
+ if (el.dataset.running === 'true') return;
120
+ el.dataset.running = 'true';
112
121
 
113
- const post = el.hasAttribute('_post');
114
- const url = el.getAttribute('_post') || el.getAttribute('_get');
115
- let bodyData, headers = {};
122
+ const post = el.hasAttribute('_post');
123
+ const url = el.getAttribute('_post') || el.getAttribute('_get');
124
+ let bodyData, headers = {};
116
125
 
117
- if (post) {
118
- const formId = el.getAttribute('_form');
119
- const json = el.getAttribute('_json');
120
- if (formId) {
121
- const form = d.getElementById(formId);
122
- if (form) bodyData = new URLSearchParams(new FormData(form));
123
- } else if (json) {
124
- bodyData = JSON.stringify(Object.fromEntries(
125
- json.split(',').map(name => {
126
- const input = d.querySelector(`[name="${name}"]`);
127
- return [name, input ? input.value : ''];
128
- })
129
- ));
130
- headers['Content-Type'] = 'application/json';
131
- }
132
- }
126
+ if (post) {
127
+ const formId = el.getAttribute('_form');
128
+ const json = el.getAttribute('_json');
129
+ if (formId) {
130
+ const form = d.getElementById(formId);
131
+ if (form) bodyData = new URLSearchParams(new FormData(form));
132
+ } else if (json) {
133
+ bodyData = JSON.stringify(Object.fromEntries(
134
+ json.split(',').map(name => {
135
+ const input = d.querySelector(`[name="${name}"]`);
136
+ return [name, input ? input.value : ''];
137
+ })
138
+ ));
139
+ headers['Content-Type'] = 'application/json';
140
+ }
141
+ }
133
142
 
134
- // Resolve target (cached)
135
- const targetSelector = el.getAttribute('_target');
136
- let container = targetCache.get(el);
137
- if (!container) {
138
- container = targetSelector === 'this' ? el : d.querySelector(targetSelector);
139
- if (container) targetCache.set(el, container);
140
- }
141
- if (!container) {
142
- console.warn('Target not found:', targetSelector);
143
- el.dataset.running = 'false';
144
- return;
145
- }
143
+ // Resolve target (cached)
144
+ const targetSelector = el.getAttribute('_target');
145
+ let container = targetCache.get(el);
146
+ if (!container) {
147
+ container = targetSelector === 'this' ? el : d.querySelector(targetSelector);
148
+ if (container) targetCache.set(el, container);
149
+ }
150
+ if (!container) {
151
+ console.warn('Target not found:', targetSelector);
152
+ el.dataset.running = 'false';
153
+ return;
154
+ }
146
155
 
147
- // Show skeleton (clone template)
148
- container.replaceChildren(skeletonTemplate.content.cloneNode(true));
156
+ container.replaceChildren(skeletonTemplate.content.cloneNode(true));
149
157
 
150
- try {
151
- const res = await fetch(url, { method: post ? 'POST' : 'GET', body: bodyData, headers });
158
+ try {
159
+ const res = await fetch(url, { method: post ? 'POST' : 'GET', body: bodyData, headers });
152
160
 
153
- // Inject resources from headers
154
- const css = res.headers.get('x-css-required') || res.headers.get('x-css-requiered');
155
- const js = res.headers.get('x-js-required');
156
- if (css) injectResources(parseHeaderList(css), 'css');
157
- if (js) injectResources(parseHeaderList(js), 'js');
161
+ // Inject resources from headers
162
+ const css = res.headers.get('x-css-required') || res.headers.get('x-css-requiered');
163
+ const js = res.headers.get('x-js-required');
164
+ if (css) injectResources(parseHeaderList(css), 'css');
165
+ if (js) injectResources(parseHeaderList(js), 'js');
158
166
 
159
- const text = await res.text();
160
- container.innerHTML = text; // MutationObserver picks up new declarative elements
161
- } catch (err) {
162
- console.error(err);
163
- // Show retry (clone template)
164
- const retry = retryTemplate.content.firstElementChild.cloneNode(true);
165
- retry.addEventListener('click', (e) => {
166
- e.preventDefault();
167
- e.stopPropagation();
168
- run(el);
169
- });
170
- container.replaceChildren(retry);
171
- } finally {
172
- el.dataset.running = 'false';
173
- }
174
- };
167
+ const text = await res.text();
168
+ container.innerHTML = text; // MutationObserver picks up new declarative elements
169
+ } catch (err) {
170
+ console.error(err);
171
+ // Show retry (clone template)
172
+ const retry = retryTemplate.content.firstElementChild.cloneNode(true);
173
+ retry.addEventListener('click', (e) => {
174
+ e.preventDefault();
175
+ e.stopPropagation();
176
+ run(el);
177
+ });
178
+ container.replaceChildren(retry);
179
+ } finally {
180
+ el.dataset.running = 'false';
181
+ }
182
+ };
175
183
 
176
- // ---------- Trigger parsing and caching ----------
177
- const getTriggerEvents = (el) => {
178
- let events = triggerCache.get(el);
179
- if (events) return events;
180
- events = new Set();
181
- const attr = el.getAttribute('_trigger');
182
- if (attr && attr.trim() !== '') {
183
- attr.split(',').forEach(s => {
184
- const evt = s.trim().toLowerCase();
185
- if (VALID_EVENTS.has(evt)) events.add(evt);
186
- });
187
- } else {
188
- // Default: click (plus load if _target="this")
189
- events.add('click');
190
- if (el.getAttribute('_target') === 'this') events.add('load');
191
- }
192
- triggerCache.set(el, events);
193
- return events;
194
- };
184
+ // ---------- Trigger parsing and caching ----------
185
+ const getTriggerEvents = (el) => {
186
+ let events = triggerCache.get(el);
187
+ if (events) return events;
188
+ events = new Set();
189
+ const attr = el.getAttribute('_trigger');
190
+ if (attr && attr.trim() !== '') {
191
+ attr.split(',').forEach(s => {
192
+ const evt = s.trim().toLowerCase();
193
+ if (VALID_EVENTS.has(evt)) events.add(evt);
194
+ });
195
+ } else {
196
+ // Default: click (plus load if _target="this")
197
+ events.add('click');
198
+ if (el.getAttribute('_target') === 'this') events.add('load');
199
+ }
200
+ triggerCache.set(el, events);
201
+ return events;
202
+ };
195
203
 
196
- // ---------- Element initialisation (no per‑element listeners) ----------
197
- const initElement = (el) => {
198
- if (el.dataset.initialized === 'true') return;
199
- el.dataset.initialized = 'true';
204
+ // ---------- Element initialisation (no per‑element listeners) ----------
205
+ const initElement = (el) => {
206
+ if (el.dataset.initialized === 'true') return;
207
+ el.dataset.initialized = 'true';
200
208
 
201
- const events = getTriggerEvents(el);
209
+ const events = getTriggerEvents(el);
202
210
 
203
- // Handle load / ready immediately
204
- if (events.has('load') || events.has('domcontentloaded') || events.has('ready')) {
205
- run(el);
206
- }
207
- // Set up visibility observation
208
- if (events.has('visible') || events.has('intersect')) {
209
- el.dataset.wasVisible = 'false';
210
- getIO().observe(el);
211
- }
212
- // All other events are handled by the global delegated listener
213
- };
211
+ // Handle load / ready immediately
212
+ if (events.has('load') || events.has('domcontentloaded') || events.has('ready')) {
213
+ run(el);
214
+ }
215
+ // Set up visibility observation
216
+ if (events.has('visible') || events.has('intersect')) {
217
+ el.dataset.wasVisible = 'false';
218
+ getIO().observe(el);
219
+ }
220
+ // All other events are handled by the global delegated listener
221
+ };
214
222
 
215
- // ---------- Global event delegation ----------
216
- const delegatedEvents = new Set([
217
- 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
218
- 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu',
219
- 'focus', 'blur', 'focusin', 'focusout',
220
- 'keydown', 'keyup', 'keypress',
221
- 'change', 'input', 'submit', 'reset',
222
- 'scroll', 'resize', 'wheel', 'touchstart', 'touchend', 'touchmove'
223
- ]);
223
+ // ---------- Global event delegation ----------
224
+ const delegatedEvents = new Set([
225
+ 'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
226
+ 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu',
227
+ 'focus', 'blur', 'focusin', 'focusout',
228
+ 'keydown', 'keyup', 'keypress',
229
+ 'change', 'input', 'submit', 'reset',
230
+ 'scroll', 'resize', 'wheel', 'touchstart', 'touchend', 'touchmove'
231
+ ]);
224
232
 
225
- // Attach one listener per event type, passive for scroll‑like events
226
- delegatedEvents.forEach(evt => {
227
- const isPassive = ['scroll', 'touchstart', 'touchmove', 'touchend', 'wheel'].includes(evt);
228
- d.addEventListener(evt, (e) => {
229
- const target = e.target;
230
- if (!(target instanceof Element)) return;
231
- const el = target.closest('[_get], [_post]');
232
- if (!el) return;
233
- const events = getTriggerEvents(el);
234
- if (events.has(evt)) {
235
- if (evt === 'click') e.preventDefault();
236
- run(el);
237
- }
238
- }, isPassive ? { passive: true } : false);
239
- });
233
+ // Attach one listener per event type, passive for scroll‑like events
234
+ delegatedEvents.forEach(evt => {
235
+ const isPassive = ['scroll', 'touchstart', 'touchmove', 'touchend', 'wheel'].includes(evt);
236
+ d.addEventListener(evt, (e) => {
237
+ const target = e.target;
238
+ if (!(target instanceof Element)) return;
239
+ const el = target.closest('[_get], [_post]');
240
+ if (!el) return;
241
+ const events = getTriggerEvents(el);
242
+ if (events.has(evt)) {
243
+ if (evt === 'click') e.preventDefault();
244
+ run(el);
245
+ }
246
+ }, isPassive ? { passive: true } : false);
247
+ });
240
248
 
241
- // ---------- MutationObserver (batch processing) ----------
242
- let mutationQueue = [];
243
- let mutationScheduled = false;
249
+ // ---------- MutationObserver (batch processing) ----------
250
+ let mutationQueue = [];
251
+ let mutationScheduled = false;
244
252
 
245
- const processMutations = () => {
246
- mutationScheduled = false;
247
- const nodes = mutationQueue;
248
- mutationQueue = [];
249
- nodes.forEach(node => {
250
- if (node.nodeType !== 1) return;
251
- if (node.matches('[_get], [_post]')) initElement(node);
252
- node.querySelectorAll('[_get], [_post]').forEach(initElement);
253
- });
254
- };
253
+ const processMutations = () => {
254
+ mutationScheduled = false;
255
+ const nodes = mutationQueue;
256
+ mutationQueue = [];
257
+ nodes.forEach(node => {
258
+ if (node.nodeType !== 1) return;
259
+ if (node.matches('[_get], [_post]')) initElement(node);
260
+ node.querySelectorAll('[_get], [_post]').forEach(initElement);
261
+ });
262
+ };
255
263
 
256
- const mo = new MutationObserver((mutations) => {
257
- mutations.forEach(m => {
258
- m.addedNodes.forEach(node => {
259
- if (node.nodeType === 1) mutationQueue.push(node);
260
- });
261
- });
262
- if (!mutationScheduled) {
263
- mutationScheduled = true;
264
- Promise.resolve().then(processMutations);
265
- }
264
+ const mo = new MutationObserver((mutations) => {
265
+ mutations.forEach(m => {
266
+ m.addedNodes.forEach(node => {
267
+ if (node.nodeType === 1) mutationQueue.push(node);
266
268
  });
267
- mo.observe(body, { childList: true, subtree: true });
269
+ });
270
+ if (!mutationScheduled) {
271
+ mutationScheduled = true;
272
+ Promise.resolve().then(processMutations);
273
+ }
274
+ });
275
+ mo.observe(body, { childList: true, subtree: true });
268
276
 
269
- // ---------- Initialisation on DOM ready ----------
270
- const initAll = () => {
271
- d.querySelectorAll('[_get], [_post]').forEach(initElement);
272
- };
277
+ // ---------- Initialisation on DOM ready ----------
278
+ const initAll = () => {
279
+ d.querySelectorAll('[_get], [_post]').forEach(initElement);
280
+ };
273
281
 
274
- if (d.readyState === 'loading') {
275
- d.addEventListener('DOMContentLoaded', initAll, { once: true });
276
- } else {
277
- initAll();
278
- }
279
- })();
282
+ if (d.readyState === 'loading') {
283
+ d.addEventListener('DOMContentLoaded', initAll, { once: true });
284
+ } else {
285
+ initAll();
286
+ }
287
+ })();