@epa-wg/custom-element 0.0.11 → 0.0.13
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/.idea/inspectionProfiles/Project_Default.xml +22 -7
- package/.idea/misc.xml +4 -5
- package/.vs/VSWorkspaceState.json +8 -0
- package/.vs/custom-element/FileContentIndex/1487e471-3751-47bc-a499-d78eda924eda.vsidx +0 -0
- package/.vs/custom-element/v17/.wsuo +0 -0
- package/.vs/slnx.sqlite +0 -0
- package/README.md +71 -19
- package/custom-element.js +241 -66
- package/demo/a.html +25 -11
- package/demo/confused.svg +36 -36
- package/demo/dom-merge.html +124 -0
- package/demo/embed-1.html +2 -2
- package/demo/external-template.html +9 -7
- package/demo/hex-grid-dce.html +183 -0
- package/demo/hex-grid-transform.png +0 -0
- package/demo/hex-grid.html +66 -0
- package/demo/html-template.html +125 -12
- package/demo/html-template.xhtml +44 -44
- package/demo/html-template.xml +44 -44
- package/demo/http-request.html +44 -57
- package/demo/local-storage.html +23 -18
- package/demo/location-element.html +59 -58
- package/demo/s.xml +1 -0
- package/demo/s.xslt +159 -0
- package/demo/scoped-css.html +170 -0
- package/demo/ss.html +32 -0
- package/demo/table.xml +24 -24
- package/demo/table.xsl +292 -292
- package/demo/template.xsl +45 -45
- package/demo/tree.xml +24 -24
- package/demo/tree.xsl +32 -32
- package/demo/xhtml-template.xhtml +44 -44
- package/demo/z.html +42 -0
- package/demo/z.xml +60 -0
- package/http-request.js +28 -37
- package/index.html +23 -19
- package/input-text.js +17 -0
- package/local-storage.js +28 -35
- package/location-element.js +15 -16
- package/package.json +1 -1
- package/0/a.html +0 -19
- package/0/a.xml +0 -10
- package/0/a.xsl +0 -66
- package/0/a1.xsl +0 -38
- package/0/ab.xsl +0 -23
- package/0/az.xml +0 -30
- package/0/b.html +0 -90
|
@@ -1,22 +1,37 @@
|
|
|
1
1
|
<component name="InspectionProjectProfileManager">
|
|
2
2
|
<profile version="1.0">
|
|
3
3
|
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
|
+
<option name="myValues">
|
|
6
|
+
<value>
|
|
7
|
+
<list size="1">
|
|
8
|
+
<item index="0" class="java.lang.String" itemvalue="slice" />
|
|
9
|
+
</list>
|
|
10
|
+
</value>
|
|
11
|
+
</option>
|
|
12
|
+
<option name="myCustomValuesEnabled" value="true" />
|
|
13
|
+
</inspection_tool>
|
|
4
14
|
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
15
|
<option name="myValues">
|
|
6
16
|
<value>
|
|
7
|
-
<list size="
|
|
17
|
+
<list size="17">
|
|
8
18
|
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
|
9
19
|
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
|
10
20
|
<item index="2" class="java.lang.String" itemvalue="comment" />
|
|
11
21
|
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
|
12
22
|
<item index="4" class="java.lang.String" itemvalue="embed" />
|
|
13
23
|
<item index="5" class="java.lang.String" itemvalue="script" />
|
|
14
|
-
<item index="6" class="java.lang.String" itemvalue="
|
|
15
|
-
<item index="7" class="java.lang.String" itemvalue="
|
|
16
|
-
<item index="8" class="java.lang.String" itemvalue="
|
|
17
|
-
<item index="9" class="java.lang.String" itemvalue="dce-
|
|
18
|
-
<item index="10" class="java.lang.String" itemvalue="
|
|
19
|
-
<item index="11" class="java.lang.String" itemvalue="
|
|
24
|
+
<item index="6" class="java.lang.String" itemvalue="html-demo-element" />
|
|
25
|
+
<item index="7" class="java.lang.String" itemvalue="custom-element" />
|
|
26
|
+
<item index="8" class="java.lang.String" itemvalue="local-storage" />
|
|
27
|
+
<item index="9" class="java.lang.String" itemvalue="dce-1" />
|
|
28
|
+
<item index="10" class="java.lang.String" itemvalue="xhtml:table" />
|
|
29
|
+
<item index="11" class="java.lang.String" itemvalue="xhtml:tbody" />
|
|
30
|
+
<item index="12" class="java.lang.String" itemvalue="xhtml:tr" />
|
|
31
|
+
<item index="13" class="java.lang.String" itemvalue="xhtml:th" />
|
|
32
|
+
<item index="14" class="java.lang.String" itemvalue="xhtml:td" />
|
|
33
|
+
<item index="15" class="java.lang.String" itemvalue="xhtml:tfoot" />
|
|
34
|
+
<item index="16" class="java.lang.String" itemvalue="dce-2" />
|
|
20
35
|
</list>
|
|
21
36
|
</value>
|
|
22
37
|
</option>
|
package/.idea/misc.xml
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
</component>
|
|
1
|
+
<project version="4">
|
|
2
|
+
<component name="ProjectRootManager">
|
|
3
|
+
<output url="file://$PROJECT_DIR$/out" />
|
|
4
|
+
</component>
|
|
6
5
|
</project>
|
|
Binary file
|
|
Binary file
|
package/.vs/slnx.sqlite
ADDED
|
Binary file
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# custom-element
|
|
2
2
|
`Declarative Custom Element` is a part of pure `Declarative Web Application` stack. A proof of concept as a part of
|
|
3
|
-
[WCCG in Declarative custom elements](https://github.com/w3c/webcomponents-cg/issues/32#issuecomment-1321037301)
|
|
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
4
|
discussion. The functionality of DCE and its data access does not require programming using JavaScript.
|
|
5
5
|
|
|
6
6
|
It allows to define custom HTML tag with template filled from slots, attributes and data `slice` as of now from
|
|
@@ -32,13 +32,13 @@ yarn add @epa-wg/custom-element
|
|
|
32
32
|
## [Live demo 🔗][demo-url]
|
|
33
33
|
```html
|
|
34
34
|
<custom-element tag="pokemon-tile" hidden>
|
|
35
|
-
<h3
|
|
35
|
+
<h3>{title}</h3> <!-- title is an attribute in instance
|
|
36
36
|
mapped into /*/attributes/title -->
|
|
37
37
|
<xsl:if test="//smile"> <!-- data-smile DCE instance attribute,
|
|
38
38
|
mapped into /*/dataset/smile
|
|
39
39
|
used in condition -->
|
|
40
40
|
<!-- data-smile DCE instance attribute, used as HTML -->
|
|
41
|
-
<div>Smile as:
|
|
41
|
+
<div>Smile as: {//smile} </div>
|
|
42
42
|
</xsl:if>
|
|
43
43
|
<!-- image would not be visible in sandbox, see live demo -->
|
|
44
44
|
<img src="https://unpkg.com/pokeapi-sprites@2.0.2/sprites/pokemon/other/dream-world/{pokemon-id}.svg"
|
|
@@ -78,6 +78,10 @@ generates HTML
|
|
|
78
78
|
</pokemon-tile>
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
[![responsive hex grid demo][hex-grid-image] responsive hex-grid demo][hex-grid-url]
|
|
82
|
+
, look into sources for samples of CSS encapsulation and external template use.
|
|
83
|
+
|
|
84
|
+
|
|
81
85
|
# Implementation notes
|
|
82
86
|
## Life cycle
|
|
83
87
|
### `custom-element` declaration
|
|
@@ -126,6 +130,44 @@ allows to refer the template withing external document
|
|
|
126
130
|
|
|
127
131
|
|
|
128
132
|
# template syntax
|
|
133
|
+
[Scoped CSS][css-demo-url] live demo
|
|
134
|
+
## styles encapsulation
|
|
135
|
+
DCE can have the own styles which would be scoped to the instances.
|
|
136
|
+
In order to prevent the style leaking, it has to be defined withing `template` tag:
|
|
137
|
+
```html
|
|
138
|
+
<custom-element>
|
|
139
|
+
<template>
|
|
140
|
+
<style>
|
|
141
|
+
color:green;
|
|
142
|
+
button{ color: blue; }
|
|
143
|
+
</style>
|
|
144
|
+
<label> green <button>blue</button> </label>
|
|
145
|
+
</template>
|
|
146
|
+
</custom-element>
|
|
147
|
+
```
|
|
148
|
+
<fieldset>
|
|
149
|
+
<label style="color: green"> green <button style="color: blue">blue</button> </label>
|
|
150
|
+
</fieldset>
|
|
151
|
+
|
|
152
|
+
### override style for instance
|
|
153
|
+
In same way as in DCE itself:
|
|
154
|
+
```html
|
|
155
|
+
<custom-element tag="dce-2">
|
|
156
|
+
<template><!-- template needed to avoid styles leaking into global HTML -->
|
|
157
|
+
<style>
|
|
158
|
+
button{ border: 0.2rem dashed blue; }
|
|
159
|
+
</style>
|
|
160
|
+
<button><slot>Blue borders</slot></button>
|
|
161
|
+
</template>
|
|
162
|
+
</custom-element>
|
|
163
|
+
<dce-2>dashed blue</dce-2>
|
|
164
|
+
<dce-2>
|
|
165
|
+
<template> <!-- template needed to avoid styles leaking into global HTML -->
|
|
166
|
+
<style>button{border-color:red;}</style>
|
|
167
|
+
Red border
|
|
168
|
+
</template>
|
|
169
|
+
</dce-2>
|
|
170
|
+
```
|
|
129
171
|
## Attributes
|
|
130
172
|
curly braces `{}` in attributes implemented as [attribute value template](https://www.w3.org/TR/xslt20/#attribute-value-templates)
|
|
131
173
|
|
|
@@ -165,21 +207,28 @@ template engine.
|
|
|
165
207
|
|
|
166
208
|
# troubleshooting
|
|
167
209
|
## HTML parser is not compatible with templates
|
|
168
|
-
On many tags like `table`, or link `a` the attempt to use XSLT operations could lead to DOM order
|
|
169
|
-
in template. In such cases the `
|
|
210
|
+
On many tags like `table`, or link `a` the attempt to use XSLT operations could lead to DOM order mismatch to given
|
|
211
|
+
in template. In such cases the `xhtml:` prefix in front of troubled tag would solve the parsing.
|
|
170
212
|
|
|
171
213
|
```html
|
|
172
214
|
<custom-element tag="dce-2" hidden>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
</
|
|
181
|
-
|
|
182
|
-
|
|
215
|
+
<local-storage key="basket" slice="basket" live type="json"></local-storage>
|
|
216
|
+
<xhtml:table xmlns:xhtml="http://www.w3.org/1999/xhtml" >
|
|
217
|
+
<xhtml:tbody>
|
|
218
|
+
<xsl:for-each select="//basket/@*">
|
|
219
|
+
<xhtml:tr>
|
|
220
|
+
<xhtml:th> {name()} </xhtml:th>
|
|
221
|
+
<xhtml:td> {.} </xhtml:td>
|
|
222
|
+
</xhtml:tr>
|
|
223
|
+
</xsl:for-each>
|
|
224
|
+
</xhtml:tbody>
|
|
225
|
+
<xhtml:tfoot>
|
|
226
|
+
<xhtml:tr>
|
|
227
|
+
<xhtml:td><slot>🤔</slot></xhtml:td>
|
|
228
|
+
<xhtml:th> {sum(//slice/basket/@*)} </xhtml:th>
|
|
229
|
+
</xhtml:tr>
|
|
230
|
+
</xhtml:tfoot>
|
|
231
|
+
</xhtml:table>
|
|
183
232
|
</custom-element>
|
|
184
233
|
```
|
|
185
234
|
See [demo source](demo/local-storage.html) for detailed sample.
|
|
@@ -206,21 +255,24 @@ run transformation under debugger.
|
|
|
206
255
|
* try to add as attribute you could observe and put the value of node name or text to identify the current location in data
|
|
207
256
|
within template
|
|
208
257
|
```xml
|
|
209
|
-
<b title="{name(*)} : {text()}">xml tag name
|
|
258
|
+
<b title="{name(*)} : {text()}">xml tag name: <xsl:value-of select='name()'/></b>
|
|
210
259
|
```
|
|
211
260
|
|
|
212
261
|
[git-url]: https://github.com/EPA-WG/custom-element
|
|
213
262
|
[git-test-url]: https://github.com/EPA-WG/custom-element-test
|
|
214
263
|
[demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/index.html
|
|
264
|
+
[css-demo-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/scoped-css.html
|
|
265
|
+
[hex-grid-url]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/hex-grid.html
|
|
266
|
+
[hex-grid-image]: demo/hex-grid-transform.png
|
|
215
267
|
[local-storage-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/local-storage.html
|
|
216
268
|
[http-request-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/http-request.html
|
|
217
269
|
[location-demo]: https://unpkg.com/@epa-wg/custom-element@0.0/demo/location.html
|
|
218
270
|
[github-image]: https://cdnjs.cloudflare.com/ajax/libs/octicons/8.5.0/svg/mark-github.svg
|
|
219
271
|
[npm-image]: https://img.shields.io/npm/v/@epa-wg/custom-element.svg
|
|
220
272
|
[npm-url]: https://npmjs.org/package/@epa-wg/custom-element
|
|
221
|
-
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
222
|
-
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
223
|
-
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.
|
|
273
|
+
[coverage-image]: https://unpkg.com/@epa-wg/custom-element-test@0.0.13/coverage/coverage.svg
|
|
274
|
+
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.13/coverage/lcov-report/index.html
|
|
275
|
+
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.13/storybook-static/index.html?path=/story/welcome--introduction
|
|
224
276
|
[sandbox-url]: https://stackblitz.com/github/EPA-WG/custom-element?file=index.html
|
|
225
277
|
[webcomponents-url]: https://www.webcomponents.org/element/@epa-wg/custom-element
|
|
226
278
|
[webcomponents-img]: https://img.shields.io/badge/webcomponents.org-published-blue.svg
|
package/custom-element.js
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
|
-
const
|
|
2
|
-
,
|
|
3
|
-
,
|
|
1
|
+
const XSL_NS_URL = 'http://www.w3.org/1999/XSL/Transform'
|
|
2
|
+
, HTML_NS_URL = 'http://www.w3.org/1999/xhtml'
|
|
3
|
+
, EXSL_NS_URL = 'http://exslt.org/common'
|
|
4
|
+
, DCE_NS_URL ="urn:schemas-epa-wg:dce";
|
|
4
5
|
|
|
5
6
|
// const log = x => console.debug( new XMLSerializer().serializeToString( x ) );
|
|
6
7
|
|
|
7
|
-
const attr = (el, attr)=> el.getAttribute(attr)
|
|
8
|
+
const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
9
|
+
, isText = e => e.nodeType === 3
|
|
8
10
|
, create = ( tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElement( tag ))
|
|
9
|
-
,
|
|
11
|
+
, createText = ( d, t) => (d.ownerDocument || d ).createTextNode( t )
|
|
12
|
+
, createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, tag ))
|
|
13
|
+
, xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
|
|
14
|
+
, xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) );
|
|
10
15
|
|
|
16
|
+
function
|
|
17
|
+
ASSERT(x)
|
|
18
|
+
{
|
|
19
|
+
// if(!x)
|
|
20
|
+
// debugger
|
|
21
|
+
}
|
|
11
22
|
function
|
|
12
23
|
xml2dom( xmlString )
|
|
13
24
|
{
|
|
14
|
-
return new DOMParser().parseFromString(
|
|
25
|
+
return new DOMParser().parseFromString( xmlString, "application/xml" )
|
|
15
26
|
}
|
|
16
27
|
function
|
|
17
28
|
xmlString(doc){ return new XMLSerializer().serializeToString( doc ) }
|
|
18
29
|
|
|
19
30
|
function
|
|
20
31
|
injectData( root, sectionName, arr, cb )
|
|
21
|
-
{
|
|
32
|
+
{ const create = ( tag ) => root.ownerDocument.createElement( tag );
|
|
22
33
|
const inject = ( tag, parent, s ) =>
|
|
23
|
-
{
|
|
24
|
-
parent.append( s = createNS( DCE_NS_URL, tag ) );
|
|
34
|
+
{ parent.append( s = create( tag ) );
|
|
25
35
|
return s;
|
|
26
36
|
};
|
|
27
37
|
const l = inject( sectionName, root );
|
|
@@ -71,70 +81,133 @@ Json2Xml( o, tag )
|
|
|
71
81
|
ret.push("/>");
|
|
72
82
|
return ret.join('\n');
|
|
73
83
|
}
|
|
84
|
+
export function
|
|
85
|
+
tagUid( node )
|
|
86
|
+
{ // {} to xsl:value-of
|
|
87
|
+
forEach$(node,'*',d => [...d.childNodes].filter( e=>e.nodeType === 3 ).forEach( e=>
|
|
88
|
+
{ if( e.parentNode.localName === 'style' )
|
|
89
|
+
return;
|
|
90
|
+
const m = e.data.matchAll( /{([^}]*)}/g );
|
|
91
|
+
if(m)
|
|
92
|
+
{ let l = 0
|
|
93
|
+
, txt = t => createText(e,t||'')
|
|
94
|
+
, tt = [];
|
|
95
|
+
[...m].forEach(t=>
|
|
96
|
+
{ if( t.index > l )
|
|
97
|
+
tt.push( txt( t.input.substring( l, t.index ) ))
|
|
98
|
+
const v = e.ownerDocument.createElement('xsl:value-of');
|
|
99
|
+
v.setAttribute('select', t[1] );
|
|
100
|
+
tt.push(v);
|
|
101
|
+
l = t.index+t[0].length;
|
|
102
|
+
})
|
|
103
|
+
if( l < e.data.length)
|
|
104
|
+
tt.push( txt( e.data.substring(l,e.data.length) ));
|
|
105
|
+
if( tt.length )
|
|
106
|
+
{ for( let t of tt )
|
|
107
|
+
d.insertBefore(t,e);
|
|
108
|
+
d.removeChild(e);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}));
|
|
74
112
|
|
|
113
|
+
if( 'all' in node ) {
|
|
114
|
+
let i= 1;
|
|
115
|
+
for( let e of node.all )
|
|
116
|
+
e.setAttribute && !e.tagName.startsWith('xsl:') && e.setAttribute('data-dce-id', '' + i++)
|
|
117
|
+
}
|
|
118
|
+
return node
|
|
119
|
+
}
|
|
75
120
|
export function
|
|
76
121
|
createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
77
122
|
{
|
|
78
123
|
if( templateNode.tagName === S || templateNode.documentElement?.tagName === S )
|
|
79
|
-
return templateNode
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
124
|
+
return tagUid(templateNode)
|
|
125
|
+
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" >
|
|
126
|
+
<xsl:output method="xml" />
|
|
127
|
+
<xsl:template match="/"><dce-root><xsl:apply-templates select="*"/></dce-root></xsl:template>
|
|
128
|
+
<xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
129
|
+
<xsl:template match="*"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
130
|
+
<xsl:template match="*[name()='svg']|*[name()='math']"><xsl:apply-templates mode="sanitize" select="."/></xsl:template>
|
|
131
|
+
<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:copy></xsl:template>
|
|
132
|
+
<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:element></xsl:template>
|
|
133
|
+
<xsl:template mode="sanitize" match="*|@*"><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></xsl:template>
|
|
134
|
+
<xsl:template mode="sanitize" match="text()[normalize-space(.) = '']"/>
|
|
135
|
+
<xsl:template mode="sanitize" match="text()"><dce-text><xsl:copy/></dce-text></xsl:template>
|
|
136
|
+
<xsl:template mode="sanitize" match="xsl:value-of|*[name()='slot']"><dce-text><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></dce-text></xsl:template>
|
|
137
|
+
<xsl:template mode="sanitize" match="xhtml:*"><xsl:element name="{local-name()}"><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:element></xsl:template>
|
|
138
|
+
</xsl:stylesheet>`)
|
|
139
|
+
const sanitizeProcessor = new XSLTProcessor()
|
|
140
|
+
, tc = (n =>
|
|
141
|
+
{
|
|
142
|
+
forEach$(n,'script', s=> s.remove() );
|
|
143
|
+
const e = n.firstElementChild?.content || n.content
|
|
144
|
+
, asXmlNode = r => xslHtmlNs(xml2dom( '<xhtml/>' ).importNode(r, true));
|
|
145
|
+
if( e )
|
|
146
|
+
{ const t = create('div');
|
|
147
|
+
[ ...e.childNodes ].map( c => t.append(c.cloneNode(true)) )
|
|
148
|
+
return asXmlNode(t)
|
|
149
|
+
}
|
|
150
|
+
return asXmlNode(n.documentElement || n.body || n)
|
|
151
|
+
})(templateNode)
|
|
152
|
+
, xslDom = xml2dom(
|
|
153
|
+
`<xsl:stylesheet version="1.0"
|
|
154
|
+
xmlns:xsl="${ XSL_NS_URL }"
|
|
155
|
+
xmlns:dce="urn:schemas-epa-wg:dce"
|
|
156
|
+
xmlns:exsl="http://exslt.org/common"
|
|
157
|
+
exclude-result-prefixes="exsl"
|
|
83
158
|
>
|
|
84
|
-
<xsl:
|
|
85
|
-
|
|
159
|
+
<xsl:template mode="payload" match="attributes"></xsl:template>
|
|
86
160
|
<xsl:template match="/">
|
|
87
|
-
<xsl:
|
|
88
|
-
<xsl:call-template name="attributes"/>\t
|
|
89
|
-
</xsl:for-each>
|
|
161
|
+
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
90
162
|
</xsl:template>
|
|
91
163
|
<xsl:template name="slot" >
|
|
92
164
|
<xsl:param name="slotname" />
|
|
93
165
|
<xsl:param name="defaultvalue" />
|
|
94
166
|
<xsl:choose>
|
|
95
167
|
<xsl:when test="//payload/*[@slot=$slotname]">
|
|
96
|
-
|
|
168
|
+
<xsl:copy-of select="//payload/*[@slot=$slotname]"/>
|
|
97
169
|
</xsl:when>
|
|
98
170
|
<xsl:otherwise>
|
|
99
171
|
<xsl:copy-of select="$defaultvalue"/>
|
|
100
172
|
</xsl:otherwise>
|
|
101
173
|
</xsl:choose>
|
|
102
174
|
</xsl:template>
|
|
103
|
-
<xsl:
|
|
104
|
-
<xsl:variable name="slottemplate">
|
|
175
|
+
<xsl:variable name="js-injected-body">
|
|
105
176
|
<xsl:call-template name="slot" >
|
|
106
177
|
<xsl:with-param name="slotname" select="''"/>
|
|
107
178
|
<xsl:with-param name="defaultvalue"/>
|
|
108
179
|
</xsl:call-template>
|
|
109
180
|
</xsl:variable>
|
|
110
181
|
</xsl:stylesheet>`
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
, getTemplateRoot = n => n.documentElement || n.firstElementChild?.content || n.content || n.body || n
|
|
115
|
-
, tc = getTemplateRoot(templateNode)
|
|
116
|
-
, cc = tc?.childNodes || [];
|
|
117
|
-
if( (tc instanceof CustomElement) || tc.nodeType===11) {
|
|
118
|
-
for( let c of cc )
|
|
119
|
-
attrsTemplate.append(dom.importNode(c,true))
|
|
120
|
-
}else
|
|
121
|
-
{
|
|
122
|
-
attrsTemplate.append(dom.importNode(tc,true))
|
|
123
|
-
}
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
sanitizeProcessor.importStylesheet( sanitizeXsl );
|
|
124
185
|
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
186
|
+
const fr = sanitizeProcessor.transformToFragment(tc, document)
|
|
187
|
+
, $ = (e,css) => e.querySelector(css)
|
|
188
|
+
, payload = $( xslDom, 'template[mode="payload"]');
|
|
189
|
+
if( !fr )
|
|
190
|
+
return console.error("transformation error",{ xml:tc.outerHTML, xsl: xmlString( sanitizeXsl ) });
|
|
191
|
+
|
|
192
|
+
for( const c of fr.childNodes )
|
|
193
|
+
payload.append(xslDom.importNode(c,true))
|
|
194
|
+
|
|
195
|
+
const embeddedTemplates = [...payload.querySelectorAll('template')];
|
|
196
|
+
embeddedTemplates.forEach(t=>payload.ownerDocument.documentElement.append(t));
|
|
197
|
+
|
|
198
|
+
const slotCall = $(xslDom,'call-template[name="slot"]')
|
|
199
|
+
, slot2xsl = s =>
|
|
200
|
+
{ const v = slotCall.cloneNode(true)
|
|
201
|
+
, name = attr(s,'name') || '';
|
|
202
|
+
name && v.firstElementChild.setAttribute('select',`'${ name }'`)
|
|
128
203
|
for( let c of s.childNodes)
|
|
129
204
|
v.lastElementChild.append(c)
|
|
130
205
|
return v
|
|
131
206
|
}
|
|
132
207
|
|
|
133
|
-
|
|
134
|
-
s.parentNode.replaceChild( slot2xsl(s), s )
|
|
208
|
+
forEach$( payload,'slot', s => s.parentNode.replaceChild( slot2xsl(s), s ) )
|
|
135
209
|
|
|
136
|
-
|
|
137
|
-
return dom
|
|
210
|
+
return tagUid(xslDom)
|
|
138
211
|
}
|
|
139
212
|
export async function
|
|
140
213
|
xhrTemplate(src)
|
|
@@ -175,9 +248,9 @@ deepEqual(a, b, O=false)
|
|
|
175
248
|
injectSlice( x, s, data )
|
|
176
249
|
{
|
|
177
250
|
const isString = typeof data === 'string' ;
|
|
178
|
-
|
|
251
|
+
const createXmlNode = ( tag, t = '' ) => ( e => ((e.append( createText(x, t||''))),e) )(x.ownerDocument.createElement( tag ))
|
|
179
252
|
const el = isString
|
|
180
|
-
?
|
|
253
|
+
? createXmlNode(s, data)
|
|
181
254
|
: document.adoptNode( xml2dom( Json2Xml( data, s ) ).documentElement);
|
|
182
255
|
[...x.children].filter( e=>e.localName === s ).map( el=>el.remove() );
|
|
183
256
|
el.data = data
|
|
@@ -186,8 +259,7 @@ injectSlice( x, s, data )
|
|
|
186
259
|
|
|
187
260
|
function forEach$( el, css, cb){
|
|
188
261
|
if( el.querySelectorAll )
|
|
189
|
-
|
|
190
|
-
cb(n)
|
|
262
|
+
[...el.querySelectorAll(css)].forEach(cb)
|
|
191
263
|
}
|
|
192
264
|
const getByHashId = ( n, id )=> ( p => n===p? null: (p && ( p.querySelector(id) || getByHashId(p,id) ) ))( n.getRootNode() )
|
|
193
265
|
const loadTemplateRoots = async ( src, dce )=>
|
|
@@ -215,6 +287,68 @@ const loadTemplateRoots = async ( src, dce )=>
|
|
|
215
287
|
}
|
|
216
288
|
return [dom]
|
|
217
289
|
}catch (error){ return [dce]}
|
|
290
|
+
}
|
|
291
|
+
export function mergeAttr( from, to )
|
|
292
|
+
{ if( isText(from) )
|
|
293
|
+
{
|
|
294
|
+
if( !isText(to) ){ debugger }
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
for( let a of from.attributes)
|
|
298
|
+
a.namespaceURI? to.setAttributeNS( a.namespaceURI, a.name, a.value ) : to.setAttribute( a.name, a.value )
|
|
299
|
+
}
|
|
300
|
+
export function assureUnique(n, id=0)
|
|
301
|
+
{
|
|
302
|
+
const m = {}
|
|
303
|
+
for( const e of n.childNodes )
|
|
304
|
+
{
|
|
305
|
+
const a = attr(e,'data-dce-id') || e.dceId || 0;
|
|
306
|
+
if( !m[a] )
|
|
307
|
+
{ if( !a )
|
|
308
|
+
{ m[a] = e.dceId = ++id;
|
|
309
|
+
if( e.setAttribute )
|
|
310
|
+
e.setAttribute('data-dce-id', e.dceId )
|
|
311
|
+
}else
|
|
312
|
+
m[a] = 1;
|
|
313
|
+
}else
|
|
314
|
+
{ const v = e.dceId = a + '-' + m[a]++;
|
|
315
|
+
if( e.setAttribute )
|
|
316
|
+
e.setAttribute('data-dce-id', v )
|
|
317
|
+
}
|
|
318
|
+
e.childNodes.length && assureUnique(e)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
export function merge( parent, fromArr )
|
|
322
|
+
{
|
|
323
|
+
const id2old = {};
|
|
324
|
+
for( let c of parent.childNodes)
|
|
325
|
+
{ ASSERT( !id2old[c.dceId] );
|
|
326
|
+
if( isText(c) )
|
|
327
|
+
{ ASSERT( c.data.trim() );
|
|
328
|
+
id2old[c.dceId || 0] = c;
|
|
329
|
+
} else
|
|
330
|
+
id2old[attr(c, 'data-dce-id') || 0] = c;
|
|
331
|
+
}
|
|
332
|
+
for( let e of [...fromArr] )
|
|
333
|
+
{
|
|
334
|
+
const o = id2old[ attr(e, 'data-dce-id') || e.dceId ];
|
|
335
|
+
if( o )
|
|
336
|
+
{ if( isText(e) )
|
|
337
|
+
{ if( o.nodeValue !== e.nodeValue )
|
|
338
|
+
o.nodeValue = e.nodeValue;
|
|
339
|
+
}else
|
|
340
|
+
{ mergeAttr(o,e)
|
|
341
|
+
if( o.childNodes.length || e.childNodes.length )
|
|
342
|
+
merge(o, e.childNodes)
|
|
343
|
+
}
|
|
344
|
+
}else
|
|
345
|
+
parent.append( e )
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
export function assureUID(n,attr)
|
|
349
|
+
{ if( !n.hasAttribute(attr) )
|
|
350
|
+
n.setAttribute(attr, crypto.randomUUID());
|
|
351
|
+
return n.getAttribute(attr)
|
|
218
352
|
}
|
|
219
353
|
export class
|
|
220
354
|
CustomElement extends HTMLElement
|
|
@@ -222,24 +356,54 @@ CustomElement extends HTMLElement
|
|
|
222
356
|
async connectedCallback()
|
|
223
357
|
{
|
|
224
358
|
const templateRoots = await loadTemplateRoots( attr( this, 'src' ), this )
|
|
225
|
-
,
|
|
359
|
+
, tag = attr( this, 'tag' )
|
|
360
|
+
, tagName = tag ? tag : 'dce-'+crypto.randomUUID();
|
|
361
|
+
|
|
362
|
+
for( const t of templateRoots )
|
|
363
|
+
forEach$(t.templateNode||t.content||t, 'style',s=>{
|
|
364
|
+
const slot = s.closest('slot');
|
|
365
|
+
const sName = slot ? `slot[name="${slot.name}"]`:'';
|
|
366
|
+
s.innerHTML = `${tagName} ${sName}{${s.innerHTML}}`;
|
|
367
|
+
this.append(s);
|
|
368
|
+
})
|
|
369
|
+
const templateDocs = templateRoots.map( n => createXsltFromDom( n ) )
|
|
226
370
|
, xp = templateDocs.map( (td, p) =>{ p = new XSLTProcessor(); p.importStylesheet( td ); return p })
|
|
227
371
|
|
|
228
|
-
Object.defineProperty( this, "xsltString", { get: ()=>
|
|
372
|
+
Object.defineProperty( this, "xsltString", { get: ()=>templateDocs.map( td => xmlString(td) ).join('\n') });
|
|
229
373
|
|
|
230
|
-
const tag = attr( this, 'tag' );
|
|
231
374
|
const dce = this;
|
|
232
375
|
const sliceNames = [...this.templateNode.querySelectorAll('[slice]')].map(e=>attr(e,'slice'));
|
|
233
376
|
class DceElement extends HTMLElement
|
|
234
377
|
{
|
|
235
378
|
connectedCallback()
|
|
236
|
-
{
|
|
379
|
+
{ if( this.firstElementChild?.tagName === 'TEMPLATE' )
|
|
380
|
+
{ const t = this.firstElementChild;
|
|
381
|
+
for( const n of [...t.content.childNodes] )
|
|
382
|
+
if( n.localName === 'style' ){
|
|
383
|
+
const id = assureUID(this,'data-dce-style')
|
|
384
|
+
n.innerHTML= `${tagName}[data-dce-style="${id}"]{${n.innerHTML}}`;
|
|
385
|
+
t.insertAdjacentElement('beforebegin',n);
|
|
386
|
+
}else
|
|
387
|
+
if(n.nodeType===1)
|
|
388
|
+
t.insertAdjacentElement('beforebegin',n);
|
|
389
|
+
else if(n.nodeType===3)
|
|
390
|
+
t.insertAdjacentText('beforebegin',n.data);
|
|
391
|
+
|
|
392
|
+
t.remove();
|
|
393
|
+
|
|
394
|
+
}
|
|
395
|
+
const x = xml2dom( '<datadom/>' ).documentElement;
|
|
396
|
+
const createXmlNode = ( tag, t = '' ) => ( e =>
|
|
397
|
+
{ if( t )
|
|
398
|
+
e.append( createText( x, t ))
|
|
399
|
+
return e;
|
|
400
|
+
})(x.ownerDocument.createElement( tag ))
|
|
237
401
|
injectData( x, 'payload' , this.childNodes, assureSlot );
|
|
238
|
-
|
|
239
|
-
injectData( x, '
|
|
240
|
-
|
|
402
|
+
this.innerHTML='';
|
|
403
|
+
injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
404
|
+
injectData( x, 'dataset', Object.keys( this.dataset ), k => createXmlNode( k, this.dataset[ k ] ) );
|
|
405
|
+
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) );
|
|
241
406
|
this.xml = x;
|
|
242
|
-
const slices = {};
|
|
243
407
|
|
|
244
408
|
const sliceEvents=[];
|
|
245
409
|
const applySlices = ()=>
|
|
@@ -249,7 +413,7 @@ CustomElement extends HTMLElement
|
|
|
249
413
|
{ const s = attr( ev.target, 'slice');
|
|
250
414
|
if( processed[s] )
|
|
251
415
|
continue;
|
|
252
|
-
injectSlice( sliceRoot, s, ev.detail );
|
|
416
|
+
injectSlice( sliceRoot, s, 'object' === typeof ev.detail ? {...ev.detail}: ev.detail );
|
|
253
417
|
processed[s] = ev;
|
|
254
418
|
}
|
|
255
419
|
Object.keys(processed).length !== 0 && transform();
|
|
@@ -271,17 +435,28 @@ CustomElement extends HTMLElement
|
|
|
271
435
|
};
|
|
272
436
|
const transform = ()=>
|
|
273
437
|
{
|
|
274
|
-
const ff = xp.map( p
|
|
275
|
-
|
|
438
|
+
const ff = xp.map( (p,i) =>
|
|
439
|
+
{ const f = p.transformToFragment(x, document)
|
|
440
|
+
if( !f )
|
|
441
|
+
console.error( "XSLT transformation error. xsl:\n", xmlString(templateDocs[i]), '\nxml:\n', xmlString(x) );
|
|
442
|
+
return f
|
|
443
|
+
});
|
|
276
444
|
ff.map( f =>
|
|
277
|
-
{
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
445
|
+
{ if( !f )
|
|
446
|
+
return;
|
|
447
|
+
assureUnique(f);
|
|
448
|
+
merge( this, f.childNodes )
|
|
449
|
+
})
|
|
450
|
+
const changeCb = el=>this.onSlice({ detail: el[attr(el,'slice-prop') || 'value'], target: el })
|
|
451
|
+
, hasInitValue = el => el.hasAttribute('slice-prop') || el.hasAttribute('value') || el.value;
|
|
452
|
+
|
|
453
|
+
forEach$( this,'[slice]', el =>
|
|
454
|
+
{ if( !el.dceInitialized )
|
|
455
|
+
{ el.dceInitialized = 1;
|
|
456
|
+
el.addEventListener( attr(el,'slice-update')|| 'change', ()=>changeCb(el) )
|
|
457
|
+
if( hasInitValue(el) )
|
|
458
|
+
changeCb(el)
|
|
459
|
+
}
|
|
285
460
|
})
|
|
286
461
|
};
|
|
287
462
|
transform();
|
|
@@ -292,11 +467,11 @@ CustomElement extends HTMLElement
|
|
|
292
467
|
if(tag)
|
|
293
468
|
window.customElements.define( tag, DceElement);
|
|
294
469
|
else
|
|
295
|
-
{ const t =
|
|
470
|
+
{ const t = tagName;
|
|
296
471
|
window.customElements.define( t, DceElement);
|
|
297
472
|
const el = document.createElement(t);
|
|
298
473
|
this.getAttributeNames().forEach(a=>el.setAttribute(a,this.getAttribute(a)));
|
|
299
|
-
el.append(...this.childNodes)
|
|
474
|
+
el.append(...[...this.childNodes].filter(e=>e.localName!=='style'))
|
|
300
475
|
this.append(el);
|
|
301
476
|
}
|
|
302
477
|
}
|