kempo-ui 0.2.1 → 0.2.3
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
import LightComponent from"./LightComponent.js";import{html,render,unsafeHTML}from"../lit-all.min.js";export default class Import extends LightComponent{static properties={src:{type:String,reflect:!0},content:{type:String},scripts:{type:Array}};static replacements={};constructor(){super(),this.src="",this.content="",this.scripts=[]}updated(t){super.updated(),t.has("src")&&this.fetch(),t.has("content")&&this.scripts.length>0&&
|
|
1
|
+
import LightComponent from"./LightComponent.js";import{html,render,unsafeHTML}from"../lit-all.min.js";export default class Import extends LightComponent{static properties={src:{type:String,reflect:!0},content:{type:String},scripts:{type:Array}};static replacements={};constructor(){super(),this.src="",this.content="",this.scripts=[]}updated(t){super.updated(),t.has("src")&&(void 0===t.get("src")&&""===this.src||this.dispatchEvent(new CustomEvent("src-change",{detail:{src:this.src},bubbles:!0,composed:!0})),this.fetch()),t.has("content")&&this.content&&setTimeout(()=>{this.scripts.length>0&&this.executeScripts(),this.dispatchEvent(new CustomEvent("content-rendered",{bubbles:!0,composed:!0}))},0)}async fetch(){if(!this.src)return;this.dispatchEvent(new CustomEvent("fetch-start",{detail:{src:this.src},bubbles:!0,composed:!0}));const t=await fetch(this.src);this.dispatchEvent(new CustomEvent("fetch-response",{detail:{src:this.src,status:t.status},bubbles:!0,composed:!0}));let e=await t.text();for(const[t,s]of Object.entries(Import.replacements))e=e.replace(new RegExp(`%%${t}%%`,"g"),s);const s=(new DOMParser).parseFromString(e,"text/html"),r=s.querySelectorAll("script");this.scripts=Array.from(r).map(t=>({src:t.src,text:t.textContent,type:t.type||"text/javascript"})),r.forEach(t=>t.remove()),this.content=s.body?s.body.innerHTML:e}executeScripts(){this.scripts.forEach(t=>{const e=document.createElement("script");e.type=t.type,t.src?e.src=t.src:t.text&&(e.textContent=t.text),document.head.appendChild(e)}),this.scripts=[]}renderLightDom(){return this.content?html`${unsafeHTML(this.content)}`:html``}}window.customElements.define("k-import",Import);
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
<a href="#requirements">Requirements</a><br />
|
|
29
29
|
<a href="#properties">Properties</a><br />
|
|
30
30
|
<a href="#methods">Methods</a><br />
|
|
31
|
+
<a href="#events">Events</a><br />
|
|
31
32
|
</div>
|
|
32
33
|
</k-accordion-panel>
|
|
33
34
|
</k-accordion>
|
|
@@ -68,8 +69,21 @@
|
|
|
68
69
|
<p>The source URL of the HTML snippet to import. Syncs to <code>src</code> attribute.</p>
|
|
69
70
|
|
|
70
71
|
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
71
|
-
<h5><code>
|
|
72
|
-
<p>
|
|
72
|
+
<h5><code>fetch()<i>: Promise</i></code></h5>
|
|
73
|
+
<p>Fetches and renders the HTML snippet from the current <code>src</code> URL. Called automatically when <code>src</code> changes.</p>
|
|
74
|
+
|
|
75
|
+
<h3 id="events"><a href="#events" class="no-link">Events</a></h3>
|
|
76
|
+
<h5><code>src-change</code></h5>
|
|
77
|
+
<p>Fired when the <code>src</code> property changes. <code>detail: { src }</code></p>
|
|
78
|
+
<h5><code>fetch-start</code></h5>
|
|
79
|
+
<p>Fired when the HTTP fetch begins. <code>detail: { src }</code></p>
|
|
80
|
+
<h5><code>fetch-response</code></h5>
|
|
81
|
+
<p>Fired when the HTTP response is received. <code>detail: { src, status }</code></p>
|
|
82
|
+
<h5><code>content-rendered</code></h5>
|
|
83
|
+
<p>Fired after the HTML content has been injected into the DOM and all inline scripts have been executed.</p>
|
|
84
|
+
<k-card label="JavaScript">
|
|
85
|
+
<pre><code class="hljs javascript"><span class="hljs-keyword">const</span> importer = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">'k-import'</span>);<br>importer.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'src-change'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'src set:'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">src</span>));<br>importer.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'fetch-start'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'fetching:'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">src</span>));<br>importer.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'fetch-response'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'response status:'</span>, e.<span class="hljs-property">detail</span>.<span class="hljs-property">status</span>));<br>importer.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">'content-rendered'</span>, <span class="hljs-function">() =></span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">'content ready'</span>));</code></pre>
|
|
86
|
+
</k-card>
|
|
73
87
|
|
|
74
88
|
</k-main>
|
|
75
89
|
<div style="height:33vh"></div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import LightComponent from"./LightComponent.js";import{html,render,unsafeHTML}from"../lit-all.min.js";export default class Import extends LightComponent{static properties={src:{type:String,reflect:!0},content:{type:String},scripts:{type:Array}};static replacements={};constructor(){super(),this.src="",this.content="",this.scripts=[]}updated(t){super.updated(),t.has("src")&&this.fetch(),t.has("content")&&this.scripts.length>0&&
|
|
1
|
+
import LightComponent from"./LightComponent.js";import{html,render,unsafeHTML}from"../lit-all.min.js";export default class Import extends LightComponent{static properties={src:{type:String,reflect:!0},content:{type:String},scripts:{type:Array}};static replacements={};constructor(){super(),this.src="",this.content="",this.scripts=[]}updated(t){super.updated(),t.has("src")&&(void 0===t.get("src")&&""===this.src||this.dispatchEvent(new CustomEvent("src-change",{detail:{src:this.src},bubbles:!0,composed:!0})),this.fetch()),t.has("content")&&this.content&&setTimeout(()=>{this.scripts.length>0&&this.executeScripts(),this.dispatchEvent(new CustomEvent("content-rendered",{bubbles:!0,composed:!0}))},0)}async fetch(){if(!this.src)return;this.dispatchEvent(new CustomEvent("fetch-start",{detail:{src:this.src},bubbles:!0,composed:!0}));const t=await fetch(this.src);this.dispatchEvent(new CustomEvent("fetch-response",{detail:{src:this.src,status:t.status},bubbles:!0,composed:!0}));let e=await t.text();for(const[t,s]of Object.entries(Import.replacements))e=e.replace(new RegExp(`%%${t}%%`,"g"),s);const s=(new DOMParser).parseFromString(e,"text/html"),r=s.querySelectorAll("script");this.scripts=Array.from(r).map(t=>({src:t.src,text:t.textContent,type:t.type||"text/javascript"})),r.forEach(t=>t.remove()),this.content=s.body?s.body.innerHTML:e}executeScripts(){this.scripts.forEach(t=>{const e=document.createElement("script");e.type=t.type,t.src?e.src=t.src:t.text&&(e.textContent=t.text),document.head.appendChild(e)}),this.scripts=[]}renderLightDom(){return this.content?html`${unsafeHTML(this.content)}`:html``}}window.customElements.define("k-import",Import);
|
package/package.json
CHANGED
package/src/components/Import.js
CHANGED
|
@@ -29,14 +29,18 @@ export default class Import extends LightComponent {
|
|
|
29
29
|
*/
|
|
30
30
|
updated(changedProperties){
|
|
31
31
|
super.updated(); // Important: call super for LightComponent
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
if(changedProperties.has('src')){
|
|
34
|
+
if(changedProperties.get('src') !== undefined || this.src !== '')
|
|
35
|
+
this.dispatchEvent(new CustomEvent('src-change', { detail: { src: this.src }, bubbles: true, composed: true }));
|
|
34
36
|
this.fetch();
|
|
35
37
|
}
|
|
36
|
-
|
|
37
|
-
if(changedProperties.has('content') && this.
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
if(changedProperties.has('content') && this.content){
|
|
40
|
+
setTimeout(() => {
|
|
41
|
+
if(this.scripts.length > 0) this.executeScripts();
|
|
42
|
+
this.dispatchEvent(new CustomEvent('content-rendered', { bubbles: true, composed: true }));
|
|
43
|
+
}, 0);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -44,7 +48,11 @@ export default class Import extends LightComponent {
|
|
|
44
48
|
Methods
|
|
45
49
|
*/
|
|
46
50
|
async fetch(){
|
|
47
|
-
|
|
51
|
+
if(!this.src) return;
|
|
52
|
+
this.dispatchEvent(new CustomEvent('fetch-start', { detail: { src: this.src }, bubbles: true, composed: true }));
|
|
53
|
+
const response = await fetch(this.src);
|
|
54
|
+
this.dispatchEvent(new CustomEvent('fetch-response', { detail: { src: this.src, status: response.status }, bubbles: true, composed: true }));
|
|
55
|
+
let contents = await response.text();
|
|
48
56
|
for (const [name, value] of Object.entries(Import.replacements)) {
|
|
49
57
|
contents = contents.replace(new RegExp(`%%${name}%%`, 'g'), value);
|
|
50
58
|
}
|
|
@@ -82,7 +90,7 @@ export default class Import extends LightComponent {
|
|
|
82
90
|
// Append to head to execute
|
|
83
91
|
document.head.appendChild(scriptElement);
|
|
84
92
|
});
|
|
85
|
-
|
|
93
|
+
|
|
86
94
|
// Clear scripts after execution to avoid re-execution
|
|
87
95
|
this.scripts = [];
|
|
88
96
|
}
|
|
@@ -308,5 +308,217 @@ export default {
|
|
|
308
308
|
// LightComponent sets display: contents
|
|
309
309
|
cleanup(container);
|
|
310
310
|
pass('Import has appropriate display');
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
Event Tests
|
|
315
|
+
*/
|
|
316
|
+
'should not dispatch src-change event on initialization with empty src': async ({pass, fail}) => {
|
|
317
|
+
let eventFired = false;
|
|
318
|
+
const container = document.createElement('div');
|
|
319
|
+
const importEl = document.createElement('k-import');
|
|
320
|
+
importEl.addEventListener('src-change', () => { eventFired = true; });
|
|
321
|
+
container.appendChild(importEl);
|
|
322
|
+
document.body.appendChild(container);
|
|
323
|
+
await importEl.updateComplete;
|
|
324
|
+
|
|
325
|
+
if(eventFired){
|
|
326
|
+
cleanup(container);
|
|
327
|
+
return fail('src-change should not fire during initialization with empty src');
|
|
328
|
+
}
|
|
329
|
+
cleanup(container);
|
|
330
|
+
pass('src-change not fired on initialization with empty src');
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
'should dispatch src-change event when src is set': async ({pass, fail}) => {
|
|
334
|
+
const { container, importEl } = await createImport();
|
|
335
|
+
let eventFired = false;
|
|
336
|
+
let eventDetail = null;
|
|
337
|
+
importEl.addEventListener('src-change', e => {
|
|
338
|
+
eventFired = true;
|
|
339
|
+
eventDetail = e.detail;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
importEl.src = '/some-file.html';
|
|
343
|
+
await importEl.updateComplete;
|
|
344
|
+
|
|
345
|
+
if(!eventFired){
|
|
346
|
+
cleanup(container);
|
|
347
|
+
return fail('src-change event should be fired when src changes');
|
|
348
|
+
}
|
|
349
|
+
if(eventDetail.src !== '/some-file.html'){
|
|
350
|
+
cleanup(container);
|
|
351
|
+
return fail(`Expected detail.src "/some-file.html", got "${eventDetail.src}"`);
|
|
352
|
+
}
|
|
353
|
+
cleanup(container);
|
|
354
|
+
pass('src-change event dispatched with correct detail');
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
'src-change event should bubble': async ({pass, fail}) => {
|
|
358
|
+
const { container, importEl } = await createImport();
|
|
359
|
+
let bubbled = false;
|
|
360
|
+
container.addEventListener('src-change', () => { bubbled = true; });
|
|
361
|
+
|
|
362
|
+
importEl.src = '/bubble-test.html';
|
|
363
|
+
await importEl.updateComplete;
|
|
364
|
+
|
|
365
|
+
if(!bubbled){
|
|
366
|
+
cleanup(container);
|
|
367
|
+
return fail('src-change event should bubble');
|
|
368
|
+
}
|
|
369
|
+
cleanup(container);
|
|
370
|
+
pass('src-change event bubbles');
|
|
371
|
+
},
|
|
372
|
+
|
|
373
|
+
'should dispatch fetch-start event when fetch begins': async ({pass, fail}) => {
|
|
374
|
+
const { container, importEl } = await createImport();
|
|
375
|
+
const originalFetch = window.fetch;
|
|
376
|
+
let fetchStartFired = false;
|
|
377
|
+
let fetchStartDetail = null;
|
|
378
|
+
|
|
379
|
+
importEl.addEventListener('fetch-start', e => {
|
|
380
|
+
fetchStartFired = true;
|
|
381
|
+
fetchStartDetail = e.detail;
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
window.fetch = () => Promise.resolve({ status: 200, text: () => Promise.resolve('<p>test</p>') });
|
|
385
|
+
|
|
386
|
+
importEl.src = '/mock-file.html';
|
|
387
|
+
await importEl.updateComplete;
|
|
388
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
389
|
+
|
|
390
|
+
window.fetch = originalFetch;
|
|
391
|
+
|
|
392
|
+
if(!fetchStartFired){
|
|
393
|
+
cleanup(container);
|
|
394
|
+
return fail('fetch-start event should be fired');
|
|
395
|
+
}
|
|
396
|
+
if(fetchStartDetail.src !== '/mock-file.html'){
|
|
397
|
+
cleanup(container);
|
|
398
|
+
return fail(`Expected detail.src "/mock-file.html", got "${fetchStartDetail.src}"`);
|
|
399
|
+
}
|
|
400
|
+
cleanup(container);
|
|
401
|
+
pass('fetch-start event dispatched with correct detail');
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
'should dispatch fetch-response event after response is received': async ({pass, fail}) => {
|
|
405
|
+
const { container, importEl } = await createImport();
|
|
406
|
+
const originalFetch = window.fetch;
|
|
407
|
+
let fetchResponseFired = false;
|
|
408
|
+
let fetchResponseDetail = null;
|
|
409
|
+
|
|
410
|
+
importEl.addEventListener('fetch-response', e => {
|
|
411
|
+
fetchResponseFired = true;
|
|
412
|
+
fetchResponseDetail = e.detail;
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
window.fetch = () => Promise.resolve({ status: 200, text: () => Promise.resolve('<p>test</p>') });
|
|
416
|
+
|
|
417
|
+
importEl.src = '/mock-file.html';
|
|
418
|
+
await importEl.updateComplete;
|
|
419
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
420
|
+
|
|
421
|
+
window.fetch = originalFetch;
|
|
422
|
+
|
|
423
|
+
if(!fetchResponseFired){
|
|
424
|
+
cleanup(container);
|
|
425
|
+
return fail('fetch-response event should be fired');
|
|
426
|
+
}
|
|
427
|
+
if(fetchResponseDetail.src !== '/mock-file.html'){
|
|
428
|
+
cleanup(container);
|
|
429
|
+
return fail(`Expected detail.src "/mock-file.html", got "${fetchResponseDetail.src}"`);
|
|
430
|
+
}
|
|
431
|
+
if(fetchResponseDetail.status !== 200){
|
|
432
|
+
cleanup(container);
|
|
433
|
+
return fail(`Expected detail.status 200, got ${fetchResponseDetail.status}`);
|
|
434
|
+
}
|
|
435
|
+
cleanup(container);
|
|
436
|
+
pass('fetch-response event dispatched with correct detail');
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
'fetch-start should fire before fetch-response': async ({pass, fail}) => {
|
|
440
|
+
const { container, importEl } = await createImport();
|
|
441
|
+
const originalFetch = window.fetch;
|
|
442
|
+
const order = [];
|
|
443
|
+
|
|
444
|
+
importEl.addEventListener('fetch-start', () => order.push('fetch-start'));
|
|
445
|
+
importEl.addEventListener('fetch-response', () => order.push('fetch-response'));
|
|
446
|
+
|
|
447
|
+
window.fetch = () => Promise.resolve({ status: 200, text: () => Promise.resolve('<p>test</p>') });
|
|
448
|
+
|
|
449
|
+
importEl.src = '/mock-file.html';
|
|
450
|
+
await importEl.updateComplete;
|
|
451
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
452
|
+
|
|
453
|
+
window.fetch = originalFetch;
|
|
454
|
+
|
|
455
|
+
if(order[0] !== 'fetch-start' || order[1] !== 'fetch-response'){
|
|
456
|
+
cleanup(container);
|
|
457
|
+
return fail(`Expected [fetch-start, fetch-response], got [${order.join(', ')}]`);
|
|
458
|
+
}
|
|
459
|
+
cleanup(container);
|
|
460
|
+
pass('fetch-start fires before fetch-response');
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
'should dispatch content-rendered event after content is set': async ({pass, fail}) => {
|
|
464
|
+
const { container, importEl } = await createImport();
|
|
465
|
+
let eventFired = false;
|
|
466
|
+
|
|
467
|
+
importEl.addEventListener('content-rendered', () => { eventFired = true; });
|
|
468
|
+
|
|
469
|
+
importEl.content = '<p>rendered content</p>';
|
|
470
|
+
await importEl.updateComplete;
|
|
471
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
472
|
+
|
|
473
|
+
if(!eventFired){
|
|
474
|
+
cleanup(container);
|
|
475
|
+
return fail('content-rendered event should be fired after content changes');
|
|
476
|
+
}
|
|
477
|
+
cleanup(container);
|
|
478
|
+
pass('content-rendered event dispatched after content is set');
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
'content-rendered event should bubble': async ({pass, fail}) => {
|
|
482
|
+
const { container, importEl } = await createImport();
|
|
483
|
+
let bubbled = false;
|
|
484
|
+
container.addEventListener('content-rendered', () => { bubbled = true; });
|
|
485
|
+
|
|
486
|
+
importEl.content = '<p>bubble test</p>';
|
|
487
|
+
await importEl.updateComplete;
|
|
488
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
489
|
+
|
|
490
|
+
if(!bubbled){
|
|
491
|
+
cleanup(container);
|
|
492
|
+
return fail('content-rendered event should bubble');
|
|
493
|
+
}
|
|
494
|
+
cleanup(container);
|
|
495
|
+
pass('content-rendered event bubbles');
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
'should dispatch all four events in order during a full fetch cycle': async ({pass, fail}) => {
|
|
499
|
+
const { container, importEl } = await createImport();
|
|
500
|
+
const originalFetch = window.fetch;
|
|
501
|
+
const order = [];
|
|
502
|
+
|
|
503
|
+
importEl.addEventListener('src-change', () => order.push('src-change'));
|
|
504
|
+
importEl.addEventListener('fetch-start', () => order.push('fetch-start'));
|
|
505
|
+
importEl.addEventListener('fetch-response', () => order.push('fetch-response'));
|
|
506
|
+
importEl.addEventListener('content-rendered', () => order.push('content-rendered'));
|
|
507
|
+
|
|
508
|
+
window.fetch = () => Promise.resolve({ status: 200, text: () => Promise.resolve('<p>full cycle</p>') });
|
|
509
|
+
|
|
510
|
+
importEl.src = '/full-cycle.html';
|
|
511
|
+
await importEl.updateComplete;
|
|
512
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
513
|
+
|
|
514
|
+
window.fetch = originalFetch;
|
|
515
|
+
|
|
516
|
+
const expected = ['src-change', 'fetch-start', 'fetch-response', 'content-rendered'];
|
|
517
|
+
if(JSON.stringify(order) !== JSON.stringify(expected)){
|
|
518
|
+
cleanup(container);
|
|
519
|
+
return fail(`Expected [${expected.join(', ')}], got [${order.join(', ')}]`);
|
|
520
|
+
}
|
|
521
|
+
cleanup(container);
|
|
522
|
+
pass('All four events fire in correct order');
|
|
311
523
|
}
|
|
312
524
|
};
|
/package/{llm.txt.md → llm.txt}
RENAMED
|
File without changes
|