@epa-wg/custom-element 0.0.18 → 0.0.19
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 +187 -57
- package/demo/a.html +43 -15
- package/demo/data-slices.html +185 -0
- package/demo/demo.css +1 -0
- package/demo/dom-merge.html +1 -1
- package/demo/external-template.html +1 -0
- package/demo/http-request.html +12 -10
- package/demo/local-storage.html +2 -2
- package/demo/location-element.html +14 -9
- package/demo/parameters.html +18 -0
- package/demo/s.xml +6 -1
- package/demo/s.xslt +9 -9
- package/demo/z.html +62 -48
- package/http-request.js +2 -1
- 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/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.19/coverage/coverage.svg
|
|
352
|
+
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.19/coverage/lcov-report/index.html
|
|
353
|
+
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.19/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.19",
|
|
120
120
|
"js-types-syntax": "typescript",
|
|
121
121
|
"description-markup": "markdown",
|
|
122
122
|
"contributions": {
|
package/custom-element.js
CHANGED
|
@@ -7,9 +7,11 @@ 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
|
-
, emptyNode = n=> { while(n.firstChild) n.firstChild.remove(); return n; }
|
|
14
|
+
, emptyNode = n => { while(n.firstChild) n.firstChild.remove(); n.getAttributeNames().map( a => n.removeAttribute(a) ); return n; }
|
|
13
15
|
, createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, tag ))
|
|
14
16
|
, xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
|
|
15
17
|
, xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) )
|
|
@@ -90,6 +92,30 @@ Json2Xml( o, tag )
|
|
|
90
92
|
ret.push("/>");
|
|
91
93
|
return ret.join('\n');
|
|
92
94
|
}
|
|
95
|
+
|
|
96
|
+
export function
|
|
97
|
+
obj2node( o, tag, doc )
|
|
98
|
+
{
|
|
99
|
+
if( typeof o === 'function'){debugger}
|
|
100
|
+
if( typeof o === 'string' )
|
|
101
|
+
return create(tag,o,doc);
|
|
102
|
+
|
|
103
|
+
if( o instanceof Array )
|
|
104
|
+
{ const ret = create('array');
|
|
105
|
+
o.map( ae => ret.append( obj2node(ae,tag,doc)) );
|
|
106
|
+
return ret
|
|
107
|
+
}
|
|
108
|
+
const ret = create(tag,'',doc);
|
|
109
|
+
for( let k in o )
|
|
110
|
+
if( isNode(o[k]) || typeof o[k] ==='function' || o[k] instanceof Window )
|
|
111
|
+
continue
|
|
112
|
+
else
|
|
113
|
+
if( typeof o[k] !== "object" )
|
|
114
|
+
ret.setAttribute(k, o[k] );
|
|
115
|
+
else
|
|
116
|
+
ret.append(obj2node(o[k], k, doc))
|
|
117
|
+
return ret;
|
|
118
|
+
}
|
|
93
119
|
export function
|
|
94
120
|
tagUid( node )
|
|
95
121
|
{ // {} to xsl:value-of
|
|
@@ -131,7 +157,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
131
157
|
{
|
|
132
158
|
if( templateNode.tagName === S || templateNode.documentElement?.tagName === S )
|
|
133
159
|
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" >
|
|
160
|
+
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
161
|
<xsl:output method="xml" />
|
|
136
162
|
<xsl:template match="/"><dce-root xmlns="${ HTML_NS_URL }"><xsl:apply-templates select="*"/></dce-root></xsl:template>
|
|
137
163
|
<xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
@@ -212,25 +238,38 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
212
238
|
if( !fr )
|
|
213
239
|
return console.error("transformation error",{ xml:tc.outerHTML, xsl: xmlString( sanitizeXsl ) });
|
|
214
240
|
const params = [];
|
|
215
|
-
[...fr.querySelectorAll('dce-root>attribute')].forEach(
|
|
216
|
-
{
|
|
241
|
+
[...fr.querySelectorAll('dce-root>attribute')].forEach( a=>
|
|
242
|
+
{
|
|
243
|
+
const p = cloneAs(a,'xsl:param')
|
|
244
|
+
, name = attr(a,'name');
|
|
217
245
|
payload.append(p);
|
|
218
246
|
let select = attr(p,'select')?.split('??')
|
|
219
247
|
if( !select)
|
|
220
|
-
{ select = ['//'+
|
|
248
|
+
{ select = ['//'+name, `'${p.textContent}'`];
|
|
221
249
|
emptyNode(p);
|
|
250
|
+
p.setAttribute('name',name);
|
|
222
251
|
}
|
|
252
|
+
let val;
|
|
223
253
|
if( select?.length>1 ){
|
|
224
254
|
p.removeAttribute('select');
|
|
225
255
|
const c = $( xslDom, 'template[match="ignore"]>choose').cloneNode(true);
|
|
226
|
-
c.firstElementChild.setAttribute('test',select[0]);
|
|
227
256
|
emptyNode(c.firstElementChild).append( createText(c,'{'+select[0]+'}'));
|
|
228
257
|
emptyNode(c.lastElementChild ).append( createText(c,'{'+select[1]+'}'));
|
|
229
|
-
|
|
230
|
-
|
|
258
|
+
c.firstElementChild.setAttribute('test',select[0]);
|
|
259
|
+
p.append(c);
|
|
260
|
+
val = c.cloneNode(true);
|
|
261
|
+
}else
|
|
262
|
+
val=cloneAs(a,'xsl:value-of');
|
|
263
|
+
val.removeAttribute('name');
|
|
264
|
+
a.append(val);
|
|
265
|
+
a.removeAttribute('select');
|
|
231
266
|
params.push(p)
|
|
232
267
|
});
|
|
233
|
-
|
|
268
|
+
[...fr.querySelectorAll('[value]')].filter(el=>el.getAttribute('value').match( /\{(.*)\?\?(.*)\}/g )).forEach(el=>
|
|
269
|
+
{ const v = attr(el,'value');
|
|
270
|
+
if(v)
|
|
271
|
+
el.setAttribute('value', evalCurly(v));
|
|
272
|
+
});
|
|
234
273
|
for( const c of fr.childNodes )
|
|
235
274
|
payload.append(xslDom.importNode(c,true))
|
|
236
275
|
|
|
@@ -287,18 +326,57 @@ deepEqual(a, b, O=false)
|
|
|
287
326
|
return O
|
|
288
327
|
return true;
|
|
289
328
|
}
|
|
290
|
-
|
|
329
|
+
export const
|
|
330
|
+
assureSlices = ( root, names) =>
|
|
331
|
+
names.split('|').map(n=>n.trim()).map( xp =>
|
|
332
|
+
{ if(xp.includes('/'))
|
|
333
|
+
{ const ret = [], r = root.ownerDocument.evaluate( xp, root );
|
|
334
|
+
for( let n; n = r.iterateNext(); )
|
|
335
|
+
ret.push( n )
|
|
336
|
+
return ret
|
|
337
|
+
}
|
|
338
|
+
return [...root.childNodes].find(n=>n.localName === xp) || create(xp);
|
|
339
|
+
}).flat();
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
*
|
|
343
|
+
* @param x slice node
|
|
344
|
+
* @param sliceNames slice name, xPath in /datadom/slice/
|
|
345
|
+
* @param ev Event obj
|
|
346
|
+
* @param dce
|
|
347
|
+
*/
|
|
291
348
|
export function
|
|
292
|
-
|
|
349
|
+
event2slice( x, sliceNames, ev, dce )
|
|
293
350
|
{
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
x.
|
|
351
|
+
// evaluate slices[]
|
|
352
|
+
// inject @attributes
|
|
353
|
+
// inject event
|
|
354
|
+
// evaluate slice-value
|
|
355
|
+
// slice[i] = slice-value
|
|
356
|
+
assureSlices(x,sliceNames).map( s =>
|
|
357
|
+
{
|
|
358
|
+
const d = x.ownerDocument
|
|
359
|
+
, el = ev.sliceEventSource
|
|
360
|
+
, sel = ev.sliceElement
|
|
361
|
+
, cleanSliceValue = ()=>[...s.childNodes].filter(n=>n.nodeType===3 || n.localName==='value').map(n=>n.remove());
|
|
362
|
+
el.getAttributeNames().map( a => s.setAttribute( a, attr(el,a) ) );
|
|
363
|
+
[...s.childNodes].filter(n=>n.localName==='event').map(n=>n.remove());
|
|
364
|
+
ev.type==='init' && cleanSliceValue();
|
|
365
|
+
s.append( obj2node( ev, 'event', d ) );
|
|
366
|
+
if( sel.hasAttribute('slice-value') )
|
|
367
|
+
{ s.setAttribute('value', el.value );
|
|
368
|
+
const v = xPath( attr( sel, 'slice-value'),s );
|
|
369
|
+
cleanSliceValue();
|
|
370
|
+
s.append( createText( d, v ) );
|
|
371
|
+
}else
|
|
372
|
+
{ const v = el.value || attr( sel, 'value' ) ;
|
|
373
|
+
cleanSliceValue();
|
|
374
|
+
if( isString(v) )
|
|
375
|
+
s.append( createText( d, v) );
|
|
376
|
+
else
|
|
377
|
+
s.append( obj2node(v,'value',s.ownerDocument) )
|
|
378
|
+
}
|
|
379
|
+
})
|
|
302
380
|
}
|
|
303
381
|
|
|
304
382
|
function forEach$( el, css, cb){
|
|
@@ -339,7 +417,10 @@ export function mergeAttr( from, to )
|
|
|
339
417
|
return
|
|
340
418
|
}
|
|
341
419
|
for( let a of from.attributes)
|
|
342
|
-
|
|
420
|
+
{ a.namespaceURI? to.setAttributeNS( a.namespaceURI, a.name, a.value ) : to.setAttribute( a.name, a.value )
|
|
421
|
+
if( a.name === 'value')
|
|
422
|
+
to.value = a.value
|
|
423
|
+
}
|
|
343
424
|
}
|
|
344
425
|
export function assureUnique(n, id=0)
|
|
345
426
|
{
|
|
@@ -381,7 +462,7 @@ export function merge( parent, fromArr )
|
|
|
381
462
|
{ if( o.nodeValue !== e.nodeValue )
|
|
382
463
|
o.nodeValue = e.nodeValue;
|
|
383
464
|
}else
|
|
384
|
-
{ mergeAttr(o
|
|
465
|
+
{ mergeAttr(e,o)
|
|
385
466
|
if( o.childNodes.length || e.childNodes.length )
|
|
386
467
|
merge(o, e.childNodes)
|
|
387
468
|
}
|
|
@@ -394,6 +475,34 @@ export function assureUID(n,attr)
|
|
|
394
475
|
n.setAttribute(attr, crypto.randomUUID());
|
|
395
476
|
return n.getAttribute(attr)
|
|
396
477
|
}
|
|
478
|
+
export const evalCurly = s =>
|
|
479
|
+
{ const exp = [...s?.matchAll( /([^{}]*)(\{)([^}]+)}([^{}]*)/g ) ].map(l=>`${l[1]}{${ xPathDefaults(l[3] )}}${l[4]}`);
|
|
480
|
+
return exp.join('');
|
|
481
|
+
}
|
|
482
|
+
export const xPathDefaults = x=>
|
|
483
|
+
{ if(!x.trim())
|
|
484
|
+
return x;
|
|
485
|
+
const xx = x.split('??')
|
|
486
|
+
, a = xx.shift()
|
|
487
|
+
, b = xPathDefaults(xx.join('??'));
|
|
488
|
+
|
|
489
|
+
return xx.length ? `concat( ${a} , substring( ${b} , (1+string-length( ${b} )) * string-length( ${a} ) ) )`: x
|
|
490
|
+
// return xx.length ? `${a}|(${xPathDefaults(xx.join('??'))})[not(${a})]`: a
|
|
491
|
+
}
|
|
492
|
+
export const xPath = (x,root)=>
|
|
493
|
+
{ x = xPathDefaults(x);
|
|
494
|
+
|
|
495
|
+
const it = root.ownerDocument.evaluate(x, root);
|
|
496
|
+
switch( it.resultType )
|
|
497
|
+
{ case XPathResult.NUMBER_TYPE: return it.numberValue;
|
|
498
|
+
case XPathResult.STRING_TYPE: return it.stringValue;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
let ret = '';
|
|
502
|
+
for( let n ;n=it.iterateNext(); )
|
|
503
|
+
ret += n.textContent;
|
|
504
|
+
return ret
|
|
505
|
+
}
|
|
397
506
|
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
507
|
export const toXsl = (el, defParent) => {
|
|
399
508
|
const x = create('xsl:'+el.localName);
|
|
@@ -436,19 +545,23 @@ CustomElement extends HTMLElement
|
|
|
436
545
|
|
|
437
546
|
Object.defineProperty( this, "xsltString", { get: ()=>templateDocs.map( td => xmlString(td) ).join('\n') });
|
|
438
547
|
|
|
439
|
-
const dce = this
|
|
440
|
-
|
|
548
|
+
const dce = this
|
|
549
|
+
, sliceNodes = [...this.templateNode.querySelectorAll('[slice]')]
|
|
550
|
+
, sliceNames = sliceNodes.map(e=>attr(e,'slice')).filter(n=>!n.includes('/')).filter((v, i, a)=>a.indexOf(v) === i)
|
|
551
|
+
, declaredAttributes = templateDocs.reduce( (ret,t) => { if( t.params ) ret.push( ...t.params ); return ret; }, [] );
|
|
552
|
+
|
|
441
553
|
class DceElement extends HTMLElement
|
|
442
554
|
{
|
|
443
|
-
static get observedAttributes()
|
|
444
|
-
|
|
445
|
-
{ if( t.params ) ret.push( ...t.params.map(e=>attr(e,'name')) );
|
|
446
|
-
return ret;
|
|
447
|
-
}, [] );
|
|
448
|
-
}
|
|
555
|
+
static get observedAttributes(){ return declaredAttributes.map( a=>attr(a,'name')); }
|
|
556
|
+
#inTransform = 0;
|
|
449
557
|
connectedCallback()
|
|
450
|
-
{
|
|
451
|
-
|
|
558
|
+
{ let payload = this.childNodes;
|
|
559
|
+
if( this.firstElementChild?.tagName === 'TEMPLATE' )
|
|
560
|
+
{
|
|
561
|
+
const t = this.firstElementChild;
|
|
562
|
+
t.remove();
|
|
563
|
+
payload = t.content.childNodes;
|
|
564
|
+
|
|
452
565
|
for( const n of [...t.content.childNodes] )
|
|
453
566
|
if( n.localName === 'style' ){
|
|
454
567
|
const id = assureUID(this,'data-dce-style')
|
|
@@ -459,9 +572,6 @@ CustomElement extends HTMLElement
|
|
|
459
572
|
t.insertAdjacentElement('beforebegin',n);
|
|
460
573
|
else if(n.nodeType===3)
|
|
461
574
|
t.insertAdjacentText('beforebegin',n.data);
|
|
462
|
-
|
|
463
|
-
t.remove();
|
|
464
|
-
|
|
465
575
|
}
|
|
466
576
|
const x = xml2dom( '<datadom/>' ).documentElement;
|
|
467
577
|
const createXmlNode = ( tag, t = '' ) => ( e =>
|
|
@@ -469,11 +579,12 @@ CustomElement extends HTMLElement
|
|
|
469
579
|
e.append( createText( x, t ))
|
|
470
580
|
return e;
|
|
471
581
|
})(x.ownerDocument.createElement( tag ))
|
|
472
|
-
injectData( x, 'payload' ,
|
|
582
|
+
injectData( x, 'payload' , payload , assureSlot );
|
|
473
583
|
this.innerHTML='';
|
|
474
584
|
injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
475
585
|
injectData( x, 'dataset', Object.keys( this.dataset ), k => createXmlNode( k, this.dataset[ k ] ) );
|
|
476
|
-
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) )
|
|
586
|
+
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) )
|
|
587
|
+
, sliceXPath = x => xPath(x, sliceRoot);
|
|
477
588
|
this.xml = x;
|
|
478
589
|
|
|
479
590
|
const sliceEvents=[];
|
|
@@ -481,10 +592,10 @@ CustomElement extends HTMLElement
|
|
|
481
592
|
{ const processed = {}
|
|
482
593
|
|
|
483
594
|
for(let ev; ev = sliceEvents.pop(); )
|
|
484
|
-
{ const s = attr( ev.
|
|
595
|
+
{ const s = attr( ev.sliceElement, 'slice');
|
|
485
596
|
if( processed[s] )
|
|
486
597
|
continue;
|
|
487
|
-
|
|
598
|
+
event2slice( sliceRoot, s, ev, this );
|
|
488
599
|
processed[s] = ev;
|
|
489
600
|
}
|
|
490
601
|
Object.keys(processed).length !== 0 && transform();
|
|
@@ -493,10 +604,7 @@ CustomElement extends HTMLElement
|
|
|
493
604
|
|
|
494
605
|
this.onSlice = ev=>
|
|
495
606
|
{ ev.stopPropagation?.();
|
|
496
|
-
|
|
497
|
-
if( deepEqual( ev.detail, [...sliceRoot.children].find( e=>e.localName === s )?.data ) )
|
|
498
|
-
return
|
|
499
|
-
|
|
607
|
+
ev.sliceEventSource = ev.currentTarget || ev.target;
|
|
500
608
|
sliceEvents.push(ev);
|
|
501
609
|
if( !timeoutID )
|
|
502
610
|
timeoutID = setTimeout(()=>
|
|
@@ -505,7 +613,9 @@ CustomElement extends HTMLElement
|
|
|
505
613
|
},10);
|
|
506
614
|
};
|
|
507
615
|
const transform = this.transform = ()=>
|
|
508
|
-
{
|
|
616
|
+
{ if(this.#inTransform){ debugger }
|
|
617
|
+
this.#inTransform = 1;
|
|
618
|
+
|
|
509
619
|
const ff = xp.map( (p,i) =>
|
|
510
620
|
{ const f = p.transformToFragment(x.ownerDocument, document)
|
|
511
621
|
if( !f )
|
|
@@ -518,35 +628,55 @@ CustomElement extends HTMLElement
|
|
|
518
628
|
assureUnique(f);
|
|
519
629
|
merge( this, f.childNodes )
|
|
520
630
|
})
|
|
521
|
-
|
|
522
|
-
|
|
631
|
+
|
|
632
|
+
DceElement.observedAttributes.map( a =>
|
|
633
|
+
{ let v = attr(this.firstElementChild,a);
|
|
634
|
+
if( v !== attr(this,a) )
|
|
635
|
+
{ this.setAttribute( a, v );
|
|
636
|
+
this.#applyAttribute( a, v );
|
|
637
|
+
}
|
|
638
|
+
})
|
|
523
639
|
|
|
524
640
|
forEach$( this,'[slice]', el =>
|
|
525
641
|
{ if( !el.dceInitialized )
|
|
526
642
|
{ el.dceInitialized = 1;
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
643
|
+
const evs = attr(el,'slice-event');
|
|
644
|
+
(evs || 'change')
|
|
645
|
+
.split(' ')
|
|
646
|
+
.forEach( t=> (el.localName==='slice'? el.parentElement : el)
|
|
647
|
+
.addEventListener( t, ev=>
|
|
648
|
+
{ ev.sliceElement = el;
|
|
649
|
+
this.onSlice(ev)
|
|
650
|
+
} ));
|
|
651
|
+
if( !evs || evs.includes('init') )
|
|
652
|
+
{ if( el.hasAttribute('slice-value') || el.hasAttribute('value') || el.value )
|
|
653
|
+
this.onSlice({type:'init', target: el, sliceElement:el })
|
|
654
|
+
else
|
|
655
|
+
el.value = sliceXPath( attr(el,'slice') )
|
|
656
|
+
}
|
|
530
657
|
}
|
|
531
|
-
})
|
|
658
|
+
});
|
|
659
|
+
this.#inTransform = 0;
|
|
532
660
|
};
|
|
533
661
|
transform();
|
|
534
662
|
applySlices();
|
|
535
663
|
}
|
|
536
|
-
|
|
537
|
-
{
|
|
538
|
-
return;
|
|
539
|
-
let a = this.xml.querySelector(`attributes>${name}`);
|
|
664
|
+
#applyAttribute(name, newValue)
|
|
665
|
+
{ let a = this.xml.querySelector(`attributes>${name}`);
|
|
540
666
|
if( a )
|
|
541
|
-
emptyNode(a).append( createText(a,newValue));
|
|
667
|
+
emptyNode(a).append( createText(a,newValue) );
|
|
542
668
|
else
|
|
543
669
|
{ a = create( name, newValue, this.xml );
|
|
544
|
-
a.append( createText(a,newValue) );
|
|
545
670
|
this.xml.querySelector('attributes').append( a );
|
|
546
671
|
}
|
|
547
|
-
|
|
672
|
+
}
|
|
673
|
+
attributeChangedCallback(name, oldValue, newValue)
|
|
674
|
+
{ if( !this.xml || this.#inTransform )
|
|
675
|
+
return;
|
|
676
|
+
this.#applyAttribute(name, newValue);
|
|
548
677
|
this.transform(); // needs throttling
|
|
549
678
|
}
|
|
679
|
+
|
|
550
680
|
get dce(){ return dce }
|
|
551
681
|
}
|
|
552
682
|
if(tag)
|
package/demo/a.html
CHANGED
|
@@ -4,30 +4,58 @@
|
|
|
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
9
|
<script type="module" src="../custom-element.js"></script>
|
|
10
10
|
|
|
11
11
|
<style>
|
|
12
12
|
@import "./demo.css";
|
|
13
13
|
|
|
14
|
-
button{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
button {
|
|
15
|
+
background: forestgreen;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
table {
|
|
19
|
+
min-width: 16rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
td {
|
|
23
|
+
border-bottom: 1px solid silver;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
tfoot td {
|
|
27
|
+
border-bottom: none;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
td, th {
|
|
31
|
+
text-align: right;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
caption {
|
|
35
|
+
padding: 1rem;
|
|
36
|
+
font-weight: bolder;
|
|
37
|
+
font-family: sans-serif;
|
|
38
|
+
}
|
|
20
39
|
</style>
|
|
21
40
|
</head>
|
|
22
41
|
<body>
|
|
23
42
|
|
|
24
|
-
<custom-element
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
<custom-element>
|
|
44
|
+
<template>
|
|
45
|
+
<button slice="clickcount"
|
|
46
|
+
slice-event="click"
|
|
47
|
+
slice-value="//clickcount + 1" >
|
|
48
|
+
+
|
|
49
|
+
</button>
|
|
50
|
+
<button slice="clickcount"
|
|
51
|
+
slice-event="click"
|
|
52
|
+
slice-value="//clickcount - 1" >
|
|
53
|
+
-
|
|
54
|
+
</button>
|
|
55
|
+
<input slice="clickcount" type="number" value="{//clickcount ?? 0}" />
|
|
56
|
+
{//clickcount}
|
|
57
|
+
</template>
|
|
58
|
+
</custom-element>
|
|
31
59
|
|
|
32
60
|
</body>
|
|
33
61
|
</html>
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
5
|
+
<title>Data slices - Declarative Custom Element implementation demo</title>
|
|
6
|
+
<link rel="icon" href="./wc-square.svg"/>
|
|
7
|
+
|
|
8
|
+
<script type="module" src="../http-request.js"></script>
|
|
9
|
+
<script type="module" src="../input-text.js"></script>
|
|
10
|
+
<script type="module" src="../custom-element.js"></script>
|
|
11
|
+
<style>
|
|
12
|
+
@import "./demo.css";
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
|
|
17
|
+
<nav>
|
|
18
|
+
<a href="../index.html"><h3><code>custom-element</code> demo</h3></a>
|
|
19
|
+
<h3>Data slices propagation by events.</h3>
|
|
20
|
+
</nav>
|
|
21
|
+
|
|
22
|
+
<html-demo-element legend="A. slice initialization, change on event"
|
|
23
|
+
description="initial value should be 0; + and - should change the number in input field">
|
|
24
|
+
<template>
|
|
25
|
+
<custom-element>
|
|
26
|
+
<template>
|
|
27
|
+
<button slice="clickcount"
|
|
28
|
+
slice-event="click"
|
|
29
|
+
slice-value="//clickcount + 1" >
|
|
30
|
+
+
|
|
31
|
+
</button>
|
|
32
|
+
<button slice="clickcount"
|
|
33
|
+
slice-event="click"
|
|
34
|
+
slice-value="//clickcount - 1" >
|
|
35
|
+
-
|
|
36
|
+
</button>
|
|
37
|
+
<input slice="clickcount" type="number" value="{//clickcount ?? 0}" />
|
|
38
|
+
{//clickcount}
|
|
39
|
+
</template>
|
|
40
|
+
</custom-element>
|
|
41
|
+
</template>
|
|
42
|
+
</html-demo-element>
|
|
43
|
+
|
|
44
|
+
<html-demo-element legend="B. slice event data."
|
|
45
|
+
description="move the mouse over TEXTAREA and click to see slice and slice event changed">
|
|
46
|
+
<template>
|
|
47
|
+
<custom-element>
|
|
48
|
+
<template>
|
|
49
|
+
<textarea slice="s" slice-value="concat('x:', //@pageX)"
|
|
50
|
+
slice-event="mousemove click"
|
|
51
|
+
style="width:16rem;height:16rem;box-shadow: inset {//@offsetX}px {//@offsetY}px gold;" ></textarea><br/>
|
|
52
|
+
//slice/s : {//slice/s} <br/>
|
|
53
|
+
//slice/s/event/@offsetY: {//slice/s/event/@offsetY} <br/>
|
|
54
|
+
event type:{//slice/s/event/@type}
|
|
55
|
+
</template>
|
|
56
|
+
</custom-element>
|
|
57
|
+
</template>
|
|
58
|
+
</html-demo-element>
|
|
59
|
+
|
|
60
|
+
<html-demo-element legend="1. slice change on event. 1:1 slice⮂value"
|
|
61
|
+
description="initial value blank; type and unfocus to see slice changed">
|
|
62
|
+
<template>
|
|
63
|
+
|
|
64
|
+
<custom-element>
|
|
65
|
+
<input slice="typed" /> //slice/typed : {//slice/typed}
|
|
66
|
+
</custom-element>
|
|
67
|
+
</template>
|
|
68
|
+
</html-demo-element>
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
<html-demo-element legend="2. initial slice value, slice change on event. slice⮂value, w/ initial"
|
|
72
|
+
description="initial value from input; type and unfocus to see slice changed">
|
|
73
|
+
<template>
|
|
74
|
+
<custom-element>
|
|
75
|
+
<input slice="s" value="{//s ?? 'B'}" /> //slice/s : {//slice/s}
|
|
76
|
+
</custom-element>
|
|
77
|
+
</template>
|
|
78
|
+
</html-demo-element>
|
|
79
|
+
|
|
80
|
+
<html-demo-element legend="3. initial slice value, slice change on event. slice⮂value, w/ initial"
|
|
81
|
+
description="initial value from input; type to see slice changed">
|
|
82
|
+
<template>
|
|
83
|
+
<custom-element>
|
|
84
|
+
<input slice="s" value="{//s ?? 'B'}" slice-event="input"/> //slice/s : {//slice/s}
|
|
85
|
+
</custom-element>
|
|
86
|
+
</template>
|
|
87
|
+
</html-demo-element>
|
|
88
|
+
|
|
89
|
+
<html-demo-element legend="4. initial slice value from attribute, slice change on event."
|
|
90
|
+
description="initial value from input; type to see slice changed">
|
|
91
|
+
<template>
|
|
92
|
+
<custom-element tag="dce-1">
|
|
93
|
+
<template>
|
|
94
|
+
<attribute name="a" >😁</attribute>
|
|
95
|
+
<input slice="s" value="{//s ?? $a}" slice-event="keyup" />
|
|
96
|
+
attribute 'a' : {$a}
|
|
97
|
+
//slice/s : {//slice/s}
|
|
98
|
+
</template>
|
|
99
|
+
</custom-element>
|
|
100
|
+
<dce-1></dce-1>
|
|
101
|
+
<dce-1 a="🤗"></dce-1>
|
|
102
|
+
</template>
|
|
103
|
+
</html-demo-element>
|
|
104
|
+
|
|
105
|
+
<html-demo-element legend="5. initial slice value from attribute, slice change on event."
|
|
106
|
+
description="initial value from input as 'xB'; type and unfocus to see slice changed">
|
|
107
|
+
<template>
|
|
108
|
+
<custom-element>
|
|
109
|
+
<template>
|
|
110
|
+
<input slice="s" value="{substring(//s, 2) ?? 'B'}" slice-value="concat('x', @value )" />
|
|
111
|
+
//slice/s : {//slice/s}
|
|
112
|
+
</template>
|
|
113
|
+
</custom-element>
|
|
114
|
+
</template>
|
|
115
|
+
</html-demo-element>
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
<html-demo-element legend="6. initial slice value from input, button ignored till change on click."
|
|
119
|
+
description="initial value from input as 'anonymous'; on button click change to 'broccoli'">
|
|
120
|
+
<template>
|
|
121
|
+
<custom-element>
|
|
122
|
+
<template>
|
|
123
|
+
<input slice="nickname" value="anonymous" />
|
|
124
|
+
<button slice="nickname" slice-value="'broccoli'" slice-event="click">🥦</button>
|
|
125
|
+
{//nickname}
|
|
126
|
+
</template>
|
|
127
|
+
</custom-element>
|
|
128
|
+
</template>
|
|
129
|
+
</html-demo-element>
|
|
130
|
+
|
|
131
|
+
<html-demo-element legend="7. initial slice value from SLICE element, button ignored till change on click."
|
|
132
|
+
description="synthetic SLICE element serves as initial value holder">
|
|
133
|
+
<template>
|
|
134
|
+
<custom-element>
|
|
135
|
+
<template>
|
|
136
|
+
<button slice="clickcount" slice-event="click tap" slice-value="//clickcount + 1">
|
|
137
|
+
<slice slice="clickcount" value="0" ></slice>
|
|
138
|
+
click/tap
|
|
139
|
+
</button>
|
|
140
|
+
//clickcount : {//clickcount}
|
|
141
|
+
</template>
|
|
142
|
+
</custom-element>
|
|
143
|
+
</template>
|
|
144
|
+
</html-demo-element>
|
|
145
|
+
|
|
146
|
+
<html-demo-element legend="8. multiple slices by SLICE element, button ignored till change on click."
|
|
147
|
+
description="synthetic SLICE elements serve as initial value holder">
|
|
148
|
+
<template>
|
|
149
|
+
<custom-element>
|
|
150
|
+
<template>
|
|
151
|
+
<button>
|
|
152
|
+
<slice slice="clicked" value="{0}" ></slice>
|
|
153
|
+
<slice slice="focused" value="{0}" ></slice>
|
|
154
|
+
<slice slice-event="click tap" slice="clicked" slice-value="//clicked+1" ></slice>
|
|
155
|
+
<slice slice-event="focus" slice="focused" slice-value="1" ></slice>
|
|
156
|
+
<slice slice-event="blur" slice="focused" slice-value="0" ></slice>
|
|
157
|
+
click/tap, focus/blur
|
|
158
|
+
</button> <br/>
|
|
159
|
+
//clicked : {//clicked} <br/>
|
|
160
|
+
//focused : {//focused}
|
|
161
|
+
</template>
|
|
162
|
+
</custom-element>
|
|
163
|
+
</template>
|
|
164
|
+
</html-demo-element>
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
<html-demo-element legend="9. slice in attribute"
|
|
168
|
+
description="initial attribute value should be smile as emoji and :) on blur from input it should be updated from value">
|
|
169
|
+
<template>
|
|
170
|
+
<custom-element tag="emotional-element">
|
|
171
|
+
<template>
|
|
172
|
+
<attribute name="emotion" select="//emotion ?? '😃'"></attribute>
|
|
173
|
+
<input slice="/datadom/attributes/emotion"/>
|
|
174
|
+
Type and unfocus to update emotion attribute: {emotion}
|
|
175
|
+
</template>
|
|
176
|
+
</custom-element>
|
|
177
|
+
<emotional-element emotion=":)"></emotional-element>
|
|
178
|
+
<emotional-element></emotional-element>
|
|
179
|
+
</template>
|
|
180
|
+
</html-demo-element>
|
|
181
|
+
|
|
182
|
+
<script type="module" src="https://unpkg.com/html-demo-element@1/html-demo-element.js"></script>
|
|
183
|
+
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|
package/demo/demo.css
CHANGED
|
@@ -6,6 +6,7 @@ body,nav{ display: flex; flex-wrap: wrap; align-content: stretch; gap: 1rem; }
|
|
|
6
6
|
body>*{flex: auto;}
|
|
7
7
|
nav{ flex-direction: column;}
|
|
8
8
|
custom-element+*,
|
|
9
|
+
custom-element+*+*,
|
|
9
10
|
custom-element:not([tag]),
|
|
10
11
|
dce-link,dce-1-slot,dce-2-slot,dce-3-slot,dce-4-slot,dce-2-slots,greet-element,pokemon-tile,
|
|
11
12
|
dce-1,dce-2,dce-3,dce-4,dce-internal,dce-hash
|
package/demo/dom-merge.html
CHANGED
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
<html-demo-element legend="4. external XSLT file"
|
|
79
79
|
description="This external templates generated the tree for DCE data set"
|
|
80
80
|
>
|
|
81
|
+
<a href="tree.xsl">tree.xsl</a>
|
|
81
82
|
<template>
|
|
82
83
|
<custom-element tag="dce-external-4" src="tree.xsl" >
|
|
83
84
|
<template><i>loading from XSLT ...</i></template>
|
package/demo/http-request.html
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
<template>
|
|
38
38
|
<custom-element>
|
|
39
39
|
<template><!-- wrapping into template to prevent images loading within DCE declaration -->
|
|
40
|
+
<p>Pokemon buttons from API</p>
|
|
40
41
|
<http-request
|
|
41
42
|
url="https://pokeapi.co/api/v2/pokemon?limit=6&offset=0"
|
|
42
43
|
slice="page"
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
></http-request>
|
|
46
47
|
<variable name="slides-url"
|
|
47
48
|
>https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world</variable>
|
|
48
|
-
<for-each select="//
|
|
49
|
+
<for-each select="//results">
|
|
49
50
|
<variable name="pokeid"
|
|
50
51
|
select="substring-before( substring-after( @url, 'https://pokeapi.co/api/v2/pokemon/'),'/')"
|
|
51
52
|
></variable>
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
<custom-element url="https://pokeapi.co/api/v2/pokemon?offset=6&limit=6">
|
|
68
69
|
<template> <!-- IMPORTANT! to wrap DCE payload into template to avoid
|
|
69
70
|
http-request initializing out of instance -->
|
|
71
|
+
<attribute name="url"></attribute>
|
|
70
72
|
<http-request
|
|
71
73
|
url="{url}"
|
|
72
74
|
slice="request_slice"
|
|
@@ -79,15 +81,15 @@
|
|
|
79
81
|
|
|
80
82
|
<h3>Samples</h3>
|
|
81
83
|
<table>
|
|
82
|
-
<tr><th> //slice/request_slice/request/@mode </th>
|
|
83
|
-
<td>{ //slice/request_slice/request/@mode }</td></tr>
|
|
84
|
-
<tr><th> //slice/request_slice/response/headers/@content-type </th>
|
|
85
|
-
<td>{ //slice/request_slice/response/headers/@content-type }</td></tr>
|
|
86
|
-
<tr><th> //slice/request_slice/response/@status </th>
|
|
87
|
-
<td>{ //slice/request_slice/response/@status }</td></tr>
|
|
84
|
+
<tr><th> //slice/request_slice/value/request/@mode </th>
|
|
85
|
+
<td>{ //slice/request_slice/value/request/@mode }</td></tr>
|
|
86
|
+
<tr><th> //slice/request_slice/value/response/headers/@content-type </th>
|
|
87
|
+
<td>{ //slice/request_slice/value/response/headers/@content-type }</td></tr>
|
|
88
|
+
<tr><th> //slice/request_slice/value/response/@status </th>
|
|
89
|
+
<td>{ //slice/request_slice/value/response/@status }</td></tr>
|
|
88
90
|
</table>
|
|
89
|
-
<apply-templates mode="display" select="//slice/request_slice/*"></apply-templates>
|
|
90
|
-
<template mode="display" match="*">
|
|
91
|
+
<apply-templates mode="display" select="//slice/request_slice/value/*"></apply-templates>
|
|
92
|
+
<xsl:template mode="display" match="*">
|
|
91
93
|
<fieldset>
|
|
92
94
|
<legend> {local-name(.)} </legend>
|
|
93
95
|
<ul>
|
|
@@ -101,7 +103,7 @@
|
|
|
101
103
|
</ul>
|
|
102
104
|
<apply-templates mode="display" select="*"></apply-templates>
|
|
103
105
|
</fieldset>
|
|
104
|
-
</template>
|
|
106
|
+
</xsl:template>
|
|
105
107
|
</template>
|
|
106
108
|
</custom-element>
|
|
107
109
|
</template>
|
package/demo/local-storage.html
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
<local-storage key="basket" slice="basket" live type="json"></local-storage>
|
|
46
46
|
<xhtml:table xmlns:xhtml="http://www.w3.org/1999/xhtml" >
|
|
47
47
|
<xhtml:tbody>
|
|
48
|
-
<for-each select="//basket/@*">
|
|
48
|
+
<for-each select="//basket/value/@*">
|
|
49
49
|
<xhtml:tr>
|
|
50
50
|
<xhtml:th> {name()} </xhtml:th>
|
|
51
51
|
<xhtml:td> {.} </xhtml:td>
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
<xhtml:tfoot>
|
|
56
56
|
<xhtml:tr>
|
|
57
57
|
<xhtml:td><slot>🤔</slot></xhtml:td>
|
|
58
|
-
<xhtml:th> {sum(//slice/basket/@*)} </xhtml:th>
|
|
58
|
+
<xhtml:th> {sum(//slice/basket/value/@*)} </xhtml:th>
|
|
59
59
|
</xhtml:tr>
|
|
60
60
|
</xhtml:tfoot>
|
|
61
61
|
</xhtml:table>
|
|
@@ -51,19 +51,24 @@
|
|
|
51
51
|
<location-element slice="window-url" live></location-element>
|
|
52
52
|
|
|
53
53
|
<xhtml:table>
|
|
54
|
-
|
|
55
|
-
<
|
|
54
|
+
<xhtml:tbody>
|
|
55
|
+
<xhtml:tr>
|
|
56
|
+
<xhtml:th><h3> URL properties </h3></xhtml:th>
|
|
57
|
+
<xhtml:td>{count(//value/@*)}</xhtml:td>
|
|
58
|
+
</xhtml:tr>
|
|
59
|
+
<apply-templates mode="attrs" select="//value/@*"></apply-templates>
|
|
60
|
+
</xhtml:tbody>
|
|
56
61
|
</xhtml:table>
|
|
57
62
|
<xhtml:table>
|
|
58
63
|
<tr><th><h3> URL parameters </h3></th></tr>
|
|
59
|
-
<apply-templates mode="attrs" select="//
|
|
64
|
+
<apply-templates mode="attrs" select="//params/*/*"></apply-templates>
|
|
60
65
|
</xhtml:table>
|
|
61
|
-
<template mode="attrs" match="*|@*">
|
|
66
|
+
<xsl:template mode="attrs" match="*|@*">
|
|
62
67
|
<xhtml:tr>
|
|
63
68
|
<xhtml:th>{name()}</xhtml:th>
|
|
64
69
|
<xhtml:td>{.}</xhtml:td>
|
|
65
70
|
</xhtml:tr>
|
|
66
|
-
</template>
|
|
71
|
+
</xsl:template>
|
|
67
72
|
</template>
|
|
68
73
|
</custom-element>
|
|
69
74
|
<dce-2>?</dce-2>
|
|
@@ -84,7 +89,7 @@
|
|
|
84
89
|
<xhtml:table>
|
|
85
90
|
<xhtml:tbody>
|
|
86
91
|
<xhtml:tr><xhtml:th><h3>URL properties</h3></xhtml:th></xhtml:tr>
|
|
87
|
-
<for-each select="//slice/window-url/@*">
|
|
92
|
+
<for-each select="//slice/window-url/value/@*">
|
|
88
93
|
<xhtml:tr>
|
|
89
94
|
<xhtml:th>{name()}</xhtml:th>
|
|
90
95
|
<xhtml:td>{.}</xhtml:td>
|
|
@@ -93,7 +98,7 @@
|
|
|
93
98
|
</xhtml:tbody>
|
|
94
99
|
<xhtml:tbody>
|
|
95
100
|
<xhtml:tr><xhtml:th><h3>URL parameters</h3></xhtml:th></xhtml:tr>
|
|
96
|
-
<for-each select="//slice/window-url/params/*">
|
|
101
|
+
<for-each select="//slice/window-url/value/params/*">
|
|
97
102
|
<xhtml:tr>
|
|
98
103
|
<xhtml:th>{name()}</xhtml:th>
|
|
99
104
|
<xhtml:td>{.}</xhtml:td>
|
|
@@ -121,7 +126,7 @@
|
|
|
121
126
|
<xhtml:table>
|
|
122
127
|
<xhtml:tbody>
|
|
123
128
|
<xhtml:tr><xhtml:th><h3>URL properties</h3></xhtml:th></xhtml:tr>
|
|
124
|
-
<for-each select="//slice/src-url/@*">
|
|
129
|
+
<for-each select="//slice/src-url/value/@*">
|
|
125
130
|
<xhtml:tr>
|
|
126
131
|
<xhtml:th>{name()}</xhtml:th>
|
|
127
132
|
<xhtml:td>{.}</xhtml:td>
|
|
@@ -130,7 +135,7 @@
|
|
|
130
135
|
</xhtml:tbody>
|
|
131
136
|
<xhtml:tbody>
|
|
132
137
|
<xhtml:tr><xhtml:th><h3>URL parameters</h3></xhtml:th></xhtml:tr>
|
|
133
|
-
<for-each select="//slice/src-url/params/*">
|
|
138
|
+
<for-each select="//slice/src-url/value/params/*">
|
|
134
139
|
<xhtml:tr>
|
|
135
140
|
<xhtml:th>{name()}</xhtml:th>
|
|
136
141
|
<xhtml:td>{.}</xhtml:td>
|
package/demo/parameters.html
CHANGED
|
@@ -44,6 +44,24 @@ params needed to declare DCE attributes and track the attributes changes. It als
|
|
|
44
44
|
</template>
|
|
45
45
|
</html-demo-element>
|
|
46
46
|
|
|
47
|
+
<html-demo-element legend="slice propagates attribute" description="
|
|
48
|
+
when slice value points to attribute, it would be populated on slice change
|
|
49
|
+
">
|
|
50
|
+
Type in the input field to see the variable $title change. <br/>
|
|
51
|
+
Hover the mouse to see the title attribute text popup.<br/>
|
|
52
|
+
Inspect DCE node in dev tools to see `title` attribute updated while typing.
|
|
53
|
+
|
|
54
|
+
<template>
|
|
55
|
+
<custom-element>
|
|
56
|
+
<template>
|
|
57
|
+
<attribute name="title" select="//title ?? '😃'" ></attribute>
|
|
58
|
+
<input slice="/datadom/attributes/title" slice-event="keyup"/>
|
|
59
|
+
title attribute: {$title}
|
|
60
|
+
</template>
|
|
61
|
+
</custom-element>
|
|
62
|
+
</template>
|
|
63
|
+
</html-demo-element>
|
|
64
|
+
|
|
47
65
|
|
|
48
66
|
|
|
49
67
|
<script type="module" src="https://unpkg.com/html-demo-element@1/html-demo-element.js"></script>
|
package/demo/s.xml
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<datadom><payload><span xmlns="http://www.w3.org/1999/xhtml" slot=""> </span><button xmlns="http://www.w3.org/1999/xhtml" slice="clickcount" slice-event="click" slice-value="//clickcount + 1" slot="">
|
|
3
|
+
+
|
|
4
|
+
</button><span xmlns="http://www.w3.org/1999/xhtml" slot=""> </span><button xmlns="http://www.w3.org/1999/xhtml" slice="clickcount" slice-event="click" slice-value="//clickcount - 1" slot="">
|
|
5
|
+
-
|
|
6
|
+
</button><span xmlns="http://www.w3.org/1999/xhtml" slot=""> </span><input xmlns="http://www.w3.org/1999/xhtml" slice="clickcount" type="number" value="{//clickcount ?? 0}" slot="" /><span xmlns="http://www.w3.org/1999/xhtml" slot=""> {//clickcount} </span></payload><attributes><tag>dce-72e0d208-b3b4-4b0a-8798-a094beaf5fe1</tag></attributes><dataset/><slice><clickcount xmlns="" slice="clickcount" type="number" value="" data-dce-id="2" slice-event="click" slice-value="//clickcount + 1"><event isTrusted="true" pointerId="1" width="1" height="1" pressure="0" tiltX="0" tiltY="0" azimuthAngle="0" altitudeAngle="1.5707963267948966" tangentialPressure="0" twist="0" pointerType="mouse" isPrimary="false" screenX="19" screenY="99" clientX="19" clientY="12" ctrlKey="false" shiftKey="false" altKey="false" metaKey="false" button="0" buttons="0" pageX="19" pageY="12" x="19" y="12" offsetX="10" offsetY="0" movementX="0" movementY="0" layerX="19" layerY="12" detail="1" which="1" type="click" eventPhase="0" bubbles="true" cancelable="true" defaultPrevented="false" composed="true" timeStamp="5460750.8000000715" returnValue="true" cancelBubble="false" NONE="0" CAPTURING_PHASE="1" AT_TARGET="2" BUBBLING_PHASE="3"><relatedTarget/><fromElement/><toElement/><sourceCapabilities firesTouchEvents="false"/><currentTarget/></event>46</clickcount></slice></datadom>
|
package/demo/s.xslt
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
1
2
|
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:dce="urn:schemas-epa-wg:dce" xmlns:exsl="http://exslt.org/common" version="1.0" exclude-result-prefixes="exsl">
|
|
2
3
|
<xsl:template match="ignore">
|
|
3
4
|
<xsl:choose>
|
|
@@ -5,14 +6,13 @@
|
|
|
5
6
|
<xsl:otherwise><xsl:value-of select="def"/></xsl:otherwise>
|
|
6
7
|
</xsl:choose>
|
|
7
8
|
<xsl:value-of select="."/></xsl:template>
|
|
8
|
-
<xsl:template mode="payload" match="attributes"><
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
</xsl:choose></xsl:param><dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" data-dce-id="1"><xsl:attribute xmlns:xsl="http://www.w3.org/1999/XSL/Transform" name="p1" select="//p1 ?? 'def_p1' "/><xsl:attribute xmlns:xsl="http://www.w3.org/1999/XSL/Transform" name="p2" select="'always_p2'"/><xsl:attribute xmlns:xsl="http://www.w3.org/1999/XSL/Transform" name="p3"/></dce-root></xsl:template>
|
|
9
|
+
<xsl:template mode="payload" match="attributes"><dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" data-dce-id="1"><button xmlns="" slice="clickcount" slice-event="click" slice-value="//clickcount + 1" data-dce-id="2">
|
|
10
|
+
+
|
|
11
|
+
</button><button xmlns="" slice="clickcount" slice-event="click" slice-value="//clickcount - 1" data-dce-id="3">
|
|
12
|
+
-
|
|
13
|
+
</button><input xmlns="" slice="clickcount" type="number" value="{concat( //clickcount , substring( 0 , (1+string-length( 0 )) * string-length( //clickcount ) ) )}" data-dce-id="4"/><dce-text xmlns="" data-dce-id="5">
|
|
14
|
+
<xsl:value-of select="//clickcount"/>
|
|
15
|
+
</dce-text></dce-root></xsl:template>
|
|
16
16
|
<xsl:template match="/">
|
|
17
17
|
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
18
18
|
</xsl:template>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<xsl:param name="defaultvalue"/>
|
|
22
22
|
<xsl:choose>
|
|
23
23
|
<xsl:when test="//payload/*[@slot=$slotname]">
|
|
24
|
-
|
|
24
|
+
<xsl:copy-of select="//payload/*[@slot=$slotname]"/>
|
|
25
25
|
</xsl:when>
|
|
26
26
|
<xsl:otherwise>
|
|
27
27
|
<xsl:copy-of select="$defaultvalue"/>
|
package/demo/z.html
CHANGED
|
@@ -1,48 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</
|
|
48
|
-
</
|
|
1
|
+
<dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
|
2
|
+
xmlns:dce="urn:schemas-epa-wg:dce" data-dce-id="1">
|
|
3
|
+
<location-element xmlns="" slice="window-url" live="" data-dce-id="2"></location-element>
|
|
4
|
+
<table xmlns="" data-dce-id="3">
|
|
5
|
+
<tbody data-dce-id="0-1">
|
|
6
|
+
<tr data-dce-id="4">
|
|
7
|
+
<th data-dce-id="5"><h3 data-dce-id="6"> URL properties </h3></th>
|
|
8
|
+
<td data-dce-id="7">9</td>
|
|
9
|
+
</tr>
|
|
10
|
+
<tr data-dce-id="10">
|
|
11
|
+
<th data-dce-id="11">href</th>
|
|
12
|
+
<td data-dce-id="12">http://localhost:63342/custom-element/demo/a.html?_ijt=dmv0p4go000q47lg48i5im92f7&_ij_reload=RELOAD_ON_SAVE</td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr data-dce-id="10-1">
|
|
15
|
+
<th data-dce-id="11">origin</th>
|
|
16
|
+
<td data-dce-id="12">http://localhost:63342</td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr data-dce-id="10-2">
|
|
19
|
+
<th data-dce-id="11">protocol</th>
|
|
20
|
+
<td data-dce-id="12">http:</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr data-dce-id="10-3">
|
|
23
|
+
<th data-dce-id="11">host</th>
|
|
24
|
+
<td data-dce-id="12">localhost:63342</td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr data-dce-id="10-4">
|
|
27
|
+
<th data-dce-id="11">hostname</th>
|
|
28
|
+
<td data-dce-id="12">localhost</td>
|
|
29
|
+
</tr>
|
|
30
|
+
<tr data-dce-id="10-5">
|
|
31
|
+
<th data-dce-id="11">port</th>
|
|
32
|
+
<td data-dce-id="12">63342</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr data-dce-id="10-6">
|
|
35
|
+
<th data-dce-id="11">pathname</th>
|
|
36
|
+
<td data-dce-id="12">/custom-element/demo/a.html</td>
|
|
37
|
+
</tr>
|
|
38
|
+
<tr data-dce-id="10-7">
|
|
39
|
+
<th data-dce-id="11">search</th>
|
|
40
|
+
<td data-dce-id="12">?_ijt=dmv0p4go000q47lg48i5im92f7&_ij_reload=RELOAD_ON_SAVE</td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr data-dce-id="10-8">
|
|
43
|
+
<th data-dce-id="11">hash</th>
|
|
44
|
+
<td data-dce-id="12"></td>
|
|
45
|
+
</tr>
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
<h3 data-dce-id="9"> URL parameters </h3>
|
|
49
|
+
<table xmlns="" data-dce-id="8">
|
|
50
|
+
|
|
51
|
+
<tbody data-dce-id="0-1">
|
|
52
|
+
<tr data-dce-id="10">
|
|
53
|
+
<th data-dce-id="11">_ijt</th>
|
|
54
|
+
<td data-dce-id="12">dmv0p4go000q47lg48i5im92f7</td>
|
|
55
|
+
</tr>
|
|
56
|
+
<tr data-dce-id="10-1">
|
|
57
|
+
<th data-dce-id="11">_ij_reload</th>
|
|
58
|
+
<td data-dce-id="12">RELOAD_ON_SAVE</td>
|
|
59
|
+
</tr>
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
62
|
+
</dce-root>
|
package/http-request.js
CHANGED
|
@@ -18,7 +18,8 @@ export class HttpRequestElement extends HTMLElement
|
|
|
18
18
|
}
|
|
19
19
|
get requestProps()
|
|
20
20
|
{ const ret = {};
|
|
21
|
-
[...this.attributes].filter(a=>!a.name.startsWith('header-'))
|
|
21
|
+
[...this.attributes].filter(a=>!a.name.startsWith('header-'))
|
|
22
|
+
.filter(a=>!a.name.startsWith('slice')).map( a => ret[a.name] = a.value );
|
|
22
23
|
return ret
|
|
23
24
|
}
|
|
24
25
|
|
package/ide/customData-dce.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "slice",
|
|
6
6
|
"description": {
|
|
7
7
|
"kind": "markdown",
|
|
8
|
-
"value": "Defines the name of data slice in DCE where the data from `value` will be propagated on `change` or by `slice-
|
|
8
|
+
"value": "Defines the name of data slice in DCE where the data from `value` will be propagated on `change` or by `slice-event` event\n\nOn: any component with `value` and associated change event"
|
|
9
9
|
},
|
|
10
10
|
"references": [
|
|
11
11
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
|
-
"name": "slice-
|
|
18
|
+
"name": "slice-event",
|
|
19
19
|
"description": {
|
|
20
20
|
"kind": "markdown",
|
|
21
21
|
"value": "Defines the event name on which `value` would be synchronized with DCE slice\n\nOn: any component with `value` and associated change event"
|
|
@@ -26,6 +26,19 @@
|
|
|
26
26
|
"url": "https://unpkg.com/@epa-wg/custom-element/demo/dom-merge.html"
|
|
27
27
|
}
|
|
28
28
|
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "slice-value",
|
|
32
|
+
"description": {
|
|
33
|
+
"kind": "markdown",
|
|
34
|
+
"value": "XPath expression to populate into the slice"
|
|
35
|
+
},
|
|
36
|
+
"references": [
|
|
37
|
+
{
|
|
38
|
+
"name": "Demo",
|
|
39
|
+
"url": "https://unpkg.com/@epa-wg/custom-element/demo/data-slices.html"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
29
42
|
}
|
|
30
43
|
],
|
|
31
44
|
"tags": [
|
|
@@ -84,6 +97,16 @@
|
|
|
84
97
|
"url": "https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/for-each"
|
|
85
98
|
}
|
|
86
99
|
]
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "slice",
|
|
103
|
+
"description": "Synthetic element for defining the slice-attributed when more then one slice/event/value associated with parent element",
|
|
104
|
+
"references": [
|
|
105
|
+
{
|
|
106
|
+
"name": "README",
|
|
107
|
+
"url": "https://github.com/EPA-WG/custom-element/tree/develop?tab=readme-ov-file#interactivity-via-data-slice-triggered-by-events"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
87
110
|
}
|
|
88
111
|
]
|
|
89
112
|
}
|
package/ide/web-types-dce.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json.schemastore.org/web-types",
|
|
3
3
|
"name": "@epa-wg/custom-element",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.19",
|
|
5
5
|
"js-types-syntax": "typescript",
|
|
6
6
|
"description-markup": "markdown",
|
|
7
7
|
"contributions": {
|
|
@@ -9,13 +9,18 @@
|
|
|
9
9
|
"attributes": [
|
|
10
10
|
{
|
|
11
11
|
"name": "slice",
|
|
12
|
-
"description": "Defines the name of data slice in DCE where the data from `value` will be propagated on `change` or by `slice-
|
|
12
|
+
"description": "Defines the name of data slice in DCE where the data from `value` will be propagated on `change` or by `slice-event` event\n\nOn: any component with `value` and associated change event",
|
|
13
13
|
"doc-url": "https://unpkg.com/@epa-wg/custom-element/demo/dom-merge.html"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
"name": "slice-
|
|
16
|
+
"name": "slice-event",
|
|
17
17
|
"description": "Defines the event name on which `value` would be synchronized with DCE slice\n\nOn: any component with `value` and associated change event",
|
|
18
18
|
"doc-url": "https://unpkg.com/@epa-wg/custom-element/demo/dom-merge.html"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "slice-value",
|
|
22
|
+
"description": "XPath expression to populate into the slice",
|
|
23
|
+
"doc-url": "https://unpkg.com/@epa-wg/custom-element/demo/data-slices.html"
|
|
19
24
|
}
|
|
20
25
|
],
|
|
21
26
|
"elements": [
|
|
@@ -91,6 +96,14 @@
|
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "slice",
|
|
102
|
+
"description": "Synthetic element for defining the slice-attributed when more then one slice/event/value associated with parent element",
|
|
103
|
+
"doc-url": "https://github.com/EPA-WG/custom-element/tree/develop?tab=readme-ov-file#interactivity-via-data-slice-triggered-by-events",
|
|
104
|
+
"attributes": [
|
|
105
|
+
|
|
106
|
+
]
|
|
94
107
|
}
|
|
95
108
|
]
|
|
96
109
|
}
|
package/ide/web-types-xsl.json
CHANGED
package/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
@import "demo/demo.css";
|
|
10
10
|
</style>
|
|
11
11
|
</head>
|
|
12
|
-
<body>
|
|
12
|
+
<body xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
13
13
|
<nav>
|
|
14
14
|
<h3><code>custom-element</code> demo</h3>
|
|
15
15
|
<div><a href="https://github.com/EPA-WG/custom-element"
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
<a href="./demo/hex-grid.html" >hex grid lib </a> |
|
|
35
35
|
<a href="./demo/scoped-css.html" >scoped CSS </a> |
|
|
36
36
|
<a href="./demo/parameters.html" >attributes </a> |
|
|
37
|
+
<a href="./demo/data-slices.html" >data slices/events </a> |
|
|
37
38
|
<a href="./demo/dom-merge.html" >DOM merge on dynamic update </a>
|
|
38
39
|
</section>
|
|
39
40
|
</nav>
|
package/package.json
CHANGED