@epa-wg/custom-element 0.0.18 → 0.0.20
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/README.md +32 -7
- package/bin/xslDtd2Ide.mjs +1 -1
- package/custom-element.js +203 -59
- package/demo/a.html +55 -15
- package/demo/data-slices.html +185 -0
- package/demo/demo.css +1 -0
- package/demo/dom-merge.html +2 -2
- package/demo/external-template.html +1 -0
- package/demo/http-request.html +39 -10
- package/demo/local-storage.html +108 -3
- package/demo/location-element.html +14 -9
- package/demo/parameters.html +18 -0
- package/demo/s.xml +93 -1
- package/demo/s.xslt +12 -10
- package/demo/ss.html +50 -30
- package/demo/z.html +62 -48
- package/http-request.js +55 -26
- package/ide/customData-dce.json +25 -2
- package/ide/web-types-dce.json +16 -3
- package/ide/web-types-xsl.json +1 -1
- package/index.html +2 -1
- package/local-storage.js +47 -10
- package/location-element.js +7 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,16 +22,17 @@ UI is re-rendered on each data slice change triggered by initialization or DOM e
|
|
|
22
22
|
<details>
|
|
23
23
|
<summary> What is DCE? </summary>
|
|
24
24
|
DCE provides the next level of abstraction in HTML - native composition. With native implementation which is
|
|
25
|
-
streaming parser, streaming transformation, multithreading. native assumes the C/Rust compiled code.
|
|
25
|
+
streaming parser, streaming transformation, multithreading. native assumes the C/Rust compiled code.
|
|
26
|
+
There is no place for JavaScript except of polyfill and ability to extend DCE, which otherwise has to be native.
|
|
26
27
|
|
|
27
28
|
The composition assumes the fully functional template and ability to call the template with parameters( custom tag + attributes) .
|
|
28
29
|
|
|
29
|
-
As the next to HTML abstraction layer - composition
|
|
30
|
+
As the next to HTML abstraction layer - **composition**, it provides:
|
|
30
31
|
* ability to use dependencies as from withing the page as from external file/lib via src attribute and # in URL
|
|
31
32
|
* ability to treat external content via content-type like html, SVG, images, video with own template rendering
|
|
32
33
|
* provide styles and embedded DCE declarations in own and named(lib) scope, sharing the scoped registry.
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
After composition the layer of **functional component** provides
|
|
35
36
|
* data layer with access to attributes/payload(+slots), dataset, data bound slice
|
|
36
37
|
* means in template to use the data selector for condition/enumeration/text injection into attributes and DOM
|
|
37
38
|
* Set of native primitives to support browser APIs declaratively: location,storage, http request which bonded to slice and as result to reactive UI.
|
|
@@ -58,12 +59,35 @@ npm i -P @epa-wg/custom-element
|
|
|
58
59
|
yarn add @epa-wg/custom-element
|
|
59
60
|
```
|
|
60
61
|
|
|
61
|
-
##
|
|
62
|
+
## Enable IDE support
|
|
63
|
+
[IDE.md](ide/IDE.md)
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
|
|
65
67
|
## [Live demo 🔗][demo-url]
|
|
68
|
+
|
|
69
|
+
### Interactivity via data `slice` triggered by events
|
|
70
|
+
```html
|
|
71
|
+
<custom-element>
|
|
72
|
+
<input slice="typed"> //slice/typed : {//slice/typed}
|
|
73
|
+
</custom-element>
|
|
74
|
+
|
|
75
|
+
<custom-element>
|
|
76
|
+
<template>
|
|
77
|
+
<button slice="clickcount"
|
|
78
|
+
slice-event="click"
|
|
79
|
+
slice-value="//clickcount + 1" > + </button>
|
|
80
|
+
<input slice="clickcount" type="number" value="{//clickcount ?? 0}">
|
|
81
|
+
Click count: { //clickcount }
|
|
82
|
+
</template>
|
|
83
|
+
</custom-element>
|
|
84
|
+
```
|
|
85
|
+
More on `slice` concept in [slice and events demo page][slice-demo-url]
|
|
86
|
+
|
|
87
|
+
### Templating power
|
|
88
|
+
comes from XSLT and XPath. Which is natively implemented in all current browsers, globally tested and well documented.
|
|
66
89
|
```html
|
|
90
|
+
|
|
67
91
|
<custom-element tag="pokemon-tile" hidden>
|
|
68
92
|
<h3>{title}</h3> <!-- title is an attribute in instance
|
|
69
93
|
mapped into /*/attributes/title -->
|
|
@@ -315,6 +339,7 @@ within template
|
|
|
315
339
|
[git-test-url]: https://github.com/EPA-WG/custom-element-test
|
|
316
340
|
[demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/index.html
|
|
317
341
|
[css-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/scoped-css.html
|
|
342
|
+
[slice-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/data-slices.html
|
|
318
343
|
[hex-grid-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/hex-grid.html
|
|
319
344
|
[hex-grid-image]: demo/hex-grid-transform.png
|
|
320
345
|
[local-storage-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/local-storage.html
|
|
@@ -323,9 +348,9 @@ within template
|
|
|
323
348
|
[github-image]: https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg
|
|
324
349
|
[npm-image]: https://img.shields.io/npm/v/@epa-wg/custom-element.svg
|
|
325
350
|
[npm-url]: https://npmjs.org/package/@epa-wg/custom-element
|
|
326
|
-
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
327
|
-
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
328
|
-
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
351
|
+
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-test@0.0.20/coverage/coverage.svg
|
|
352
|
+
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.20/coverage/lcov-report/index.html
|
|
353
|
+
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.20/storybook-static/index.html?path=/story/welcome--introduction
|
|
329
354
|
[sandbox-url]: https://stackblitz.com/github/EPA-WG/custom-element?file=index.html
|
|
330
355
|
[webcomponents-url]: https://www.webcomponents.org/element/@epa-wg/custom-element
|
|
331
356
|
[webcomponents-img]: https://img.shields.io/badge/webcomponents.org-published-blue.svg
|
package/bin/xslDtd2Ide.mjs
CHANGED
|
@@ -116,7 +116,7 @@ writeFileSync( '.././ide/customData-xsl.json', JSON.stringify( vsCode, undefined
|
|
|
116
116
|
const intelliJ = {
|
|
117
117
|
"$schema": "http://json.schemastore.org/web-types",
|
|
118
118
|
"name": "@epa-wg/custom-element",
|
|
119
|
-
"version": "0.0.
|
|
119
|
+
"version": "0.0.20",
|
|
120
120
|
"js-types-syntax": "typescript",
|
|
121
121
|
"description-markup": "markdown",
|
|
122
122
|
"contributions": {
|
package/custom-element.js
CHANGED
|
@@ -7,9 +7,12 @@ const XSL_NS_URL = 'http://www.w3.org/1999/XSL/Transform'
|
|
|
7
7
|
|
|
8
8
|
const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
9
9
|
, isText = e => e.nodeType === 3
|
|
10
|
-
,
|
|
10
|
+
, isString = s => typeof s === 'string'
|
|
11
|
+
, isNode = e => e && typeof e.nodeType === 'number'
|
|
12
|
+
, create = ( tag, t = '', d=document ) => ( e => ((t && e.append(createText(d.ownerDocument||d, t))),e) )((d.ownerDocument || d ).createElement( tag ))
|
|
11
13
|
, createText = ( d, t) => (d.ownerDocument || d ).createTextNode( t )
|
|
12
|
-
,
|
|
14
|
+
, removeChildren = n => { while(n.firstChild) n.firstChild.remove(); return n; }
|
|
15
|
+
, emptyNode = n => { n.getAttributeNames().map( a => n.removeAttribute(a) ); return removeChildren(n); }
|
|
13
16
|
, createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, tag ))
|
|
14
17
|
, xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
|
|
15
18
|
, xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) )
|
|
@@ -90,6 +93,32 @@ Json2Xml( o, tag )
|
|
|
90
93
|
ret.push("/>");
|
|
91
94
|
return ret.join('\n');
|
|
92
95
|
}
|
|
96
|
+
|
|
97
|
+
export function
|
|
98
|
+
obj2node( o, tag, doc )
|
|
99
|
+
{ const t = typeof o;
|
|
100
|
+
if( t === 'function'){debugger}
|
|
101
|
+
if( t === 'string' )
|
|
102
|
+
return create(tag,o,doc);
|
|
103
|
+
if( t === 'number' )
|
|
104
|
+
return create(tag,''+o,doc);
|
|
105
|
+
|
|
106
|
+
if( o instanceof Array )
|
|
107
|
+
{ const ret = create('array');
|
|
108
|
+
o.map( ae => ret.append( obj2node(ae,tag,doc)) );
|
|
109
|
+
return ret
|
|
110
|
+
}
|
|
111
|
+
const ret = create(tag,'',doc);
|
|
112
|
+
for( let k in o )
|
|
113
|
+
if( isNode(o[k]) || typeof o[k] ==='function' || o[k] instanceof Window )
|
|
114
|
+
continue
|
|
115
|
+
else
|
|
116
|
+
if( typeof o[k] !== "object" )
|
|
117
|
+
ret.setAttribute(k, o[k] );
|
|
118
|
+
else
|
|
119
|
+
ret.append(obj2node(o[k], k, doc))
|
|
120
|
+
return ret;
|
|
121
|
+
}
|
|
93
122
|
export function
|
|
94
123
|
tagUid( node )
|
|
95
124
|
{ // {} to xsl:value-of
|
|
@@ -131,7 +160,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
131
160
|
{
|
|
132
161
|
if( templateNode.tagName === S || templateNode.documentElement?.tagName === S )
|
|
133
162
|
return tagUid(templateNode)
|
|
134
|
-
const sanitizeXsl = xml2dom(`<xsl:stylesheet version="1.0" xmlns:xsl="${ XSL_NS_URL }" xmlns:xhtml="${ HTML_NS_URL }" xmlns:exsl="${EXSL_NS_URL}" exclude-result-prefixes="exsl" >
|
|
163
|
+
const sanitizeXsl = xml2dom(`<xsl:stylesheet version="1.0" xmlns:xsl="${ XSL_NS_URL }" xmlns:xhtml="${ HTML_NS_URL }" xmlns:exsl="${EXSL_NS_URL}" exclude-result-prefixes="exsl" >
|
|
135
164
|
<xsl:output method="xml" />
|
|
136
165
|
<xsl:template match="/"><dce-root xmlns="${ HTML_NS_URL }"><xsl:apply-templates select="*"/></dce-root></xsl:template>
|
|
137
166
|
<xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
@@ -212,25 +241,38 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
212
241
|
if( !fr )
|
|
213
242
|
return console.error("transformation error",{ xml:tc.outerHTML, xsl: xmlString( sanitizeXsl ) });
|
|
214
243
|
const params = [];
|
|
215
|
-
[...fr.querySelectorAll('dce-root>attribute')].forEach(
|
|
216
|
-
{
|
|
244
|
+
[...fr.querySelectorAll('dce-root>attribute')].forEach( a=>
|
|
245
|
+
{
|
|
246
|
+
const p = cloneAs(a,'xsl:param')
|
|
247
|
+
, name = attr(a,'name');
|
|
217
248
|
payload.append(p);
|
|
218
249
|
let select = attr(p,'select')?.split('??')
|
|
219
250
|
if( !select)
|
|
220
|
-
{ select = ['//'+
|
|
251
|
+
{ select = ['//'+name, `'${p.textContent}'`];
|
|
221
252
|
emptyNode(p);
|
|
253
|
+
p.setAttribute('name',name);
|
|
222
254
|
}
|
|
255
|
+
let val;
|
|
223
256
|
if( select?.length>1 ){
|
|
224
257
|
p.removeAttribute('select');
|
|
225
258
|
const c = $( xslDom, 'template[match="ignore"]>choose').cloneNode(true);
|
|
226
|
-
c.firstElementChild.setAttribute('test',select[0]);
|
|
227
259
|
emptyNode(c.firstElementChild).append( createText(c,'{'+select[0]+'}'));
|
|
228
260
|
emptyNode(c.lastElementChild ).append( createText(c,'{'+select[1]+'}'));
|
|
229
|
-
|
|
230
|
-
|
|
261
|
+
c.firstElementChild.setAttribute('test',select[0]);
|
|
262
|
+
p.append(c);
|
|
263
|
+
val = c.cloneNode(true);
|
|
264
|
+
}else
|
|
265
|
+
val=cloneAs(a,'xsl:value-of');
|
|
266
|
+
val.removeAttribute('name');
|
|
267
|
+
a.append(val);
|
|
268
|
+
a.removeAttribute('select');
|
|
231
269
|
params.push(p)
|
|
232
270
|
});
|
|
233
|
-
|
|
271
|
+
[...fr.querySelectorAll('[value]')].filter(el=>el.getAttribute('value').match( /\{(.*)\?\?(.*)\}/g )).forEach(el=>
|
|
272
|
+
{ const v = attr(el,'value');
|
|
273
|
+
if(v)
|
|
274
|
+
el.setAttribute('value', evalCurly(v));
|
|
275
|
+
});
|
|
234
276
|
for( const c of fr.childNodes )
|
|
235
277
|
payload.append(xslDom.importNode(c,true))
|
|
236
278
|
|
|
@@ -287,18 +329,63 @@ deepEqual(a, b, O=false)
|
|
|
287
329
|
return O
|
|
288
330
|
return true;
|
|
289
331
|
}
|
|
290
|
-
|
|
332
|
+
export const
|
|
333
|
+
assureSlices = ( root, names) =>
|
|
334
|
+
names.split('|').map(n=>n.trim()).map( xp =>
|
|
335
|
+
{ if(xp.includes('/'))
|
|
336
|
+
{ const ret = [], r = root.ownerDocument.evaluate( xp, root );
|
|
337
|
+
for( let n; n = r.iterateNext(); )
|
|
338
|
+
ret.push( n )
|
|
339
|
+
return ret
|
|
340
|
+
}
|
|
341
|
+
return [...root.childNodes].find(n=>n.localName === xp) || create(xp);
|
|
342
|
+
}).flat();
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
*
|
|
346
|
+
* @param x slice node
|
|
347
|
+
* @param sliceNames slice name, xPath in /datadom/slice/
|
|
348
|
+
* @param ev Event obj
|
|
349
|
+
* @param dce
|
|
350
|
+
*/
|
|
291
351
|
export function
|
|
292
|
-
|
|
352
|
+
event2slice( x, sliceNames, ev, dce )
|
|
293
353
|
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
x.
|
|
354
|
+
// evaluate slices[]
|
|
355
|
+
// inject @attributes
|
|
356
|
+
// inject event
|
|
357
|
+
// evaluate slice-value
|
|
358
|
+
// slice[i] = slice-value
|
|
359
|
+
assureSlices(x,sliceNames).map( s =>
|
|
360
|
+
{
|
|
361
|
+
const d = x.ownerDocument
|
|
362
|
+
, el = ev.sliceEventSource
|
|
363
|
+
, sel = ev.sliceElement
|
|
364
|
+
, cleanSliceValue = ()=>[...s.childNodes].filter(n=>n.nodeType===3 || n.localName==='value').map(n=>n.remove());
|
|
365
|
+
el.getAttributeNames().map( a => s.setAttribute( a, attr(el,a) ) );
|
|
366
|
+
[...s.childNodes].filter(n=>n.localName==='event').map(n=>n.remove());
|
|
367
|
+
ev.type==='init' && cleanSliceValue();
|
|
368
|
+
s.append( obj2node( ev, 'event', d ) );
|
|
369
|
+
if( sel.hasAttribute('slice-value') )
|
|
370
|
+
{ if( el.value === undefined)
|
|
371
|
+
s.removeAttribute('value')
|
|
372
|
+
else
|
|
373
|
+
s.setAttribute('value', el.value );
|
|
374
|
+
const v = xPath( attr( sel, 'slice-value'),s );
|
|
375
|
+
cleanSliceValue();
|
|
376
|
+
s.append( createText( d, v ) );
|
|
377
|
+
}else
|
|
378
|
+
{ const v = el.value ?? attr( sel, 'value' ) ;
|
|
379
|
+
cleanSliceValue();
|
|
380
|
+
if( v === null || v === undefined )
|
|
381
|
+
[...s.childNodes].filter(n=>n.localName!=='event').map(n=>n.remove());
|
|
382
|
+
else
|
|
383
|
+
if( isString(v) )
|
|
384
|
+
s.append( createText( d, v) );
|
|
385
|
+
else
|
|
386
|
+
s.append( obj2node(v,'value',s.ownerDocument) )
|
|
387
|
+
}
|
|
388
|
+
})
|
|
302
389
|
}
|
|
303
390
|
|
|
304
391
|
function forEach$( el, css, cb){
|
|
@@ -339,7 +426,10 @@ export function mergeAttr( from, to )
|
|
|
339
426
|
return
|
|
340
427
|
}
|
|
341
428
|
for( let a of from.attributes)
|
|
342
|
-
|
|
429
|
+
{ a.namespaceURI? to.setAttributeNS( a.namespaceURI, a.name, a.value ) : to.setAttribute( a.name, a.value )
|
|
430
|
+
if( a.name === 'value')
|
|
431
|
+
to.value = a.value
|
|
432
|
+
}
|
|
343
433
|
}
|
|
344
434
|
export function assureUnique(n, id=0)
|
|
345
435
|
{
|
|
@@ -364,6 +454,8 @@ export function assureUnique(n, id=0)
|
|
|
364
454
|
}
|
|
365
455
|
export function merge( parent, fromArr )
|
|
366
456
|
{
|
|
457
|
+
if(!fromArr.length)
|
|
458
|
+
return removeChildren(parent);
|
|
367
459
|
const id2old = {};
|
|
368
460
|
for( let c of parent.childNodes)
|
|
369
461
|
{ ASSERT( !id2old[c.dceId] );
|
|
@@ -374,26 +466,57 @@ export function merge( parent, fromArr )
|
|
|
374
466
|
id2old[attr(c, 'data-dce-id') || 0] = c;
|
|
375
467
|
}
|
|
376
468
|
for( let e of [...fromArr] )
|
|
377
|
-
{
|
|
378
|
-
const o = id2old[
|
|
469
|
+
{ const k = attr(e, 'data-dce-id') || e.dceId;
|
|
470
|
+
const o = id2old[ k ];
|
|
379
471
|
if( o )
|
|
380
472
|
{ if( isText(e) )
|
|
381
473
|
{ if( o.nodeValue !== e.nodeValue )
|
|
382
474
|
o.nodeValue = e.nodeValue;
|
|
383
475
|
}else
|
|
384
|
-
{ mergeAttr(o
|
|
476
|
+
{ mergeAttr(e,o)
|
|
385
477
|
if( o.childNodes.length || e.childNodes.length )
|
|
386
478
|
merge(o, e.childNodes)
|
|
387
479
|
}
|
|
480
|
+
delete id2old[ k ]
|
|
388
481
|
}else
|
|
389
482
|
parent.append( e )
|
|
390
483
|
}
|
|
484
|
+
for( let v of Object.values(id2old) )
|
|
485
|
+
v.remove();
|
|
391
486
|
}
|
|
392
487
|
export function assureUID(n,attr)
|
|
393
488
|
{ if( !n.hasAttribute(attr) )
|
|
394
489
|
n.setAttribute(attr, crypto.randomUUID());
|
|
395
490
|
return n.getAttribute(attr)
|
|
396
491
|
}
|
|
492
|
+
export const evalCurly = s =>
|
|
493
|
+
{ const exp = [...s?.matchAll( /([^{}]*)(\{)([^}]+)}([^{}]*)/g ) ].map(l=>`${l[1]}{${ xPathDefaults(l[3] )}}${l[4]}`);
|
|
494
|
+
return exp.join('');
|
|
495
|
+
}
|
|
496
|
+
export const xPathDefaults = x=>
|
|
497
|
+
{ if(!x.trim())
|
|
498
|
+
return x;
|
|
499
|
+
const xx = x.split('??')
|
|
500
|
+
, a = xx.shift()
|
|
501
|
+
, b = xPathDefaults(xx.join('??'));
|
|
502
|
+
|
|
503
|
+
return xx.length ? `concat( ${a} , substring( ${b} , (1+string-length( ${b} )) * string-length( ${a} ) ) )`: x
|
|
504
|
+
// return xx.length ? `${a}|(${xPathDefaults(xx.join('??'))})[not(${a})]`: a
|
|
505
|
+
}
|
|
506
|
+
export const xPath = (x,root)=>
|
|
507
|
+
{ x = xPathDefaults(x);
|
|
508
|
+
|
|
509
|
+
const it = root.ownerDocument.evaluate(x, root);
|
|
510
|
+
switch( it.resultType )
|
|
511
|
+
{ case XPathResult.NUMBER_TYPE: return it.numberValue;
|
|
512
|
+
case XPathResult.STRING_TYPE: return it.stringValue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let ret = '';
|
|
516
|
+
for( let n ;n=it.iterateNext(); )
|
|
517
|
+
ret += n.textContent;
|
|
518
|
+
return ret
|
|
519
|
+
}
|
|
397
520
|
export const xslTags = 'stylesheet,transform,import,include,strip-space,preserve-space,output,key,decimal-format,namespace-alias,template,value-of,copy-of,number,apply-templates,apply-imports,for-each,sort,if,choose,when,otherwise,attribute-set,call-template,with-param,variable,param,text,processing-instruction,element,attribute,comment,copy,message,fallback'.split(',');
|
|
398
521
|
export const toXsl = (el, defParent) => {
|
|
399
522
|
const x = create('xsl:'+el.localName);
|
|
@@ -436,19 +559,23 @@ CustomElement extends HTMLElement
|
|
|
436
559
|
|
|
437
560
|
Object.defineProperty( this, "xsltString", { get: ()=>templateDocs.map( td => xmlString(td) ).join('\n') });
|
|
438
561
|
|
|
439
|
-
const dce = this
|
|
440
|
-
|
|
562
|
+
const dce = this
|
|
563
|
+
, sliceNodes = [...this.templateNode.querySelectorAll('[slice]')]
|
|
564
|
+
, sliceNames = sliceNodes.map(e=>attr(e,'slice')).filter(n=>!n.includes('/')).filter((v, i, a)=>a.indexOf(v) === i)
|
|
565
|
+
, declaredAttributes = templateDocs.reduce( (ret,t) => { if( t.params ) ret.push( ...t.params ); return ret; }, [] );
|
|
566
|
+
|
|
441
567
|
class DceElement extends HTMLElement
|
|
442
568
|
{
|
|
443
|
-
static get observedAttributes()
|
|
444
|
-
|
|
445
|
-
{ if( t.params ) ret.push( ...t.params.map(e=>attr(e,'name')) );
|
|
446
|
-
return ret;
|
|
447
|
-
}, [] );
|
|
448
|
-
}
|
|
569
|
+
static get observedAttributes(){ return declaredAttributes.map( a=>attr(a,'name')); }
|
|
570
|
+
#inTransform = 0;
|
|
449
571
|
connectedCallback()
|
|
450
|
-
{
|
|
451
|
-
|
|
572
|
+
{ let payload = this.childNodes;
|
|
573
|
+
if( this.firstElementChild?.tagName === 'TEMPLATE' )
|
|
574
|
+
{
|
|
575
|
+
const t = this.firstElementChild;
|
|
576
|
+
t.remove();
|
|
577
|
+
payload = t.content.childNodes;
|
|
578
|
+
|
|
452
579
|
for( const n of [...t.content.childNodes] )
|
|
453
580
|
if( n.localName === 'style' ){
|
|
454
581
|
const id = assureUID(this,'data-dce-style')
|
|
@@ -459,9 +586,6 @@ CustomElement extends HTMLElement
|
|
|
459
586
|
t.insertAdjacentElement('beforebegin',n);
|
|
460
587
|
else if(n.nodeType===3)
|
|
461
588
|
t.insertAdjacentText('beforebegin',n.data);
|
|
462
|
-
|
|
463
|
-
t.remove();
|
|
464
|
-
|
|
465
589
|
}
|
|
466
590
|
const x = xml2dom( '<datadom/>' ).documentElement;
|
|
467
591
|
const createXmlNode = ( tag, t = '' ) => ( e =>
|
|
@@ -469,11 +593,12 @@ CustomElement extends HTMLElement
|
|
|
469
593
|
e.append( createText( x, t ))
|
|
470
594
|
return e;
|
|
471
595
|
})(x.ownerDocument.createElement( tag ))
|
|
472
|
-
injectData( x, 'payload' ,
|
|
596
|
+
injectData( x, 'payload' , payload , assureSlot );
|
|
473
597
|
this.innerHTML='';
|
|
474
598
|
injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
475
599
|
injectData( x, 'dataset', Object.keys( this.dataset ), k => createXmlNode( k, this.dataset[ k ] ) );
|
|
476
|
-
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) )
|
|
600
|
+
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) )
|
|
601
|
+
, sliceXPath = x => xPath(x, sliceRoot);
|
|
477
602
|
this.xml = x;
|
|
478
603
|
|
|
479
604
|
const sliceEvents=[];
|
|
@@ -481,10 +606,10 @@ CustomElement extends HTMLElement
|
|
|
481
606
|
{ const processed = {}
|
|
482
607
|
|
|
483
608
|
for(let ev; ev = sliceEvents.pop(); )
|
|
484
|
-
{ const s = attr( ev.
|
|
609
|
+
{ const s = attr( ev.sliceElement, 'slice');
|
|
485
610
|
if( processed[s] )
|
|
486
611
|
continue;
|
|
487
|
-
|
|
612
|
+
event2slice( sliceRoot, s, ev, this );
|
|
488
613
|
processed[s] = ev;
|
|
489
614
|
}
|
|
490
615
|
Object.keys(processed).length !== 0 && transform();
|
|
@@ -493,10 +618,7 @@ CustomElement extends HTMLElement
|
|
|
493
618
|
|
|
494
619
|
this.onSlice = ev=>
|
|
495
620
|
{ ev.stopPropagation?.();
|
|
496
|
-
|
|
497
|
-
if( deepEqual( ev.detail, [...sliceRoot.children].find( e=>e.localName === s )?.data ) )
|
|
498
|
-
return
|
|
499
|
-
|
|
621
|
+
ev.sliceEventSource = ev.currentTarget || ev.target;
|
|
500
622
|
sliceEvents.push(ev);
|
|
501
623
|
if( !timeoutID )
|
|
502
624
|
timeoutID = setTimeout(()=>
|
|
@@ -505,7 +627,9 @@ CustomElement extends HTMLElement
|
|
|
505
627
|
},10);
|
|
506
628
|
};
|
|
507
629
|
const transform = this.transform = ()=>
|
|
508
|
-
{
|
|
630
|
+
{ if(this.#inTransform){ debugger }
|
|
631
|
+
this.#inTransform = 1;
|
|
632
|
+
|
|
509
633
|
const ff = xp.map( (p,i) =>
|
|
510
634
|
{ const f = p.transformToFragment(x.ownerDocument, document)
|
|
511
635
|
if( !f )
|
|
@@ -518,35 +642,55 @@ CustomElement extends HTMLElement
|
|
|
518
642
|
assureUnique(f);
|
|
519
643
|
merge( this, f.childNodes )
|
|
520
644
|
})
|
|
521
|
-
|
|
522
|
-
|
|
645
|
+
|
|
646
|
+
DceElement.observedAttributes.map( a =>
|
|
647
|
+
{ let v = attr(this.firstElementChild,a);
|
|
648
|
+
if( v !== attr(this,a) )
|
|
649
|
+
{ this.setAttribute( a, v );
|
|
650
|
+
this.#applyAttribute( a, v );
|
|
651
|
+
}
|
|
652
|
+
})
|
|
523
653
|
|
|
524
654
|
forEach$( this,'[slice]', el =>
|
|
525
655
|
{ if( !el.dceInitialized )
|
|
526
656
|
{ el.dceInitialized = 1;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
657
|
+
const evs = attr(el,'slice-event');
|
|
658
|
+
(evs || 'change')
|
|
659
|
+
.split(' ')
|
|
660
|
+
.forEach( t=> (el.localName==='slice'? el.parentElement : el)
|
|
661
|
+
.addEventListener( t, ev=>
|
|
662
|
+
{ ev.sliceElement = el;
|
|
663
|
+
this.onSlice(ev)
|
|
664
|
+
} ));
|
|
665
|
+
if( !evs || evs.includes('init') )
|
|
666
|
+
{ if( el.hasAttribute('slice-value') || el.hasAttribute('value') || el.value )
|
|
667
|
+
this.onSlice({type:'init', target: el, sliceElement:el })
|
|
668
|
+
else
|
|
669
|
+
el.value = sliceXPath( attr(el,'slice') )
|
|
670
|
+
}
|
|
530
671
|
}
|
|
531
|
-
})
|
|
672
|
+
});
|
|
673
|
+
this.#inTransform = 0;
|
|
532
674
|
};
|
|
533
675
|
transform();
|
|
534
676
|
applySlices();
|
|
535
677
|
}
|
|
536
|
-
|
|
537
|
-
{
|
|
538
|
-
return;
|
|
539
|
-
let a = this.xml.querySelector(`attributes>${name}`);
|
|
678
|
+
#applyAttribute(name, newValue)
|
|
679
|
+
{ let a = this.xml.querySelector(`attributes>${name}`);
|
|
540
680
|
if( a )
|
|
541
|
-
emptyNode(a).append( createText(a,newValue));
|
|
681
|
+
emptyNode(a).append( createText(a,newValue) );
|
|
542
682
|
else
|
|
543
683
|
{ a = create( name, newValue, this.xml );
|
|
544
|
-
a.append( createText(a,newValue) );
|
|
545
684
|
this.xml.querySelector('attributes').append( a );
|
|
546
685
|
}
|
|
547
|
-
|
|
686
|
+
}
|
|
687
|
+
attributeChangedCallback(name, oldValue, newValue)
|
|
688
|
+
{ if( !this.xml || this.#inTransform )
|
|
689
|
+
return;
|
|
690
|
+
this.#applyAttribute(name, newValue);
|
|
548
691
|
this.transform(); // needs throttling
|
|
549
692
|
}
|
|
693
|
+
|
|
550
694
|
get dce(){ return dce }
|
|
551
695
|
}
|
|
552
696
|
if(tag)
|
package/demo/a.html
CHANGED
|
@@ -4,30 +4,70 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
6
6
|
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
7
|
-
<link rel="icon" href="./wc-square.svg"
|
|
8
|
-
<script type="module" src="../
|
|
7
|
+
<link rel="icon" href="./wc-square.svg"/>
|
|
8
|
+
<script type="module" src="../http-request.js"></script>
|
|
9
|
+
<script type="module" src="../local-storage.js"></script>
|
|
9
10
|
<script type="module" src="../custom-element.js"></script>
|
|
10
11
|
|
|
11
12
|
<style>
|
|
12
13
|
@import "./demo.css";
|
|
13
14
|
|
|
14
|
-
button{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
button {
|
|
16
|
+
background: forestgreen;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
table {
|
|
20
|
+
min-width: 16rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
td {
|
|
24
|
+
border-bottom: 1px solid silver;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
tfoot td {
|
|
28
|
+
border-bottom: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
td, th {
|
|
32
|
+
text-align: right;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
caption {
|
|
36
|
+
padding: 1rem;
|
|
37
|
+
font-weight: bolder;
|
|
38
|
+
font-family: sans-serif;
|
|
39
|
+
}
|
|
20
40
|
</style>
|
|
21
41
|
</head>
|
|
22
42
|
<body>
|
|
23
43
|
|
|
24
|
-
<custom-element
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<
|
|
44
|
+
<custom-element>
|
|
45
|
+
<template>
|
|
46
|
+
<button slice="url-string" slice-value="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click"
|
|
47
|
+
>⬇️https://pokeapi.co/api/v2/pokemon?limit=6</button>
|
|
48
|
+
<input slice="url-string" value="{ //url-string ?? '' }" style="width:100%"/>
|
|
49
|
+
<button slice="fetch-url" slice-event="click" slice-value="//url-string"> GET </button>
|
|
50
|
+
<http-request
|
|
51
|
+
url="{//fetch-url}"
|
|
52
|
+
slice="request_slice"
|
|
53
|
+
type="text"
|
|
54
|
+
mode="cors"
|
|
55
|
+
></http-request>
|
|
56
|
+
//fetch-url : <code>{//fetch-url}</code>
|
|
57
|
+
<!-- <for-each select="//slice/request_slice/value/*">-->
|
|
58
|
+
<!-- <ul>-->
|
|
59
|
+
<!-- <var data-testid="request-section"><value-of select='name(.)'/></var>-->
|
|
60
|
+
<!-- <for-each select="@*">-->
|
|
61
|
+
<!-- <div>-->
|
|
62
|
+
<!-- <var data-testid="section-attribute">@<value-of select='local-name(.)'/></var>-->
|
|
63
|
+
<!-- =-->
|
|
64
|
+
<!-- <code><value-of select='.'/></code>-->
|
|
65
|
+
<!-- </div>-->
|
|
66
|
+
<!-- </for-each>-->
|
|
67
|
+
<!-- </ul>-->
|
|
68
|
+
<!-- </for-each>-->
|
|
69
|
+
</template>
|
|
70
|
+
</custom-element>
|
|
31
71
|
|
|
32
72
|
</body>
|
|
33
73
|
</html>
|