@epa-wg/custom-element 0.0.19 → 0.0.21
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/.editorconfig +11 -0
- package/README.md +29 -29
- package/bin/xslDtd2Ide.mjs +1 -1
- package/custom-element.d.ts +1 -0
- package/custom-element.js +35 -48
- package/demo/a.html +18 -16
- package/demo/data-slices.html +0 -1
- package/demo/dom-merge.html +2 -3
- package/demo/http-request.html +27 -0
- package/demo/local-storage.html +106 -1
- package/demo/location-element.html +6 -6
- package/demo/s.xml +14 -6
- package/demo/s.xslt +52 -13
- package/demo/scoped-css.html +0 -1
- package/demo/ss.html +57 -32
- package/http-request.js +61 -25
- package/ide/web-types-dce.json +2 -2
- package/ide/web-types-xsl.json +2 -2
- package/index.js +7 -0
- package/local-storage.js +49 -12
- package/location-element.js +44 -19
- package/package.json +3 -3
- package/input-text.js +0 -17
package/.editorconfig
ADDED
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# custom-element
|
|
2
|
-
`Declarative Custom Element` (DCE) is a part of pure `Declarative Web Application` stack. A proof of concept as a part of
|
|
2
|
+
`Declarative Custom Element` (DCE) is a part of pure `Declarative Web Application` stack. A proof of concept as a part of
|
|
3
3
|
[WCCG in Declarative custom elements](https://github.com/w3c/webcomponents-cg/issues/32#issuecomment-1321037301) and [Declarative Web Application](https://github.com/EPA-WG/dwa#readme)
|
|
4
|
-
discussion. **NO-JS** The functionality of DCE and its data access does not require programming using JavaScript.
|
|
4
|
+
discussion. **NO-JS** The functionality of DCE and its data access does not require programming using JavaScript.
|
|
5
5
|
|
|
6
|
-
It allows to define custom HTML tag with template filled from slots, attributes and data `slice` as of now from
|
|
6
|
+
It allows to define custom HTML tag with template filled from slots, attributes and data `slice` as of now from
|
|
7
7
|
[local-storage][local-storage-demo], [http-request][http-request-demo], [location][location-demo].
|
|
8
8
|
UI is re-rendered on each data slice change triggered by initialization or DOM event.
|
|
9
9
|
|
|
@@ -13,16 +13,16 @@ UI is re-rendered on each data slice change triggered by initialization or DOM e
|
|
|
13
13
|
| [tests project][git-test-url]
|
|
14
14
|
| [Chrome devtools pugin][plugin-url]
|
|
15
15
|
|
|
16
|
-
[![NPM version][npm-image]][npm-url]
|
|
17
|
-
[![coverage][coverage-image]][coverage-url]
|
|
16
|
+
[![NPM version][npm-image]][npm-url]
|
|
17
|
+
[![coverage][coverage-image]][coverage-url]
|
|
18
18
|
[![Published on webcomponents.org][webcomponents-img]][webcomponents-url]
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
<details>
|
|
23
23
|
<summary> What is DCE? </summary>
|
|
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.
|
|
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.
|
|
26
26
|
There is no place for JavaScript except of polyfill and ability to extend DCE, which otherwise has to be native.
|
|
27
27
|
|
|
28
28
|
The composition assumes the fully functional template and ability to call the template with parameters( custom tag + attributes) .
|
|
@@ -113,11 +113,11 @@ comes from XSLT and XPath. Which is natively implemented in all current browsers
|
|
|
113
113
|
the seed grows in size as the Pokémon does.</p>
|
|
114
114
|
</pokemon-tile>
|
|
115
115
|
|
|
116
|
-
<pokemon-tile title="ninetales" pokemon-id="38" ></pokemon-tile>
|
|
116
|
+
<pokemon-tile title="ninetales" pokemon-id="38" ></pokemon-tile>
|
|
117
117
|
```
|
|
118
118
|
generates HTML
|
|
119
119
|
```html
|
|
120
|
-
<pokemon-tile title="bulbasaur" data-smile="👼"
|
|
120
|
+
<pokemon-tile title="bulbasaur" data-smile="👼"
|
|
121
121
|
image-src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/1.svg"
|
|
122
122
|
>
|
|
123
123
|
<h3>bulbasaur</h3>
|
|
@@ -126,7 +126,7 @@ generates HTML
|
|
|
126
126
|
<p>Bulbasaur is a cute Pokémon born with a large seed firmly affixed to its back;
|
|
127
127
|
the seed grows in size as the Pokémon does.</p>
|
|
128
128
|
</pokemon-tile>
|
|
129
|
-
<pokemon-tile title="ninetales"
|
|
129
|
+
<pokemon-tile title="ninetales"
|
|
130
130
|
image-src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/38.svg"
|
|
131
131
|
>
|
|
132
132
|
<h3>ninetales</h3>
|
|
@@ -146,16 +146,16 @@ generates HTML
|
|
|
146
146
|
* creates a class for custom element extending HTMLElement
|
|
147
147
|
* registers element by `tag` attribute
|
|
148
148
|
|
|
149
|
-
NOTE: attempt to register custom element with already registered tag name would fail due to w3c standard limitations.
|
|
149
|
+
NOTE: attempt to register custom element with already registered tag name would fail due to w3c standard limitations.
|
|
150
150
|
The scoped custom element registry is still a proposal.
|
|
151
151
|
|
|
152
152
|
### omitting `tag` leads to template instantiation
|
|
153
|
-
Whether template is inline or given by `src` attribute, the `custom-element` would be instantiated inline if no `tag`
|
|
153
|
+
Whether template is inline or given by `src` attribute, the `custom-element` would be instantiated inline if no `tag`
|
|
154
154
|
attribute is given.
|
|
155
155
|
|
|
156
156
|
### custom element instance
|
|
157
|
-
constructor creates XML with
|
|
158
|
-
* root matching the tag
|
|
157
|
+
constructor creates XML with
|
|
158
|
+
* root matching the tag
|
|
159
159
|
* payload
|
|
160
160
|
* dom nodes with `slot` attribute stay inside
|
|
161
161
|
* attributes
|
|
@@ -180,7 +180,7 @@ See [demo](https://unpkg.com/@epa-wg/custom-element@0.0/demo/external-template.h
|
|
|
180
180
|
## types of template
|
|
181
181
|
* HTML with DCE syntax ( slots, data slices, xslt operators, etc. )
|
|
182
182
|
* SVG image, MathML, etc.
|
|
183
|
-
* XSLT template. The `datadom` is the XML payload for transformation. In order to be embedded into external document,
|
|
183
|
+
* XSLT template. The `datadom` is the XML payload for transformation. In order to be embedded into external document,
|
|
184
184
|
this document has to have XML syntax like XHTML. Attempt of including XSLT within HTML file would break the template
|
|
185
185
|
integrity by parser.
|
|
186
186
|
|
|
@@ -198,7 +198,7 @@ allows to refer the template withing external document
|
|
|
198
198
|
# template syntax
|
|
199
199
|
[Scoped CSS][css-demo-url] live demo
|
|
200
200
|
## styles encapsulation
|
|
201
|
-
DCE can have the own styles which would be scoped to the instances.
|
|
201
|
+
DCE can have the own styles which would be scoped to the instances.
|
|
202
202
|
In order to prevent the style leaking, it has to be defined withing `template` tag:
|
|
203
203
|
```html
|
|
204
204
|
<custom-element>
|
|
@@ -250,15 +250,15 @@ The curly braces `{}` in attributes implemented as [attribute value template](ht
|
|
|
250
250
|
|
|
251
251
|
The names in curly braces are matching the instance attributes. I.e. in XML node `/my-component/attributes/`.
|
|
252
252
|
|
|
253
|
-
To access payload XPath could start with `/*/payload/`. I.e. `{/*/payload//label}` refers to all `label` tags in payload.
|
|
253
|
+
To access payload XPath could start with `/*/payload/`. I.e. `{/*/payload//label}` refers to all `label` tags in payload.
|
|
254
254
|
|
|
255
255
|
## Slots
|
|
256
|
-
`<slot name="xxx">` is replaced by payload top elements with `slot` attribute matching the name,
|
|
256
|
+
`<slot name="xxx">` is replaced by payload top elements with `slot` attribute matching the name,
|
|
257
257
|
i.e. slot `xxx` is matching `<i slot="xxx">...</i>` in payload.
|
|
258
258
|
```html
|
|
259
259
|
<custom-element tag="with-description" >
|
|
260
260
|
<slot name="description">description is not available</slot>
|
|
261
|
-
<!-- same as
|
|
261
|
+
<!-- same as
|
|
262
262
|
<value-of select='/*/payload/*[@slot="description"]'/>
|
|
263
263
|
-->
|
|
264
264
|
</custom-element>
|
|
@@ -270,7 +270,7 @@ i.e. slot `xxx` is matching `<i slot="xxx">...</i>` in payload.
|
|
|
270
270
|
## loops, variables
|
|
271
271
|
Loop implemented via [for-each](https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/for-each)
|
|
272
272
|
|
|
273
|
-
[Variables in XSLT](https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/variable)
|
|
273
|
+
[Variables in XSLT](https://developer.mozilla.org/en-US/docs/Web/XSLT/Element/variable)
|
|
274
274
|
|
|
275
275
|
## [XPath](https://developer.mozilla.org/en-US/docs/Web/XSLT/Transforming_XML_with_XSLT/The_Netscape_XSLT_XPath_Reference)
|
|
276
276
|
is available in `{}` in attributes, in `for-each`, `if`, `value-of`, and other XSL tags.
|
|
@@ -278,13 +278,13 @@ is available in `{}` in attributes, in `for-each`, `if`, `value-of`, and other X
|
|
|
278
278
|
XPath is a selector language to navigate over custom element instance data, attributes, and payload.
|
|
279
279
|
|
|
280
280
|
## XSLT 1.0
|
|
281
|
-
The in-browser native implementation as of now supports [XSLT 1.0](https://www.w3.org/TR/xslt-10/).
|
|
282
|
-
File the [change request](https://github.com/EPA-WG/custom-element/issues) for support of another XSLT version or
|
|
281
|
+
The in-browser native implementation as of now supports [XSLT 1.0](https://www.w3.org/TR/xslt-10/).
|
|
282
|
+
File the [change request](https://github.com/EPA-WG/custom-element/issues) for support of another XSLT version or
|
|
283
283
|
template engine.
|
|
284
284
|
|
|
285
285
|
# troubleshooting
|
|
286
286
|
## HTML parser is not compatible with templates
|
|
287
|
-
On many tags like `table`, or link `a` the attempt to use XSLT operations could lead to DOM order mismatch to given
|
|
287
|
+
On many tags like `table`, or link `a` the attempt to use XSLT operations could lead to DOM order mismatch to given
|
|
288
288
|
in template. In such cases the `xhtml:` prefix in front of troubled tag would solve the parsing.
|
|
289
289
|
|
|
290
290
|
```html
|
|
@@ -324,19 +324,19 @@ See [demo source](demo/local-storage.html) for detailed sample.
|
|
|
324
324
|
## template debugging
|
|
325
325
|
`xml` and `xslt` can be saved to file via for "_copy string contents_" into clipboard.
|
|
326
326
|
|
|
327
|
-
The XSLT debugger from your favorite IDE can set the breakpoints withing those files and
|
|
327
|
+
The XSLT debugger from your favorite IDE can set the breakpoints withing those files and
|
|
328
328
|
run transformation under debugger.
|
|
329
329
|
|
|
330
330
|
|
|
331
331
|
## `{}` does not give a value
|
|
332
|
-
* try to add as attribute you could observe and put the value of node name or text to identify the current location in data
|
|
332
|
+
* try to add as attribute you could observe and put the value of node name or text to identify the current location in data
|
|
333
333
|
within template
|
|
334
334
|
```xml
|
|
335
335
|
<b title="{name(*)} : {text()}">xml tag name: <value-of select='name()'/></b>
|
|
336
336
|
```
|
|
337
337
|
|
|
338
338
|
[git-url]: https://github.com/EPA-WG/custom-element
|
|
339
|
-
[git-test-url]: https://github.com/EPA-WG/custom-element-
|
|
339
|
+
[git-test-url]: https://github.com/EPA-WG/custom-element-dist
|
|
340
340
|
[demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/index.html
|
|
341
341
|
[css-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/scoped-css.html
|
|
342
342
|
[slice-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/data-slices.html
|
|
@@ -348,9 +348,9 @@ within template
|
|
|
348
348
|
[github-image]: https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg
|
|
349
349
|
[npm-image]: https://img.shields.io/npm/v/@epa-wg/custom-element.svg
|
|
350
350
|
[npm-url]: https://npmjs.org/package/@epa-wg/custom-element
|
|
351
|
-
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-
|
|
352
|
-
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-
|
|
353
|
-
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-
|
|
351
|
+
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.21/coverage/src/custom-element/coverage.svg
|
|
352
|
+
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.21/coverage/src/custom-element/index.html
|
|
353
|
+
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.21/storybook-static/index.html?path=/story/welcome--introduction
|
|
354
354
|
[sandbox-url]: https://stackblitz.com/github/EPA-WG/custom-element?file=index.html
|
|
355
355
|
[webcomponents-url]: https://www.webcomponents.org/element/@epa-wg/custom-element
|
|
356
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.21",
|
|
120
120
|
"js-types-syntax": "typescript",
|
|
121
121
|
"description-markup": "markdown",
|
|
122
122
|
"contributions": {
|
package/custom-element.d.ts
CHANGED
package/custom-element.js
CHANGED
|
@@ -11,7 +11,8 @@ const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
|
11
11
|
, isNode = e => e && typeof e.nodeType === 'number'
|
|
12
12
|
, create = ( tag, t = '', d=document ) => ( e => ((t && e.append(createText(d.ownerDocument||d, t))),e) )((d.ownerDocument || d ).createElement( tag ))
|
|
13
13
|
, createText = ( d, t) => (d.ownerDocument || d ).createTextNode( t )
|
|
14
|
-
,
|
|
14
|
+
, removeChildren = n => { while(n.firstChild) n.firstChild.remove(); return n; }
|
|
15
|
+
, emptyNode = n => { n.getAttributeNames().map( a => n.removeAttribute(a) ); return removeChildren(n); }
|
|
15
16
|
, createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, tag ))
|
|
16
17
|
, xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
|
|
17
18
|
, xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) )
|
|
@@ -62,43 +63,14 @@ assureSlot( e )
|
|
|
62
63
|
return e;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
export function
|
|
66
|
-
Json2Xml( o, tag )
|
|
67
|
-
{
|
|
68
|
-
if( typeof o === 'string' )
|
|
69
|
-
return o;
|
|
70
|
-
|
|
71
|
-
const noTag = "string" != typeof tag;
|
|
72
|
-
|
|
73
|
-
if( o instanceof Array )
|
|
74
|
-
{ noTag && (tag = 'array');
|
|
75
|
-
return "<"+tag+">"+o.map(function(el){ return Json2Xml(el,tag); }).join()+"</"+tag+">";
|
|
76
|
-
}
|
|
77
|
-
noTag && (tag = 'r');
|
|
78
|
-
tag=tag.replace( /[^a-z0-9\-]/gi,'_' );
|
|
79
|
-
var oo = {}
|
|
80
|
-
, ret = [ "<"+tag+" "];
|
|
81
|
-
for( let k in o )
|
|
82
|
-
if( typeof o[k] == "object" )
|
|
83
|
-
oo[k] = o[k];
|
|
84
|
-
else
|
|
85
|
-
ret.push( k.replace( /[^a-z0-9\-]/gi,'_' ) + '="'+o[k].toString().replace(/&/gi,'&')+'"');
|
|
86
|
-
if( oo )
|
|
87
|
-
{ ret.push(">");
|
|
88
|
-
for( let k in oo )
|
|
89
|
-
ret.push( Json2Xml( oo[k], k ) );
|
|
90
|
-
ret.push("</"+tag+">");
|
|
91
|
-
}else
|
|
92
|
-
ret.push("/>");
|
|
93
|
-
return ret.join('\n');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
66
|
export function
|
|
97
67
|
obj2node( o, tag, doc )
|
|
98
|
-
{
|
|
99
|
-
if(
|
|
100
|
-
if(
|
|
68
|
+
{ const t = typeof o;
|
|
69
|
+
if( t === 'function'){debugger}
|
|
70
|
+
if( t === 'string' )
|
|
101
71
|
return create(tag,o,doc);
|
|
72
|
+
if( t === 'number' )
|
|
73
|
+
return create(tag,''+o,doc);
|
|
102
74
|
|
|
103
75
|
if( o instanceof Array )
|
|
104
76
|
{ const ret = create('array');
|
|
@@ -163,8 +135,8 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
163
135
|
<xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
164
136
|
<xsl:template match="*"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
165
137
|
<xsl:template match="*[name()='svg']|*[name()='math']"><xsl:apply-templates mode="sanitize" select="."/></xsl:template>
|
|
166
|
-
<xsl:template mode="sanitize" match="*[count(text())=1 and count(*)=0]"><xsl:copy><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"
|
|
167
|
-
<xsl:template mode="sanitize" match="xhtml:*[count(text())=1 and count(*)=0]"><xsl:element name="{local-name()}"><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"
|
|
138
|
+
<xsl:template mode="sanitize" match="*[count(text())=1 and count(*)=0]"><xsl:copy><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"></xsl:value-of></xsl:copy></xsl:template>
|
|
139
|
+
<xsl:template mode="sanitize" match="xhtml:*[count(text())=1 and count(*)=0]"><xsl:element name="{local-name()}"><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"></xsl:value-of></xsl:element></xsl:template>
|
|
168
140
|
<xsl:template mode="sanitize" match="*|@*"><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></xsl:template>
|
|
169
141
|
<xsl:template mode="sanitize" match="text()[normalize-space(.) = '']"/>
|
|
170
142
|
<xsl:template mode="sanitize" match="text()"><dce-text><xsl:copy/></dce-text></xsl:template>
|
|
@@ -203,8 +175,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
203
175
|
<xsl:choose>
|
|
204
176
|
<xsl:when test="//attr">{//attr}</xsl:when>
|
|
205
177
|
<xsl:otherwise>{def}</xsl:otherwise>
|
|
206
|
-
</xsl:choose>
|
|
207
|
-
<xsl:value-of select="."/></xsl:template>
|
|
178
|
+
</xsl:choose><xsl:value-of select="."></xsl:value-of></xsl:template>
|
|
208
179
|
<xsl:template mode="payload" match="attributes"></xsl:template>
|
|
209
180
|
<xsl:template match="/">
|
|
210
181
|
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
@@ -364,17 +335,23 @@ event2slice( x, sliceNames, ev, dce )
|
|
|
364
335
|
ev.type==='init' && cleanSliceValue();
|
|
365
336
|
s.append( obj2node( ev, 'event', d ) );
|
|
366
337
|
if( sel.hasAttribute('slice-value') )
|
|
367
|
-
{
|
|
338
|
+
{ if( el.value === undefined)
|
|
339
|
+
s.removeAttribute('value')
|
|
340
|
+
else
|
|
341
|
+
s.setAttribute('value', el.value );
|
|
368
342
|
const v = xPath( attr( sel, 'slice-value'),s );
|
|
369
343
|
cleanSliceValue();
|
|
370
344
|
s.append( createText( d, v ) );
|
|
371
345
|
}else
|
|
372
|
-
{ const v = el.value
|
|
346
|
+
{ const v = el.value ?? attr( sel, 'value' ) ;
|
|
373
347
|
cleanSliceValue();
|
|
374
|
-
if(
|
|
375
|
-
s.
|
|
348
|
+
if( v === null || v === undefined )
|
|
349
|
+
[...s.childNodes].filter(n=>n.localName!=='event').map(n=>n.remove());
|
|
376
350
|
else
|
|
377
|
-
|
|
351
|
+
if( isString(v) )
|
|
352
|
+
s.append( createText( d, v) );
|
|
353
|
+
else
|
|
354
|
+
s.append( obj2node(v,'value',s.ownerDocument) )
|
|
378
355
|
}
|
|
379
356
|
})
|
|
380
357
|
}
|
|
@@ -445,6 +422,8 @@ export function assureUnique(n, id=0)
|
|
|
445
422
|
}
|
|
446
423
|
export function merge( parent, fromArr )
|
|
447
424
|
{
|
|
425
|
+
if(!fromArr.length)
|
|
426
|
+
return removeChildren(parent);
|
|
448
427
|
const id2old = {};
|
|
449
428
|
for( let c of parent.childNodes)
|
|
450
429
|
{ ASSERT( !id2old[c.dceId] );
|
|
@@ -455,8 +434,8 @@ export function merge( parent, fromArr )
|
|
|
455
434
|
id2old[attr(c, 'data-dce-id') || 0] = c;
|
|
456
435
|
}
|
|
457
436
|
for( let e of [...fromArr] )
|
|
458
|
-
{
|
|
459
|
-
const o = id2old[
|
|
437
|
+
{ const k = attr(e, 'data-dce-id') || e.dceId;
|
|
438
|
+
const o = id2old[ k ];
|
|
460
439
|
if( o )
|
|
461
440
|
{ if( isText(e) )
|
|
462
441
|
{ if( o.nodeValue !== e.nodeValue )
|
|
@@ -466,9 +445,12 @@ export function merge( parent, fromArr )
|
|
|
466
445
|
if( o.childNodes.length || e.childNodes.length )
|
|
467
446
|
merge(o, e.childNodes)
|
|
468
447
|
}
|
|
448
|
+
delete id2old[ k ]
|
|
469
449
|
}else
|
|
470
450
|
parent.append( e )
|
|
471
451
|
}
|
|
452
|
+
for( let v of Object.values(id2old) )
|
|
453
|
+
v.remove();
|
|
472
454
|
}
|
|
473
455
|
export function assureUID(n,attr)
|
|
474
456
|
{ if( !n.hasAttribute(attr) )
|
|
@@ -679,12 +661,17 @@ CustomElement extends HTMLElement
|
|
|
679
661
|
|
|
680
662
|
get dce(){ return dce }
|
|
681
663
|
}
|
|
664
|
+
const registerTag = tag =>
|
|
665
|
+
{
|
|
666
|
+
if( window.customElements.get(tag) !== DceElement )
|
|
667
|
+
window.customElements.define( tag, DceElement);
|
|
668
|
+
};
|
|
682
669
|
if(tag)
|
|
683
|
-
|
|
670
|
+
registerTag(tag);
|
|
684
671
|
else
|
|
685
672
|
{ const t = tagName;
|
|
686
673
|
this.setAttribute('tag', t );
|
|
687
|
-
|
|
674
|
+
registerTag(t);
|
|
688
675
|
const el = document.createElement(t);
|
|
689
676
|
this.getAttributeNames().forEach(a=>el.setAttribute(a,this.getAttribute(a)));
|
|
690
677
|
el.append(...[...this.childNodes].filter( e => e.localName!=='style') );
|
package/demo/a.html
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
7
7
|
<link rel="icon" href="./wc-square.svg"/>
|
|
8
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>
|
|
@@ -40,22 +41,23 @@
|
|
|
40
41
|
</head>
|
|
41
42
|
<body>
|
|
42
43
|
|
|
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>
|
|
59
44
|
|
|
45
|
+
<custom-element tag="dce-1">
|
|
46
|
+
<template >
|
|
47
|
+
<style >
|
|
48
|
+
color:red;
|
|
49
|
+
</style>
|
|
50
|
+
|
|
51
|
+
<u ><slot>red</slot></u>
|
|
52
|
+
</template>
|
|
53
|
+
</custom-element>
|
|
54
|
+
<dce-1></dce-1> *
|
|
55
|
+
<dce-1><template >
|
|
56
|
+
<style >
|
|
57
|
+
color:green;
|
|
58
|
+
</style>
|
|
59
|
+
|
|
60
|
+
<u >green</u>
|
|
61
|
+
</template></dce-1>
|
|
60
62
|
</body>
|
|
61
63
|
</html>
|
package/demo/data-slices.html
CHANGED
package/demo/dom-merge.html
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
<link rel="icon" href="./wc-square.svg"/>
|
|
7
7
|
|
|
8
8
|
<script type="module" src="../http-request.js"></script>
|
|
9
|
-
<script type="module" src="../input-text.js"></script>
|
|
10
9
|
<script type="module" src="../custom-element.js"></script>
|
|
11
10
|
<style>
|
|
12
11
|
@import "./demo.css";
|
|
@@ -94,9 +93,9 @@
|
|
|
94
93
|
<form>
|
|
95
94
|
<label>
|
|
96
95
|
<input type="text"
|
|
97
|
-
value="Type time update"
|
|
96
|
+
value="{//txt ?? 'Type time update'}"
|
|
98
97
|
slice="txt"
|
|
99
|
-
slice-event="
|
|
98
|
+
slice-event="init input"/>
|
|
100
99
|
|
|
101
100
|
<span> Character count:
|
|
102
101
|
<b> {string-length(//slice/txt)} </b>
|
package/demo/http-request.html
CHANGED
|
@@ -31,6 +31,33 @@
|
|
|
31
31
|
<a href="../index.html"><h3><code>custom-element</code> demo</h3></a>
|
|
32
32
|
</nav>
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
<html-demo-element legend="0. url from text to http-request "
|
|
36
|
+
description="read data from arbitrary URL">
|
|
37
|
+
<template>
|
|
38
|
+
<custom-element>
|
|
39
|
+
<template>
|
|
40
|
+
|
|
41
|
+
<button slice="url-string" slice-value="''" slice-event="click">⬇️set blank</button>
|
|
42
|
+
<button slice="url-string" slice-value="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click">⬇️https://pokeapi.co/api/v2/pokemon?limit=6</button>
|
|
43
|
+
<button slice="url-string" slice-value="'https://pokeapi.co/api/v2/pokemon?limit=3'" slice-event="click">⬇️https://pokeapi.co/api/v2/pokemon?limit=3</button>
|
|
44
|
+
<input slice="url-string" value="{ //url-string ?? '' }" style="width:100%"/>
|
|
45
|
+
<button slice="fetch-url" slice-event="click" slice-value="//url-string"> GET </button>
|
|
46
|
+
<http-request
|
|
47
|
+
url="{//fetch-url}"
|
|
48
|
+
slice="request_slice"
|
|
49
|
+
type="text"
|
|
50
|
+
mode="cors"
|
|
51
|
+
></http-request>
|
|
52
|
+
<code>//fetch-url</code> from <code>{//fetch-url}</code><br/>
|
|
53
|
+
<ul><for-each select="//results">
|
|
54
|
+
<li>{@name}</li>
|
|
55
|
+
</for-each></ul>
|
|
56
|
+
</template>
|
|
57
|
+
</custom-element>
|
|
58
|
+
</template>
|
|
59
|
+
</html-demo-element>
|
|
60
|
+
|
|
34
61
|
<html-demo-element legend="1. http-request simplest"
|
|
35
62
|
description="load the list of pokemons">
|
|
36
63
|
<p>Should display 6 image buttons with pokemon name </p>
|
package/demo/local-storage.html
CHANGED
|
@@ -16,14 +16,119 @@
|
|
|
16
16
|
td,th{text-align: right; }
|
|
17
17
|
caption{ padding: 1rem; font-weight: bolder; font-family: sans-serif; }
|
|
18
18
|
</style>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
window.JsonSample = {a:1,b:'B'};
|
|
22
|
+
</script>
|
|
19
23
|
</head>
|
|
20
24
|
<body>
|
|
21
25
|
|
|
22
26
|
<nav>
|
|
23
27
|
<a href="../index.html"><h3><code>custom-element</code> demo</h3></a>
|
|
24
28
|
</nav>
|
|
29
|
+
<main>
|
|
30
|
+
<code>local-storage</code> allows to read and write its value to the key in <code>localStorage</code>.
|
|
31
|
+
The <code>type</code> attribute allows to place the validation constrains to the value: when the value does not
|
|
32
|
+
match the expected type, it would not be assigned, keeping empty <code>value</code> instead.
|
|
33
|
+
</main>
|
|
34
|
+
<html-demo-element legend="0. read localStorage text value"
|
|
35
|
+
description="click should set text-key slice via localStorage change.">
|
|
36
|
+
<template>
|
|
37
|
+
<custom-element>
|
|
38
|
+
<template>
|
|
39
|
+
<local-storage key="textKey" slice="text-key" type="text" live="live"></local-storage>
|
|
40
|
+
<button onclick="localStorage.setItem('textKey','text value')">text value</button>
|
|
41
|
+
<button onclick="localStorage.setItem('textKey','another value')">another value</button>
|
|
42
|
+
//text-key: <code>{//text-key}</code>
|
|
43
|
+
</template>
|
|
44
|
+
</custom-element>
|
|
45
|
+
</template>
|
|
46
|
+
</html-demo-element>
|
|
47
|
+
|
|
48
|
+
<html-demo-element legend="1. always override "
|
|
49
|
+
description="value in localStorage[] should be always reset to ABC. click should set text-key slice via localStorage change.">
|
|
50
|
+
<template>
|
|
51
|
+
<custom-element>
|
|
52
|
+
<template>
|
|
53
|
+
<!-- always reset -->
|
|
54
|
+
<local-storage slice="override-key" key="overrideKey" type="text" value="ABC"></local-storage>
|
|
55
|
+
<button onclick="localStorage.setItem( 'overrideKey','text value')">text value</button>
|
|
56
|
+
<button onclick="localStorage.removeItem('overrideKey' )">clear key</button>
|
|
57
|
+
//override-key: <code>{ //override-key }</code>
|
|
58
|
+
</template>
|
|
59
|
+
</custom-element>
|
|
60
|
+
</template>
|
|
61
|
+
</html-demo-element>
|
|
62
|
+
|
|
63
|
+
<html-demo-element legend="2. from storage with default "
|
|
64
|
+
description="default overridden by button, refresh should preserve updated value">
|
|
65
|
+
<template>
|
|
66
|
+
<custom-element>
|
|
67
|
+
<template>
|
|
68
|
+
<!-- initially set value to DEF and update by button. On reload the value picked from localStorage -->
|
|
69
|
+
<local-storage key="attr2Key" slice="attr2-key" type="text" live="live" slice-value="@value ?? 'DEF'"></local-storage>
|
|
70
|
+
<button onclick="localStorage.clear()">clear localStorage</button>
|
|
71
|
+
<button onclick="localStorage.removeItem('attr2Key')">clear key</button>
|
|
72
|
+
<button onclick="localStorage.setItem('attr2Key','text value')">updated value</button>
|
|
73
|
+
//attr2-key: <code>{//attr2-key}</code>
|
|
74
|
+
</template>
|
|
75
|
+
</custom-element>
|
|
76
|
+
</template>
|
|
77
|
+
</html-demo-element>
|
|
78
|
+
|
|
79
|
+
<html-demo-element legend="3. localStorage type"
|
|
80
|
+
description="type validation happy path. Invalid for type value in storage would be treated as null">
|
|
81
|
+
<template>
|
|
82
|
+
<custom-element>
|
|
83
|
+
<template>
|
|
84
|
+
<local-storage key="dateKey" slice="date-key" type="date" live="live"></local-storage>
|
|
85
|
+
<local-storage key="timeKey" slice="time-key" type="time" live="live"></local-storage>
|
|
86
|
+
<local-storage key="localDateTimeKey" slice="local-date-time" type="datetime-local" live="live"></local-storage>
|
|
87
|
+
<local-storage key="numberKey" slice="number-key" type="number" live="live"></local-storage>
|
|
88
|
+
<local-storage key="jsonKey" slice="json-key" type="json" live="live"></local-storage>
|
|
89
|
+
<input id="typesinput" placeholder="set value" /><button onclick="
|
|
90
|
+
'dateKey,timeKey,localDateTimeKey,numberKey,jsonKey'.split(',')
|
|
91
|
+
.map( k=> localStorage.setItem(k, typesinput.value) )
|
|
92
|
+
"> set to all</button><br/>
|
|
93
|
+
<hr/>
|
|
94
|
+
date-key:
|
|
95
|
+
<button onclick="localStorage.setItem('dateKey', '2024-04-20T03:58:42.131Z')" >2024-04-21T03:58:42.131Z </button>
|
|
96
|
+
<button onclick="localStorage.setItem('dateKey', new Date(Date.now()).toISOString())" >now </button>
|
|
97
|
+
<button onclick="localStorage.setItem('dateKey', 'ABC' )" >date ABC - invalid </button>
|
|
98
|
+
<code>{//date-key }</code><br/>
|
|
99
|
+
time-key:
|
|
100
|
+
<button onclick="localStorage.setItem('timeKey', '13:30')" >13:30 </button>
|
|
101
|
+
<code>{//time-key }</code><br/>
|
|
102
|
+
local-date-time:
|
|
103
|
+
<button onclick="localStorage.setItem('localDateTimeKey', '1977-04-01T14:00:30')" >21977-04-01T14:00:30 - local </button>
|
|
104
|
+
<code>{//local-date-time}</code><br/>
|
|
105
|
+
number-key:
|
|
106
|
+
<button onclick="localStorage.setItem('numberKey', '2024' )" >2024 - number </button>
|
|
107
|
+
<button onclick="localStorage.setItem('numberKey', '24' )" >24 - number </button>
|
|
108
|
+
<button onclick="localStorage.setItem('numberKey', '1.23456e+5' )" >1.23456e+5 </button>
|
|
109
|
+
<button onclick="localStorage.setItem('numberKey', '0001' )" >0001 </button>
|
|
110
|
+
<button onclick="localStorage.setItem('numberKey', '000' )" >000 </button>
|
|
111
|
+
<button onclick="localStorage.setItem('numberKey', '0' )" >0 </button>
|
|
112
|
+
<button onclick="localStorage.setItem('numberKey', 'ABC' )" >ABC - invalid, NaN </button>
|
|
113
|
+
<code>{//number-key }</code> <br/>
|
|
114
|
+
<fieldset>
|
|
115
|
+
<legend>json-key: </legend>
|
|
116
|
+
|
|
117
|
+
<button onclick="localStorage.setItem('jsonKey', JSON.stringify('ABC'))" >'ABC' - string </button>
|
|
118
|
+
<button onclick="localStorage.setItem('jsonKey', JSON.stringify(12.345))" >12.345 - number </button>
|
|
119
|
+
<button onclick="localStorage.setItem('jsonKey', JSON.stringify(window.JsonSample) )" >a:1,b:'B' -json </button>
|
|
120
|
+
<button onclick="localStorage.setItem('jsonKey', 'ABC' )" >ABC - invalid </button><br/>
|
|
121
|
+
json-key:<code><xsl:apply-templates select="//json-key/value/@*|//json-key/text()|//json-key/value/text()" mode="json"></xsl:apply-templates></code>
|
|
122
|
+
</fieldset>
|
|
123
|
+
<xsl:template mode="json" match="*|@*">
|
|
124
|
+
<div>{name()} : {.}</div>
|
|
125
|
+
</xsl:template>
|
|
126
|
+
</template>
|
|
127
|
+
</custom-element>
|
|
128
|
+
</template>
|
|
129
|
+
</html-demo-element>
|
|
25
130
|
|
|
26
|
-
<html-demo-element legend="
|
|
131
|
+
<html-demo-element legend="3. localStorage simplest"
|
|
27
132
|
description="local-storage read only during initial and only render, does not track the changes.">
|
|
28
133
|
<p>Has to produce 12🍒</p>
|
|
29
134
|
<template>
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
<input name="p2" value="def"/>
|
|
31
31
|
<input type="submit" value="params"/>
|
|
32
32
|
</form>
|
|
33
|
-
<button onclick="history.pushState( {},'', 'location.html?pushstate')"
|
|
33
|
+
<button onclick="history.pushState( {},'', 'location-element.html?pushstate')"
|
|
34
34
|
>history.pushState</button>
|
|
35
|
-
<button onclick="history.replaceState( {},'', 'location.html?replaceState#dce1')"
|
|
35
|
+
<button onclick="history.replaceState( {},'', 'location-element.html?replaceState#dce1')"
|
|
36
36
|
>history.replaceState</button>
|
|
37
37
|
|
|
38
38
|
</template>
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
</html-demo-element>
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
<html-demo-element legend="3. External URL as
|
|
116
|
+
<html-demo-element legend="3. External URL as HREF attribute"
|
|
117
117
|
description="url parsed and populated into slice."
|
|
118
118
|
id="dce3">
|
|
119
119
|
<p>Has to produce URL properties</p>
|
|
@@ -121,12 +121,12 @@
|
|
|
121
121
|
<custom-element tag="dce-3" hidden>
|
|
122
122
|
<template>
|
|
123
123
|
|
|
124
|
-
<location-element slice="
|
|
124
|
+
<location-element slice="href-url" href="https://my.example?a=1&b=2#3"></location-element>
|
|
125
125
|
|
|
126
126
|
<xhtml:table>
|
|
127
127
|
<xhtml:tbody>
|
|
128
128
|
<xhtml:tr><xhtml:th><h3>URL properties</h3></xhtml:th></xhtml:tr>
|
|
129
|
-
<for-each select="//slice/
|
|
129
|
+
<for-each select="//slice/href-url/value/@*">
|
|
130
130
|
<xhtml:tr>
|
|
131
131
|
<xhtml:th>{name()}</xhtml:th>
|
|
132
132
|
<xhtml:td>{.}</xhtml:td>
|
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
</xhtml:tbody>
|
|
136
136
|
<xhtml:tbody>
|
|
137
137
|
<xhtml:tr><xhtml:th><h3>URL parameters</h3></xhtml:th></xhtml:tr>
|
|
138
|
-
<for-each select="//slice/
|
|
138
|
+
<for-each select="//slice/href-url/value/params/*">
|
|
139
139
|
<xhtml:tr>
|
|
140
140
|
<xhtml:th>{name()}</xhtml:th>
|
|
141
141
|
<xhtml:td>{.}</xhtml:td>
|
package/demo/s.xml
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
<div xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><!-- wrapping into template to prevent images loading within DCE declaration -->
|
|
2
|
+
<local-storage key="undefined" slice="s" }=""></local-storage>
|
|
3
|
+
|
|
4
|
+
<input placeholder="value for localStorage" slice="s" value="{ //s ?? 'undefined' }" />
|
|
5
|
+
<button>set</button>
|
|
6
|
+
<button slice="sv" slice-value="''" slice-event="click">set blank</button>
|
|
7
|
+
<button slice="sv" slice-value="'/reflect'" slice-event="click">/reflect</button>
|
|
8
|
+
<button slice="sv" slice-value="'/pokemon?limit=6'" slice-event="click">/pokemon?limit=6</button>
|
|
9
|
+
<button slice="sv" slice-value="'/pokemon?limit=3'" slice-event="click">/pokemon?limit=3</button>
|
|
10
|
+
<br />
|
|
11
|
+
<var>undefined</var>:<code>{ //slice/s }</code>
|
|
12
|
+
<br />
|
|
13
|
+
undefined
|
|
14
|
+
</div>
|
package/demo/s.xslt
CHANGED
|
@@ -1,18 +1,57 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml"
|
|
2
|
+
xmlns:dce="urn:schemas-epa-wg:dce" xmlns:exsl="http://exslt.org/common" version="1.0"
|
|
3
|
+
exclude-result-prefixes="exsl">
|
|
3
4
|
<xsl:template match="ignore">
|
|
4
5
|
<xsl:choose>
|
|
5
|
-
<xsl:when test="//attr"
|
|
6
|
-
|
|
6
|
+
<xsl:when test="//attr">
|
|
7
|
+
<xsl:value-of select="//attr"/>
|
|
8
|
+
</xsl:when>
|
|
9
|
+
<xsl:otherwise>
|
|
10
|
+
<xsl:value-of select="def"/>
|
|
11
|
+
</xsl:otherwise>
|
|
7
12
|
</xsl:choose>
|
|
8
|
-
<xsl:value-of select="."
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<xsl:value-of select="//
|
|
15
|
-
</
|
|
13
|
+
<xsl:value-of select="."/>
|
|
14
|
+
</xsl:template>
|
|
15
|
+
<xsl:template mode="payload" match="attributes">
|
|
16
|
+
<dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" data-dce-id="1">
|
|
17
|
+
<http-request xmlns="" url="{concat(//s , '') }" slice="s" data-dce-id="2"/>
|
|
18
|
+
<p xmlns="" data-dce-id="3">Pokemon Count:
|
|
19
|
+
<xsl:value-of select="count(/datadom/slice/s//results)"/>
|
|
20
|
+
</p>
|
|
21
|
+
<xsl:if xmlns:xsl="http://www.w3.org/1999/XSL/Transform" test="count(/datadom/slice/s//results) < 0">
|
|
22
|
+
<h3 xmlns="" data-dce-id="4">loading...</h3>
|
|
23
|
+
</xsl:if>
|
|
24
|
+
<xsl:for-each xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="/datadom/slice/s//results">
|
|
25
|
+
<xsl:variable name="pokeid"
|
|
26
|
+
select="substring-before( substring-after( @url, 'https://pokeapi.co/api/v2/pokemon/'),'/')"/>
|
|
27
|
+
<button xmlns="" data-dce-id="5">
|
|
28
|
+
<xsl:value-of select="@name">
|
|
29
|
+
</xsl:value-of>
|
|
30
|
+
</button>
|
|
31
|
+
</xsl:for-each>
|
|
32
|
+
<xsl:for-each xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="//slice/s/value/*">
|
|
33
|
+
<ul xmlns="" data-dce-id="6">
|
|
34
|
+
<var data-testid="request-section" data-dce-id="7">
|
|
35
|
+
<dce-text data-dce-id="8">
|
|
36
|
+
<xsl:value-of select="name(.)"/>
|
|
37
|
+
</dce-text>
|
|
38
|
+
</var>
|
|
39
|
+
<xsl:for-each select="@*">
|
|
40
|
+
<div data-dce-id="9">
|
|
41
|
+
<var data-dce-id="10">@<xsl:value-of select="local-name(.)"/>
|
|
42
|
+
</var>
|
|
43
|
+
<dce-text data-dce-id="11">
|
|
44
|
+
=
|
|
45
|
+
</dce-text>
|
|
46
|
+
<code data-testid="attr-{local-name(.)}" data-dce-id="12">
|
|
47
|
+
<xsl:value-of select="."/>
|
|
48
|
+
</code>
|
|
49
|
+
</div>
|
|
50
|
+
</xsl:for-each>
|
|
51
|
+
</ul>
|
|
52
|
+
</xsl:for-each>
|
|
53
|
+
</dce-root>
|
|
54
|
+
</xsl:template>
|
|
16
55
|
<xsl:template match="/">
|
|
17
56
|
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
18
57
|
</xsl:template>
|
|
@@ -21,7 +60,7 @@
|
|
|
21
60
|
<xsl:param name="defaultvalue"/>
|
|
22
61
|
<xsl:choose>
|
|
23
62
|
<xsl:when test="//payload/*[@slot=$slotname]">
|
|
24
|
-
|
|
63
|
+
<xsl:copy-of select="//payload/*[@slot=$slotname]"/>
|
|
25
64
|
</xsl:when>
|
|
26
65
|
<xsl:otherwise>
|
|
27
66
|
<xsl:copy-of select="$defaultvalue"/>
|
package/demo/scoped-css.html
CHANGED
package/demo/ss.html
CHANGED
|
@@ -1,32 +1,57 @@
|
|
|
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
|
-
|
|
1
|
+
<dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:dce="urn:schemas-epa-wg:dce" data-dce-id="1"><http-request xmlns="" url="/reflect" slice="page" data-dce-id="2"></http-request><p xmlns="" data-dce-id="3">Pokemon Count: 6</p>
|
|
2
|
+
<ul xmlns="" data-dce-id="6">
|
|
3
|
+
<var data-testid="request-section" data-dce-id="7"><dce-text data-dce-id="8">request</dce-text></var><div data-dce-id="9">
|
|
4
|
+
<var data-dce-id="10">@url</var><dce-text data-dce-id="11">
|
|
5
|
+
=
|
|
6
|
+
</dce-text><code data-testid="attr-url" data-dce-id="12">/reflect</code>
|
|
7
|
+
</div>
|
|
8
|
+
<div data-dce-id="9-1">
|
|
9
|
+
<var data-dce-id="10">@data-dce-id</var><dce-text data-dce-id="11">
|
|
10
|
+
=
|
|
11
|
+
</dce-text><code data-testid="attr-data-dce-id" data-dce-id="12">2</code>
|
|
12
|
+
</div>
|
|
13
|
+
</ul>
|
|
14
|
+
<ul xmlns="" data-dce-id="6-1">
|
|
15
|
+
<var data-testid="request-section" data-dce-id="7"><dce-text data-dce-id="8">response</dce-text></var><div data-dce-id="9">
|
|
16
|
+
<var data-dce-id="10">@ok</var><dce-text data-dce-id="11">
|
|
17
|
+
=
|
|
18
|
+
</dce-text><code data-testid="attr-ok" data-dce-id="12">true</code>
|
|
19
|
+
</div>
|
|
20
|
+
<div data-dce-id="9-1">
|
|
21
|
+
<var data-dce-id="10">@status</var><dce-text data-dce-id="11">
|
|
22
|
+
=
|
|
23
|
+
</dce-text><code data-testid="attr-status" data-dce-id="12">200</code>
|
|
24
|
+
</div>
|
|
25
|
+
<div data-dce-id="9-2">
|
|
26
|
+
<var data-dce-id="10">@statusText</var><dce-text data-dce-id="11">
|
|
27
|
+
=
|
|
28
|
+
</dce-text><code data-testid="attr-statusText" data-dce-id="12">OK</code>
|
|
29
|
+
</div>
|
|
30
|
+
<div data-dce-id="9-3">
|
|
31
|
+
<var data-dce-id="10">@type</var><dce-text data-dce-id="11">
|
|
32
|
+
=
|
|
33
|
+
</dce-text><code data-testid="attr-type" data-dce-id="12">basic</code>
|
|
34
|
+
</div>
|
|
35
|
+
<div data-dce-id="9-4">
|
|
36
|
+
<var data-dce-id="10">@url</var><dce-text data-dce-id="11">
|
|
37
|
+
=
|
|
38
|
+
</dce-text><code data-testid="attr-url" data-dce-id="12">http://localhost:5173/reflect</code>
|
|
39
|
+
</div>
|
|
40
|
+
<div data-dce-id="9-5">
|
|
41
|
+
<var data-dce-id="10">@redirected</var><dce-text data-dce-id="11">
|
|
42
|
+
=
|
|
43
|
+
</dce-text><code data-testid="attr-redirected" data-dce-id="12">false</code>
|
|
44
|
+
</div>
|
|
45
|
+
</ul><button xmlns="" data-dce-id="5">bulbasaur</button><button xmlns="" data-dce-id="5-1">ivysaur</button><button xmlns="" data-dce-id="5-2">venusaur</button><button xmlns="" data-dce-id="5-3">charmander</button><button xmlns="" data-dce-id="5-4">charmeleon</button><button xmlns="" data-dce-id="5-5">charizard</button>
|
|
46
|
+
<ul xmlns="" data-dce-id="6-2">
|
|
47
|
+
<var data-testid="request-section" data-dce-id="7"><dce-text data-dce-id="8">data</dce-text></var><div data-dce-id="9">
|
|
48
|
+
<var data-dce-id="10">@count</var><dce-text data-dce-id="11">
|
|
49
|
+
=
|
|
50
|
+
</dce-text><code data-testid="attr-count" data-dce-id="12">1279</code>
|
|
51
|
+
</div>
|
|
52
|
+
<div data-dce-id="9-1">
|
|
53
|
+
<var data-dce-id="10">@next</var><dce-text data-dce-id="11">
|
|
54
|
+
=
|
|
55
|
+
</dce-text><code data-testid="attr-next" data-dce-id="12">https://pokeapi.co/api/v2/pokemon?offset=6&limit=6</code>
|
|
56
|
+
</div>
|
|
57
|
+
</ul></dce-root>
|
package/http-request.js
CHANGED
|
@@ -2,14 +2,13 @@ const attr = (el, attr)=> el.getAttribute(attr);
|
|
|
2
2
|
|
|
3
3
|
export class HttpRequestElement extends HTMLElement
|
|
4
4
|
{
|
|
5
|
-
static
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
5
|
+
static observedAttributes =
|
|
6
|
+
[ 'value' // populated from localStorage, if defined initially, sets the value in storage
|
|
7
|
+
, 'slice'
|
|
8
|
+
, 'url'
|
|
9
|
+
, 'method'
|
|
10
|
+
, 'header-accept'
|
|
11
|
+
];
|
|
13
12
|
|
|
14
13
|
get requestHeaders()
|
|
15
14
|
{ const ret = {};
|
|
@@ -23,30 +22,67 @@ export class HttpRequestElement extends HTMLElement
|
|
|
23
22
|
return ret
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
disconnectedCallback(){ this
|
|
25
|
+
disconnectedCallback(){ this.#destroy?.(); }
|
|
27
26
|
|
|
28
27
|
connectedCallback()
|
|
29
|
-
{
|
|
30
|
-
|
|
28
|
+
{
|
|
29
|
+
setTimeout(()=>this.fetch(),0)
|
|
30
|
+
}
|
|
31
|
+
#inProgressUrl = ''
|
|
32
|
+
#destroy = ()=>{}
|
|
33
|
+
|
|
34
|
+
async fetch()
|
|
35
|
+
{
|
|
36
|
+
if( !this.closest('body') )
|
|
37
|
+
return;
|
|
38
|
+
const url = attr(this, 'url') || '';
|
|
39
|
+
if( !url )
|
|
40
|
+
{ this.#destroy?.();
|
|
41
|
+
return this.value = {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if( this.#inProgressUrl === url )
|
|
45
|
+
return ;
|
|
46
|
+
|
|
47
|
+
this.#inProgressUrl = url;
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
this.#destroy = ()=> { controller.abort(this.localName+' disconnected'); this.#inProgressUrl = ''; }
|
|
31
50
|
|
|
32
|
-
const
|
|
33
|
-
, request = { ...this.requestProps, headers: this.requestHeaders }
|
|
51
|
+
const request = { ...this.requestProps, headers: this.requestHeaders }
|
|
34
52
|
, slice = { request }
|
|
35
53
|
, update = () => this.dispatchEvent( new Event('change') );
|
|
36
54
|
this.value = slice;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
|
|
56
|
+
update();
|
|
57
|
+
const response = await fetch(url,{ ...this.requestProps, signal: controller.signal, headers: this.requestHeaders })
|
|
58
|
+
, r = {headers: {}};
|
|
59
|
+
[...response.headers].map( ([k,v]) => r.headers[k] = v );
|
|
60
|
+
'ok,status,statusText,type,url,redirected'.split(',').map( k=> r[k] = response[k] )
|
|
61
|
+
|
|
62
|
+
slice.response = r;
|
|
63
|
+
update();
|
|
64
|
+
if( r.headers['content-type']?.includes('json'))
|
|
65
|
+
try
|
|
66
|
+
{ slice.data = await response.json();
|
|
67
|
+
update();
|
|
68
|
+
}catch(_e){}
|
|
49
69
|
}
|
|
70
|
+
|
|
71
|
+
attributeChangedCallback(name, oldValue, newValue)
|
|
72
|
+
{ if( name === 'url' )
|
|
73
|
+
{ if( oldValue !== newValue)
|
|
74
|
+
{
|
|
75
|
+
oldValue && this.#destroy?.();
|
|
76
|
+
if( newValue )
|
|
77
|
+
setTimeout(()=>this.fetch(),10)
|
|
78
|
+
else
|
|
79
|
+
{ this.value = {}
|
|
80
|
+
setTimeout(()=>this.dispatchEvent( new Event('change') ),10)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
50
86
|
}
|
|
51
87
|
|
|
52
88
|
window.customElements.define( 'http-request', HttpRequestElement );
|
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.21",
|
|
5
5
|
"js-types-syntax": "typescript",
|
|
6
6
|
"description-markup": "markdown",
|
|
7
7
|
"contributions": {
|
|
@@ -108,4 +108,4 @@
|
|
|
108
108
|
]
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
|
-
}
|
|
111
|
+
}
|
package/ide/web-types-xsl.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.21",
|
|
5
5
|
"js-types-syntax": "typescript",
|
|
6
6
|
"description-markup": "markdown",
|
|
7
7
|
"contributions": {
|
|
@@ -864,4 +864,4 @@
|
|
|
864
864
|
]
|
|
865
865
|
}
|
|
866
866
|
}
|
|
867
|
-
}
|
|
867
|
+
}
|
package/index.js
ADDED
package/local-storage.js
CHANGED
|
@@ -2,44 +2,81 @@ const string2value = (type, v) =>
|
|
|
2
2
|
{ if( type === 'text')
|
|
3
3
|
return v;
|
|
4
4
|
if( type === 'json')
|
|
5
|
-
return JSON.parse( v );
|
|
5
|
+
try{ return JSON.parse( v );}
|
|
6
|
+
catch(err){ return null }
|
|
6
7
|
const el = document.createElement('input');
|
|
7
8
|
el.setAttribute('type',type);
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
if( 'number' === type )
|
|
10
|
+
{ el.value = v;
|
|
11
|
+
return el.valueAsNumber;
|
|
12
|
+
}
|
|
13
|
+
if( 'date' === type )
|
|
14
|
+
{ if(!v) return null;
|
|
15
|
+
el.valueAsDate = new Date( v );
|
|
16
|
+
return el.value;
|
|
17
|
+
}
|
|
18
|
+
el.value = v;
|
|
19
|
+
return el.value;
|
|
10
20
|
};
|
|
11
21
|
|
|
22
|
+
let originalSetItem,originalRemoveItem,originalClear;
|
|
23
|
+
|
|
24
|
+
function ensureTrackLocalStorage()
|
|
25
|
+
{ if( originalSetItem )
|
|
26
|
+
return;
|
|
27
|
+
originalSetItem = localStorage.setItem;
|
|
28
|
+
localStorage.setItem = function( key, value, ...rest )
|
|
29
|
+
{ originalSetItem.apply(this, [ key, value, ...rest ]);
|
|
30
|
+
window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
|
|
31
|
+
};
|
|
32
|
+
originalRemoveItem = localStorage.removeItem;
|
|
33
|
+
localStorage.removeItem = function( key, ...rest )
|
|
34
|
+
{ originalRemoveItem.apply(this, [ key, ...rest ]);
|
|
35
|
+
window.dispatchEvent( new CustomEvent('local-storage',{detail:{key}}) );
|
|
36
|
+
};
|
|
37
|
+
originalClear = localStorage.clear;
|
|
38
|
+
localStorage.clear = function( ...rest )
|
|
39
|
+
{ originalClear.apply(this, [ ...rest ]);
|
|
40
|
+
window.dispatchEvent( new CustomEvent('local-storage',{detail:{}}) );
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
12
44
|
export function localStorageSetItem(key, value)
|
|
13
45
|
{ localStorage.setItem(key, value);
|
|
14
46
|
window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
|
|
15
47
|
}
|
|
16
48
|
export class LocalStorageElement extends HTMLElement
|
|
17
49
|
{
|
|
18
|
-
static
|
|
19
|
-
|
|
50
|
+
static observedAttributes=
|
|
51
|
+
[ 'value' // populated from localStorage, if defined initially, sets the value in storage
|
|
20
52
|
, 'slice'
|
|
21
53
|
, 'key'
|
|
22
54
|
, 'type' // `text|json`, defaults to text, other types are compatible with INPUT field
|
|
23
55
|
, 'live' // monitors localStorage change
|
|
24
56
|
];
|
|
25
|
-
|
|
57
|
+
|
|
58
|
+
#value;
|
|
59
|
+
get value(){ return this.#value ===null ? undefined: this.#value }
|
|
60
|
+
set value(o){ return this.#value = o; }
|
|
26
61
|
|
|
27
62
|
async connectedCallback()
|
|
28
63
|
{
|
|
29
64
|
const attr = attr => this.getAttribute(attr)
|
|
30
|
-
|
|
31
|
-
{ this
|
|
65
|
+
, fromStorage = ()=>
|
|
66
|
+
{ this.#value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
|
|
32
67
|
this.dispatchEvent( new Event('change') )
|
|
33
68
|
}
|
|
34
|
-
|
|
69
|
+
this.#value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
|
|
70
|
+
|
|
35
71
|
if( this.hasAttribute('value'))
|
|
36
|
-
localStorageSetItem( attr(
|
|
72
|
+
localStorageSetItem( attr( 'key' ), this.#value = attr( 'value' ) )
|
|
37
73
|
else
|
|
38
74
|
fromStorage()
|
|
39
75
|
|
|
40
76
|
if( this.hasAttribute('live') )
|
|
41
|
-
{ const listener = (e => e.detail.key === attr( 'key' ) && fromStorage());
|
|
77
|
+
{ const listener = (e => (e.detail.key === attr( 'key' ) || !e.detail.key ) && fromStorage());
|
|
42
78
|
window.addEventListener( 'local-storage', listener );
|
|
79
|
+
ensureTrackLocalStorage();
|
|
43
80
|
this._destroy = ()=> window.removeEventListener('local-storage', listener );
|
|
44
81
|
}
|
|
45
82
|
}
|
|
@@ -47,4 +84,4 @@ export class LocalStorageElement extends HTMLElement
|
|
|
47
84
|
}
|
|
48
85
|
|
|
49
86
|
window.customElements.define( 'local-storage', LocalStorageElement );
|
|
50
|
-
export default LocalStorageElement;
|
|
87
|
+
export default LocalStorageElement;
|
package/location-element.js
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
|
-
const
|
|
1
|
+
const attr = ( el, attr )=> el.getAttribute( attr );
|
|
2
|
+
|
|
3
|
+
let originalHistory;
|
|
4
|
+
|
|
5
|
+
function ensureTrackLocationChange()
|
|
6
|
+
{ if( originalHistory )
|
|
7
|
+
return;
|
|
8
|
+
originalHistory = {};
|
|
9
|
+
'back,forward,go,pushState,replaceState'.split(',').forEach( k =>
|
|
10
|
+
{
|
|
11
|
+
originalHistory[ k ] = history[ k ];
|
|
12
|
+
history[ k ] = function(...rest )
|
|
13
|
+
{
|
|
14
|
+
originalHistory[k].apply( history, rest );
|
|
15
|
+
window.dispatchEvent( new CustomEvent('dce-location',{detail:{ k }}) );
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
2
19
|
|
|
3
20
|
export class LocationElement extends HTMLElement
|
|
4
21
|
{
|
|
5
|
-
static
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
22
|
+
static observedAttributes=
|
|
23
|
+
[ 'value' // populated from url
|
|
24
|
+
, 'slice'
|
|
25
|
+
, 'href' // url to be parsed. When omitted window.location is used.
|
|
26
|
+
, 'type' // `text|json`, defaults to text, other types are compatible with INPUT field
|
|
27
|
+
, 'live' // monitors history change, applicable only when href is omitted.
|
|
28
|
+
];
|
|
12
29
|
|
|
13
30
|
constructor()
|
|
14
31
|
{
|
|
15
32
|
super();
|
|
16
33
|
const state = {}
|
|
17
|
-
, listener =
|
|
34
|
+
, listener = () => setTimeout( propagateSlice,1 )
|
|
18
35
|
, propagateSlice = ()=>
|
|
19
|
-
{ const urlStr = attr(this,'
|
|
20
|
-
|
|
36
|
+
{ const urlStr = attr(this,'href')
|
|
37
|
+
if(!urlStr)
|
|
38
|
+
ensureTrackLocationChange();
|
|
39
|
+
const url = urlStr? new URL(urlStr, window.location) : window.location;
|
|
21
40
|
|
|
22
41
|
const params= {}
|
|
23
42
|
const search = new URLSearchParams(url.search);
|
|
@@ -36,24 +55,30 @@ export class LocationElement extends HTMLElement
|
|
|
36
55
|
{
|
|
37
56
|
if( !state.listener && this.hasAttribute('live') )
|
|
38
57
|
{ state.listener = 1;
|
|
39
|
-
window.addEventListener(
|
|
40
|
-
window.addEventListener( '
|
|
58
|
+
window.navigation?.addEventListener("navigate", listener );
|
|
59
|
+
window.addEventListener( 'popstate' , listener );
|
|
60
|
+
window.addEventListener( 'hashchange' , listener );
|
|
61
|
+
window.addEventListener( 'dce-location' , listener );
|
|
41
62
|
}
|
|
42
63
|
propagateSlice();
|
|
43
64
|
return s || {}
|
|
44
65
|
}
|
|
45
66
|
this._destroy = ()=>
|
|
46
67
|
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{ window.removeEventListener('popstate' , listener);
|
|
51
|
-
window.removeEventListener('hashchange', listener);
|
|
52
|
-
}
|
|
68
|
+
window.removeEventListener('popstate' , listener);
|
|
69
|
+
window.removeEventListener('hashchange' , listener);
|
|
70
|
+
window.removeEventListener('dce-location', listener);
|
|
53
71
|
delete state.listener;
|
|
54
72
|
};
|
|
55
73
|
|
|
56
74
|
}
|
|
75
|
+
attributeChangedCallback(name, oldValue, newValue)
|
|
76
|
+
{
|
|
77
|
+
if('href'!== name)
|
|
78
|
+
return;
|
|
79
|
+
this.sliceInit && this.sliceInit();
|
|
80
|
+
}
|
|
81
|
+
|
|
57
82
|
connectedCallback(){ this.sliceInit() }
|
|
58
83
|
disconnectedCallback(){ this._destroy() }
|
|
59
84
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epa-wg/custom-element",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Declarative Custom Element as W3C proposal PoC with native(XSLT) based templating",
|
|
5
5
|
"browser": "custom-element.js",
|
|
6
6
|
"module": "custom-element.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./
|
|
8
|
+
".": "./index.js",
|
|
9
9
|
"./package.json": "./package.json",
|
|
10
10
|
"./CustomElement": "./custom-element.js"
|
|
11
11
|
},
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dev:help": "echo \"needed for sandbox demo\"",
|
|
17
17
|
"dev": "bash bin/stackblitz.sh",
|
|
18
18
|
"start": "npm i --no-save @web/dev-server && web-dev-server --node-resolve",
|
|
19
|
-
"test": "echo \"test would reside in https://github.com/EPA-WG/custom-element-
|
|
19
|
+
"test": "echo \"test would reside in https://github.com/EPA-WG/custom-element-dist\" && exit 0",
|
|
20
20
|
"typings": "npx -p typescript tsc custom-element.js --declaration --allowJs --emitDeclarationOnly "
|
|
21
21
|
},
|
|
22
22
|
"type": "module",
|
package/input-text.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export class InputTextElement extends HTMLElement
|
|
2
|
-
{
|
|
3
|
-
constructor()
|
|
4
|
-
{
|
|
5
|
-
super();
|
|
6
|
-
const i = this.ownerDocument.createElement('input');
|
|
7
|
-
for(let a of this.attributes)
|
|
8
|
-
a.namespaceURI ? i.setAttributeNS(a.namespaceURI,a.name,a.value) : i.setAttribute(a.name,a.value)
|
|
9
|
-
this.append(i)
|
|
10
|
-
}
|
|
11
|
-
get value(){ return this.firstChild.value }
|
|
12
|
-
set value(v){ return this.firstChild.value = v }
|
|
13
|
-
disconnectedCallback(){ }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
window.customElements.define( 'input-text', InputTextElement );
|
|
17
|
-
export default InputTextElement;
|