@epa-wg/custom-element 0.0.10 → 0.0.12
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/.gitignore +6 -2
- package/.idea/inspectionProfiles/Project_Default.xml +41 -0
- 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 +53 -19
- package/custom-element.js +335 -76
- package/demo/a.html +38 -0
- package/demo/confused.svg +37 -0
- package/demo/demo.css +3 -1
- package/demo/dom-merge.html +121 -0
- package/demo/embed-1.html +3 -0
- package/demo/external-template.html +178 -0
- package/demo/html-template.html +126 -0
- package/demo/html-template.xhtml +45 -0
- package/demo/html-template.xml +45 -0
- package/demo/http-request.html +45 -58
- package/demo/local-storage.html +24 -19
- package/demo/location-element.html +60 -59
- package/demo/s.xml +1 -0
- package/demo/s.xslt +159 -0
- package/demo/table.xml +25 -0
- package/demo/table.xsl +293 -0
- package/demo/template.xsl +46 -0
- package/demo/tree.xml +25 -0
- package/demo/tree.xsl +33 -0
- package/demo/wc-square.svg +1 -1
- package/demo/xhtml-template.xhtml +45 -0
- package/demo/z.html +42 -0
- package/demo/z.xml +60 -0
- package/http-request.js +28 -35
- package/index.html +41 -33
- package/input-text.js +17 -0
- package/local-storage.js +28 -35
- package/location-element.js +15 -16
- package/package.json +1 -1
package/custom-element.js
CHANGED
|
@@ -1,46 +1,37 @@
|
|
|
1
|
-
const
|
|
2
|
-
,
|
|
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";
|
|
3
5
|
|
|
4
6
|
// const log = x => console.debug( new XMLSerializer().serializeToString( x ) );
|
|
5
7
|
|
|
6
|
-
const attr = (el, attr)=> el.getAttribute(attr)
|
|
7
|
-
,
|
|
8
|
+
const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
9
|
+
, isText = e => e.nodeType === 3
|
|
10
|
+
, create = ( tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElement( tag ))
|
|
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) );
|
|
8
15
|
|
|
9
16
|
function
|
|
10
|
-
|
|
17
|
+
ASSERT(x)
|
|
11
18
|
{
|
|
12
|
-
|
|
19
|
+
// if(!x)
|
|
20
|
+
// debugger
|
|
13
21
|
}
|
|
14
|
-
|
|
15
22
|
function
|
|
16
|
-
|
|
23
|
+
xml2dom( xmlString )
|
|
17
24
|
{
|
|
18
|
-
|
|
19
|
-
, sanitize = s => s.replaceAll("<html:","<")
|
|
20
|
-
.replaceAll("</html:","</")
|
|
21
|
-
.replaceAll( />\s*<\/xsl:value-of>/g ,"/>")
|
|
22
|
-
.replaceAll( />\s*<\/(br|hr|img|area|base|col|embed|input|link|meta|param|source|track|wbr)>/g ,"/>");
|
|
23
|
-
if( t?.tagName === 'TEMPLATE')
|
|
24
|
-
return sanitize( new XMLSerializer().serializeToString( t.content ) );
|
|
25
|
-
|
|
26
|
-
const s = new XMLSerializer().serializeToString( dce );
|
|
27
|
-
return sanitize( s.substring( s.indexOf( '>' ) + 1, s.lastIndexOf( '<' ) ) );
|
|
25
|
+
return new DOMParser().parseFromString( xmlString, "application/xml" )
|
|
28
26
|
}
|
|
29
|
-
|
|
30
27
|
function
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
const v = document.createElementNS( XSL_NS_URL, 'value-of' );
|
|
34
|
-
v.setAttribute( 'select', `//*[@slot="${ s.name }"]` );
|
|
35
|
-
s.parentNode.replaceChild( v, s );
|
|
36
|
-
}
|
|
28
|
+
xmlString(doc){ return new XMLSerializer().serializeToString( doc ) }
|
|
37
29
|
|
|
38
30
|
function
|
|
39
31
|
injectData( root, sectionName, arr, cb )
|
|
40
|
-
{
|
|
32
|
+
{ const create = ( tag ) => root.ownerDocument.createElement( tag );
|
|
41
33
|
const inject = ( tag, parent, s ) =>
|
|
42
|
-
{
|
|
43
|
-
parent.append( s = create( tag ) );
|
|
34
|
+
{ parent.append( s = create( tag ) );
|
|
44
35
|
return s;
|
|
45
36
|
};
|
|
46
37
|
const l = inject( sectionName, root );
|
|
@@ -90,45 +81,300 @@ Json2Xml( o, tag )
|
|
|
90
81
|
ret.push("/>");
|
|
91
82
|
return ret.join('\n');
|
|
92
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
|
+
}));
|
|
93
112
|
|
|
94
|
-
|
|
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
|
+
}
|
|
120
|
+
export function
|
|
121
|
+
createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
122
|
+
{
|
|
123
|
+
if( templateNode.tagName === S || templateNode.documentElement?.tagName === S )
|
|
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"
|
|
158
|
+
>
|
|
159
|
+
<xsl:template mode="payload" match="attributes"></xsl:template>
|
|
160
|
+
<xsl:template match="/">
|
|
161
|
+
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
162
|
+
</xsl:template>
|
|
163
|
+
<xsl:template name="slot" >
|
|
164
|
+
<xsl:param name="slotname" />
|
|
165
|
+
<xsl:param name="defaultvalue" />
|
|
166
|
+
<xsl:choose>
|
|
167
|
+
<xsl:when test="//payload/*[@slot=$slotname]">
|
|
168
|
+
<xsl:copy-of select="//payload/*[@slot=$slotname]"/>
|
|
169
|
+
</xsl:when>
|
|
170
|
+
<xsl:otherwise>
|
|
171
|
+
<xsl:copy-of select="$defaultvalue"/>
|
|
172
|
+
</xsl:otherwise>
|
|
173
|
+
</xsl:choose>
|
|
174
|
+
</xsl:template>
|
|
175
|
+
<xsl:variable name="js-injected-body">
|
|
176
|
+
<xsl:call-template name="slot" >
|
|
177
|
+
<xsl:with-param name="slotname" select="''"/>
|
|
178
|
+
<xsl:with-param name="defaultvalue"/>
|
|
179
|
+
</xsl:call-template>
|
|
180
|
+
</xsl:variable>
|
|
181
|
+
</xsl:stylesheet>`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
sanitizeProcessor.importStylesheet( sanitizeXsl );
|
|
185
|
+
|
|
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 }'`)
|
|
203
|
+
for( let c of s.childNodes)
|
|
204
|
+
v.lastElementChild.append(c)
|
|
205
|
+
return v
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
forEach$( payload,'slot', s => s.parentNode.replaceChild( slot2xsl(s), s ) )
|
|
209
|
+
|
|
210
|
+
return tagUid(xslDom)
|
|
211
|
+
}
|
|
212
|
+
export async function
|
|
213
|
+
xhrTemplate(src)
|
|
214
|
+
{
|
|
215
|
+
const dom = await new Promise((resolve,reject)=>
|
|
216
|
+
{ const xhr = new XMLHttpRequest();
|
|
217
|
+
xhr.open("GET", src);
|
|
218
|
+
xhr.responseType = "document";
|
|
219
|
+
// xhr.overrideMimeType("text/xml");
|
|
220
|
+
xhr.onload = () =>
|
|
221
|
+
{ if( xhr.readyState === xhr.DONE && xhr.status === 200 )
|
|
222
|
+
resolve( xhr.responseXML || create('div', xhr.responseText ) )
|
|
223
|
+
reject(xhr.statusText)
|
|
224
|
+
};
|
|
225
|
+
xhr.addEventListener("error", ev=>reject(ev) );
|
|
226
|
+
|
|
227
|
+
xhr.send();
|
|
228
|
+
})
|
|
229
|
+
return dom
|
|
230
|
+
}
|
|
231
|
+
export function
|
|
232
|
+
deepEqual(a, b, O=false)
|
|
233
|
+
{
|
|
234
|
+
if( a === b )
|
|
235
|
+
return true;
|
|
236
|
+
|
|
237
|
+
if( (typeof a !== "object" || a === null) || (typeof b !== "object" || b === null)
|
|
238
|
+
|| Object.keys(a).length !== Object.keys(b).length )
|
|
239
|
+
return O;
|
|
240
|
+
|
|
241
|
+
for( let k in a )
|
|
242
|
+
if( !(k in b) || !deepEqual( a[k], b[k] ) )
|
|
243
|
+
return O
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function
|
|
95
248
|
injectSlice( x, s, data )
|
|
96
249
|
{
|
|
97
250
|
const isString = typeof data === 'string' ;
|
|
98
|
-
|
|
251
|
+
const createXmlNode = ( tag, t = '' ) => ( e => ((e.append( createText(x, t||''))),e) )(x.ownerDocument.createElement( tag ))
|
|
99
252
|
const el = isString
|
|
100
|
-
?
|
|
253
|
+
? createXmlNode(s, data)
|
|
101
254
|
: document.adoptNode( xml2dom( Json2Xml( data, s ) ).documentElement);
|
|
102
255
|
[...x.children].filter( e=>e.localName === s ).map( el=>el.remove() );
|
|
256
|
+
el.data = data
|
|
103
257
|
x.append(el);
|
|
104
258
|
}
|
|
105
259
|
|
|
260
|
+
function forEach$( el, css, cb){
|
|
261
|
+
if( el.querySelectorAll )
|
|
262
|
+
[...el.querySelectorAll(css)].forEach(cb)
|
|
263
|
+
}
|
|
264
|
+
const getByHashId = ( n, id )=> ( p => n===p? null: (p && ( p.querySelector(id) || getByHashId(p,id) ) ))( n.getRootNode() )
|
|
265
|
+
const loadTemplateRoots = async ( src, dce )=>
|
|
266
|
+
{
|
|
267
|
+
if( !src || !src.trim() )
|
|
268
|
+
return [dce]
|
|
269
|
+
if( src.startsWith('#') )
|
|
270
|
+
return ( n =>
|
|
271
|
+
{ if(!n) return []
|
|
272
|
+
const a = n.querySelectorAll(src)
|
|
273
|
+
if( a.length )
|
|
274
|
+
return [...a]
|
|
275
|
+
const r = n.getRootNode();
|
|
276
|
+
return r===n ? []: getByHashId(r)
|
|
277
|
+
})(dce.parentElement)
|
|
278
|
+
try
|
|
279
|
+
{ // todo cache
|
|
280
|
+
const dom = await xhrTemplate(src)
|
|
281
|
+
const hash = new URL(src, location).hash
|
|
282
|
+
if( hash )
|
|
283
|
+
{ const ret = dom.querySelectorAll(hash);
|
|
284
|
+
if( ret.length )
|
|
285
|
+
return [...ret]
|
|
286
|
+
return [dce]
|
|
287
|
+
}
|
|
288
|
+
return [dom]
|
|
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
|
+
|
|
106
349
|
export class
|
|
107
350
|
CustomElement extends HTMLElement
|
|
108
351
|
{
|
|
109
|
-
|
|
352
|
+
async connectedCallback()
|
|
110
353
|
{
|
|
111
|
-
|
|
354
|
+
const templateRoots = await loadTemplateRoots( attr( this, 'src' ), this )
|
|
355
|
+
, templateDocs = templateRoots.map( n => createXsltFromDom( n ) )
|
|
356
|
+
, xp = templateDocs.map( (td, p) =>{ p = new XSLTProcessor(); p.importStylesheet( td ); return p })
|
|
357
|
+
|
|
358
|
+
Object.defineProperty( this, "xsltString", { get: ()=>templateDocs.map( td => xmlString(td) ).join('\n') });
|
|
112
359
|
|
|
113
|
-
[ ...this.templateNode.querySelectorAll('slot') ].forEach( slot2xsl );
|
|
114
|
-
const p = new XSLTProcessor();
|
|
115
|
-
p.importStylesheet( this.xslt );
|
|
116
360
|
const tag = attr( this, 'tag' );
|
|
117
361
|
const dce = this;
|
|
118
362
|
const sliceNames = [...this.templateNode.querySelectorAll('[slice]')].map(e=>attr(e,'slice'));
|
|
119
|
-
|
|
363
|
+
class DceElement extends HTMLElement
|
|
120
364
|
{
|
|
121
|
-
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
365
|
+
connectedCallback()
|
|
366
|
+
{ const x = xml2dom( '<datadom/>' ).documentElement;
|
|
367
|
+
const createXmlNode = ( tag, t = '' ) => ( e =>
|
|
368
|
+
{ if( t )
|
|
369
|
+
e.append( createText( x, t ))
|
|
370
|
+
return e;
|
|
371
|
+
})(x.ownerDocument.createElement( tag ))
|
|
372
|
+
injectData( x, 'payload' , this.childNodes, assureSlot );
|
|
373
|
+
this.innerHTML='';
|
|
374
|
+
injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
375
|
+
injectData( x, 'dataset', Object.keys( this.dataset ), k => createXmlNode( k, this.dataset[ k ] ) );
|
|
376
|
+
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) );
|
|
129
377
|
this.xml = x;
|
|
130
|
-
const slices = {};
|
|
131
|
-
|
|
132
378
|
|
|
133
379
|
const sliceEvents=[];
|
|
134
380
|
const applySlices = ()=>
|
|
@@ -138,7 +384,7 @@ CustomElement extends HTMLElement
|
|
|
138
384
|
{ const s = attr( ev.target, 'slice');
|
|
139
385
|
if( processed[s] )
|
|
140
386
|
continue;
|
|
141
|
-
injectSlice( sliceRoot, s, ev.detail );
|
|
387
|
+
injectSlice( sliceRoot, s, 'object' === typeof ev.detail ? {...ev.detail}: ev.detail );
|
|
142
388
|
processed[s] = ev;
|
|
143
389
|
}
|
|
144
390
|
Object.keys(processed).length !== 0 && transform();
|
|
@@ -147,6 +393,10 @@ CustomElement extends HTMLElement
|
|
|
147
393
|
|
|
148
394
|
this.onSlice = ev=>
|
|
149
395
|
{ ev.stopPropagation?.();
|
|
396
|
+
const s = attr( ev.target, 'slice')
|
|
397
|
+
if( deepEqual( ev.detail, [...sliceRoot.children].find( e=>e.localName === s )?.data ) )
|
|
398
|
+
return
|
|
399
|
+
|
|
150
400
|
sliceEvents.push(ev);
|
|
151
401
|
if( !timeoutID )
|
|
152
402
|
timeoutID = setTimeout(()=>
|
|
@@ -156,41 +406,50 @@ CustomElement extends HTMLElement
|
|
|
156
406
|
};
|
|
157
407
|
const transform = ()=>
|
|
158
408
|
{
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
409
|
+
const ff = xp.map( (p,i) =>
|
|
410
|
+
{ const f = p.transformToFragment(x, document)
|
|
411
|
+
if( !f )
|
|
412
|
+
console.error( "XSLT transformation error. xsl:\n", xmlString(templateDocs[i]), '\nxml:\n', xmlString(x) );
|
|
413
|
+
return f
|
|
414
|
+
});
|
|
415
|
+
ff.map( f =>
|
|
416
|
+
{ if( !f )
|
|
417
|
+
return;
|
|
418
|
+
assureUnique(f)
|
|
419
|
+
merge( this, f.childNodes )
|
|
420
|
+
})
|
|
421
|
+
const changeCb = el=>this.onSlice({ detail: el[attr(el,'slice-prop') || 'value'], target: el })
|
|
422
|
+
, hasInitValue = el => el.hasAttribute('slice-prop') || el.hasAttribute('value') || el.value;
|
|
423
|
+
|
|
424
|
+
forEach$( this,'[slice]', el =>
|
|
425
|
+
{ if( !el.dceInitialized )
|
|
426
|
+
{ el.dceInitialized = 1;
|
|
427
|
+
el.addEventListener( attr(el,'slice-update')|| 'change', ()=>changeCb(el) )
|
|
428
|
+
if( hasInitValue(el) )
|
|
429
|
+
changeCb(el)
|
|
167
430
|
}
|
|
431
|
+
})
|
|
168
432
|
};
|
|
169
433
|
transform();
|
|
170
434
|
applySlices();
|
|
171
435
|
}
|
|
172
|
-
get dce(){ return dce
|
|
173
|
-
}
|
|
436
|
+
get dce(){ return dce }
|
|
437
|
+
}
|
|
438
|
+
if(tag)
|
|
439
|
+
window.customElements.define( tag, DceElement);
|
|
440
|
+
else
|
|
441
|
+
{ const t = 'dce-'+crypto.randomUUID()
|
|
442
|
+
window.customElements.define( t, DceElement);
|
|
443
|
+
const el = document.createElement(t);
|
|
444
|
+
this.getAttributeNames().forEach(a=>el.setAttribute(a,this.getAttribute(a)));
|
|
445
|
+
el.append(...this.childNodes)
|
|
446
|
+
this.append(el);
|
|
447
|
+
}
|
|
174
448
|
}
|
|
175
449
|
get templateNode(){ return this.firstElementChild?.tagName === 'TEMPLATE'? this.firstElementChild.content : this }
|
|
176
|
-
get dce(){ return this
|
|
177
|
-
|
|
178
|
-
{
|
|
179
|
-
return (
|
|
180
|
-
`<xsl:stylesheet version="1.0"
|
|
181
|
-
xmlns:xsl="${ XSL_NS_URL }">
|
|
182
|
-
<xsl:output method="html" />
|
|
183
|
-
|
|
184
|
-
<xsl:template match="/">
|
|
185
|
-
<xsl:apply-templates select="//attributes"/>
|
|
186
|
-
</xsl:template>
|
|
187
|
-
<xsl:template match="attributes">
|
|
188
|
-
${ bodyXml( this ) }
|
|
189
|
-
</xsl:template>
|
|
190
|
-
|
|
191
|
-
</xsl:stylesheet>` );
|
|
192
|
-
}
|
|
193
|
-
get xslt(){ return xml2dom( this.xsltString ); }
|
|
450
|
+
get dce(){ return this }
|
|
451
|
+
|
|
452
|
+
get xslt(){ return xml2dom( this.xsltString ) }
|
|
194
453
|
}
|
|
195
454
|
|
|
196
455
|
window.customElements.define( 'custom-element', CustomElement );
|
package/demo/a.html
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
xmlns:html="http://www.w3.org/1999/xhtml">
|
|
4
|
+
<head>
|
|
5
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
|
6
|
+
<title>custom-element Declarative Custom Element implementation demo</title>
|
|
7
|
+
<link rel="icon" href="./wc-square.svg" />
|
|
8
|
+
<script type="module" src="../location-element.js"></script>
|
|
9
|
+
<script type="module" src="../custom-element.js"></script>
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
@import "./demo.css";
|
|
13
|
+
|
|
14
|
+
button{ background: forestgreen; }
|
|
15
|
+
table{ min-width: 16rem; }
|
|
16
|
+
td{ border-bottom: 1px solid silver; }
|
|
17
|
+
tfoot td{ border-bottom: none; }
|
|
18
|
+
td,th{text-align: right; }
|
|
19
|
+
caption{ padding: 1rem; font-weight: bolder; font-family: sans-serif; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
|
|
24
|
+
<custom-element >
|
|
25
|
+
<local-storage key="basket" slice="basket"></local-storage>
|
|
26
|
+
<html:table>
|
|
27
|
+
<xsl:for-each select="//slice/basket/@*">
|
|
28
|
+
<html:tr>
|
|
29
|
+
<html:th> {name()} </html:th>
|
|
30
|
+
<html:td> {.} </html:td>
|
|
31
|
+
</html:tr>
|
|
32
|
+
</xsl:for-each>
|
|
33
|
+
</html:table>
|
|
34
|
+
count:<xsl:value-of select="count(//slice/basket/@*)"/>
|
|
35
|
+
</custom-element>
|
|
36
|
+
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48" version="1.0">
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="d">
|
|
5
|
+
<stop offset="0"/>
|
|
6
|
+
<stop offset=".5"/>
|
|
7
|
+
<stop offset=".80000001" stop-opacity=".46666667"/>
|
|
8
|
+
<stop offset="1" stop-opacity="0"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<linearGradient id="a">
|
|
11
|
+
<stop offset="0" stop-color="#fb0"/>
|
|
12
|
+
<stop offset=".5" stop-color="#e29d00"/>
|
|
13
|
+
<stop offset="1" stop-color="#fb0"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<linearGradient id="c">
|
|
16
|
+
<stop offset="0"/>
|
|
17
|
+
<stop offset="1" stop-opacity="0"/>
|
|
18
|
+
</linearGradient>
|
|
19
|
+
<linearGradient id="b">
|
|
20
|
+
<stop offset="0" stop-color="#ffc"/>
|
|
21
|
+
<stop offset=".5" stop-color="#fff965"/>
|
|
22
|
+
<stop offset="1" stop-color="#fc3"/>
|
|
23
|
+
</linearGradient>
|
|
24
|
+
<linearGradient xlink:href="#a" id="i" x1="8.0350637" x2="42.788235" y1="32.372219" y2="32.372219" gradientUnits="userSpaceOnUse" spreadMethod="pad"/>
|
|
25
|
+
<radialGradient xlink:href="#b" id="f" cx="17.986637" cy="16.545853" r="23.978155" fx="17.986637" fy="16.545853" gradientUnits="userSpaceOnUse"/>
|
|
26
|
+
<radialGradient xlink:href="#c" id="e" cx="53.309223" cy="94.956306" r="63.252911" fx="53.309223" fy="94.956306" gradientTransform="matrix(1 0 0 .34935 0 61.7838)" gradientUnits="userSpaceOnUse"/>
|
|
27
|
+
<radialGradient xlink:href="#d" id="g" cx="18.71347" cy="21.759708" r="1.8644418" fx="18.71347" fy="21.759708" gradientTransform="matrix(1 0 0 1.77778 0 -16.92422)" gradientUnits="userSpaceOnUse"/>
|
|
28
|
+
</defs>
|
|
29
|
+
<path fill="url(#e)" d="M116.56213 94.956306a63.252911 22.097088 0 1 1-126.5058174 0 63.252911 22.097088 0 1 1 126.5058174 0z" opacity=".53200001" transform="matrix(.3162 0 0 .33941 6.936944 8.132618)"/>
|
|
30
|
+
<path fill="url(#f)" stroke="#fb0" stroke-width="1.43869453" d="M47.094418 23.83131a23.478155 23.478155 0 1 1-46.9563107 0 23.478155 23.478155 0 1 1 46.9563107 0z" transform="translate(4.30185 4.122792) scale(.83409)"/>
|
|
31
|
+
<path id="h" fill="#fff" fill-opacity="1" fill-rule="nonzero" stroke="#fc0" stroke-dasharray="none" stroke-dashoffset="0" stroke-linejoin="miter" stroke-miterlimit="4" stroke-opacity="1" stroke-width="1" d="M21.682767 18.5142a3.9360437 6.9743929 0 1 1-7.872088 0 3.9360437 6.9743929 0 1 1 7.872088 0z" opacity="1" transform="matrix(1.01507 0 0 1.00354 -.0090285 .916405)"/>
|
|
32
|
+
<path id="j" fill="url(#g)" fill-opacity="1" fill-rule="nonzero" stroke="none" stroke-dasharray="none" stroke-dashoffset="0" stroke-linejoin="miter" stroke-miterlimit="4" stroke-opacity="1" stroke-width="1" d="M20.577912 21.759708a1.8644418 3.314563 0 1 1-3.728883 0 1.8644418 3.314563 0 1 1 3.728883 0z" opacity="1" transform="translate(-.138107 .535104)"/>
|
|
33
|
+
<use xlink:href="#h" width="48" height="48" transform="translate(12.50001 -4.4e-7)"/>
|
|
34
|
+
<path fill="none" stroke="url(#i)" stroke-linecap="round" stroke-width="1.97319973" d="M9.0216636 35.899178c4.7689724-7.457767 10.9544424-9.489956 17.3095664-3.728884 5.404329 4.899155 11.190398 4.350365 15.470406-.656007"/>
|
|
35
|
+
<path fill="none" stroke="#e2ac00" stroke-linecap="round" stroke-width="1.17813516" d="M15.504748 34.21319c3.012147-3.243177 6.693658.87012 6.693658.87012" opacity=".8"/>
|
|
36
|
+
<use xlink:href="#j" width="48" height="48" transform="translate(10.78418 -5)"/>
|
|
37
|
+
</svg>
|
package/demo/demo.css
CHANGED
|
@@ -5,8 +5,10 @@ html
|
|
|
5
5
|
body,nav{ display: flex; flex-wrap: wrap; align-content: stretch; gap: 1rem; }
|
|
6
6
|
body>*{flex: auto;}
|
|
7
7
|
nav{ flex-direction: column;}
|
|
8
|
+
custom-element+*,
|
|
9
|
+
custom-element:not([tag]),
|
|
8
10
|
dce-link,dce-1-slot,dce-2-slot,dce-3-slot,dce-4-slot,dce-2-slots,greet-element,pokemon-tile,
|
|
9
|
-
dce-1,dce-2,dce-3,dce-4
|
|
11
|
+
dce-1,dce-2,dce-3,dce-4,dce-internal,dce-hash
|
|
10
12
|
{ box-shadow: 0 0 0.5rem lime; padding: 1rem; display: inline-block; flex:1; }
|
|
11
13
|
dd{ padding: 1rem;}
|
|
12
14
|
p{ margin: 0;}
|