@epa-wg/custom-element 0.0.20 → 0.0.22
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 +5 -0
- package/custom-element.js +113 -79
- package/custom-element1-1.js +763 -0
- package/custom-element1.js +763 -0
- package/demo/a.html +36 -49
- package/demo/b.html +13 -0
- package/demo/data-slices.html +32 -1
- package/demo/dom-merge.html +0 -1
- package/demo/form.html +193 -0
- package/demo/http-request.html +5 -5
- package/demo/local-storage.html +4 -4
- package/demo/location-element.html +6 -6
- package/demo/s.xml +13 -87
- package/demo/s.xslt +37 -16
- package/demo/s1.xslt +60 -0
- package/demo/scoped-css.html +0 -1
- package/demo/ss.html +57 -52
- package/http-request.js +14 -6
- package/ide/customData-dce.json +14 -1
- package/ide/web-types-dce.json +7 -2
- package/ide/web-types-xsl.json +2 -2
- package/index.html +1 -0
- package/index.js +7 -0
- package/local-storage.js +2 -2
- package/location-element.js +40 -15
- 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.22/coverage/src/custom-element/coverage.svg
|
|
352
|
+
[coverage-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.22/coverage/src/custom-element/index.html
|
|
353
|
+
[storybook-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.22/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.22",
|
|
120
120
|
"js-types-syntax": "typescript",
|
|
121
121
|
"description-markup": "markdown",
|
|
122
122
|
"contributions": {
|
package/custom-element.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export function log(x: any): void;
|
|
2
|
+
export function deepEqual(a: any, b:any): boolean|0;
|
|
3
|
+
export function xml2dom(xmlString:string): Document;
|
|
4
|
+
export function xmlString(doc:Node|Document): string;
|
|
5
|
+
export function obj2node(o:any, tag:string, doc:Document): HTMLElement;
|
|
6
|
+
export function tagUid(node:HTMLElement): HTMLElement;
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* @summary Declarative Custom Element as W3C proposal PoC with native(XSLT) based templating
|
package/custom-element.js
CHANGED
|
@@ -13,7 +13,6 @@ const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
|
13
13
|
, createText = ( d, t) => (d.ownerDocument || d ).createTextNode( t )
|
|
14
14
|
, removeChildren = n => { while(n.firstChild) n.firstChild.remove(); return n; }
|
|
15
15
|
, emptyNode = n => { n.getAttributeNames().map( a => n.removeAttribute(a) ); return removeChildren(n); }
|
|
16
|
-
, createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, tag ))
|
|
17
16
|
, xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
|
|
18
17
|
, xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) )
|
|
19
18
|
, cloneAs = (p,tag) =>
|
|
@@ -23,7 +22,7 @@ const attr = (el, attr)=> el.getAttribute?.(attr)
|
|
|
23
22
|
while( p.firstChild )
|
|
24
23
|
px.append(p.firstChild);
|
|
25
24
|
return px;
|
|
26
|
-
}
|
|
25
|
+
};
|
|
27
26
|
|
|
28
27
|
function
|
|
29
28
|
ASSERT(x)
|
|
@@ -63,51 +62,25 @@ assureSlot( e )
|
|
|
63
62
|
return e;
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
export function
|
|
67
|
-
Json2Xml( o, tag )
|
|
68
|
-
{
|
|
69
|
-
if( typeof o === 'string' )
|
|
70
|
-
return o;
|
|
71
|
-
|
|
72
|
-
const noTag = "string" != typeof tag;
|
|
73
|
-
|
|
74
|
-
if( o instanceof Array )
|
|
75
|
-
{ noTag && (tag = 'array');
|
|
76
|
-
return "<"+tag+">"+o.map(function(el){ return Json2Xml(el,tag); }).join()+"</"+tag+">";
|
|
77
|
-
}
|
|
78
|
-
noTag && (tag = 'r');
|
|
79
|
-
tag=tag.replace( /[^a-z0-9\-]/gi,'_' );
|
|
80
|
-
var oo = {}
|
|
81
|
-
, ret = [ "<"+tag+" "];
|
|
82
|
-
for( let k in o )
|
|
83
|
-
if( typeof o[k] == "object" )
|
|
84
|
-
oo[k] = o[k];
|
|
85
|
-
else
|
|
86
|
-
ret.push( k.replace( /[^a-z0-9\-]/gi,'_' ) + '="'+o[k].toString().replace(/&/gi,'&')+'"');
|
|
87
|
-
if( oo )
|
|
88
|
-
{ ret.push(">");
|
|
89
|
-
for( let k in oo )
|
|
90
|
-
ret.push( Json2Xml( oo[k], k ) );
|
|
91
|
-
ret.push("</"+tag+">");
|
|
92
|
-
}else
|
|
93
|
-
ret.push("/>");
|
|
94
|
-
return ret.join('\n');
|
|
95
|
-
}
|
|
96
|
-
|
|
97
65
|
export function
|
|
98
66
|
obj2node( o, tag, doc )
|
|
99
67
|
{ const t = typeof o;
|
|
100
|
-
if( t === 'function'){debugger}
|
|
101
68
|
if( t === 'string' )
|
|
102
69
|
return create(tag,o,doc);
|
|
103
70
|
if( t === 'number' )
|
|
104
71
|
return create(tag,''+o,doc);
|
|
105
72
|
|
|
106
73
|
if( o instanceof Array )
|
|
107
|
-
{ const ret = create('array');
|
|
74
|
+
{ const ret = create('array','',doc);
|
|
108
75
|
o.map( ae => ret.append( obj2node(ae,tag,doc)) );
|
|
109
76
|
return ret
|
|
110
77
|
}
|
|
78
|
+
if( o instanceof FormData )
|
|
79
|
+
{ const ret = create('form-data','',doc);
|
|
80
|
+
for( const p of o )
|
|
81
|
+
ret.append( obj2node(p[1],p[0],doc) );
|
|
82
|
+
return ret
|
|
83
|
+
}
|
|
111
84
|
const ret = create(tag,'',doc);
|
|
112
85
|
for( let k in o )
|
|
113
86
|
if( isNode(o[k]) || typeof o[k] ==='function' || o[k] instanceof Window )
|
|
@@ -122,13 +95,14 @@ obj2node( o, tag, doc )
|
|
|
122
95
|
export function
|
|
123
96
|
tagUid( node )
|
|
124
97
|
{ // {} to xsl:value-of
|
|
125
|
-
forEach$(node,'*',d => [...d.childNodes]
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
98
|
+
forEach$(node,'*',d => [...d.childNodes]
|
|
99
|
+
.filter( e => e.nodeType === 3 && e.parentNode.localName !== 'style' && e.data )
|
|
100
|
+
.forEach( e=>
|
|
101
|
+
{ const s = e.data,
|
|
102
|
+
m = s.matchAll( /{([^}]*)}/g );
|
|
129
103
|
if(m)
|
|
130
104
|
{ let l = 0
|
|
131
|
-
, txt = t => createText(e,t
|
|
105
|
+
, txt = t => createText(e,t)
|
|
132
106
|
, tt = [];
|
|
133
107
|
[...m].forEach(t=>
|
|
134
108
|
{ if( t.index > l )
|
|
@@ -138,8 +112,8 @@ tagUid( node )
|
|
|
138
112
|
tt.push(v);
|
|
139
113
|
l = t.index+t[0].length;
|
|
140
114
|
})
|
|
141
|
-
if( l <
|
|
142
|
-
tt.push( txt(
|
|
115
|
+
if( l < s.length)
|
|
116
|
+
tt.push( txt( s.substring(l,s.length) ));
|
|
143
117
|
if( tt.length )
|
|
144
118
|
{ for( let t of tt )
|
|
145
119
|
d.insertBefore(t,e);
|
|
@@ -166,8 +140,8 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
166
140
|
<xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
167
141
|
<xsl:template match="*"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
|
|
168
142
|
<xsl:template match="*[name()='svg']|*[name()='math']"><xsl:apply-templates mode="sanitize" select="."/></xsl:template>
|
|
169
|
-
<xsl:template mode="sanitize" match="*[count(text())=1 and count(*)=0]"><xsl:copy><xsl:apply-templates mode="sanitize" select="@*"/><xsl:value-of select="text()"
|
|
170
|
-
<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()"
|
|
143
|
+
<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>
|
|
144
|
+
<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>
|
|
171
145
|
<xsl:template mode="sanitize" match="*|@*"><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></xsl:template>
|
|
172
146
|
<xsl:template mode="sanitize" match="text()[normalize-space(.) = '']"/>
|
|
173
147
|
<xsl:template mode="sanitize" match="text()"><dce-text><xsl:copy/></dce-text></xsl:template>
|
|
@@ -206,8 +180,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
206
180
|
<xsl:choose>
|
|
207
181
|
<xsl:when test="//attr">{//attr}</xsl:when>
|
|
208
182
|
<xsl:otherwise>{def}</xsl:otherwise>
|
|
209
|
-
</xsl:choose>
|
|
210
|
-
<xsl:value-of select="."/></xsl:template>
|
|
183
|
+
</xsl:choose><xsl:value-of select="."></xsl:value-of></xsl:template>
|
|
211
184
|
<xsl:template mode="payload" match="attributes"></xsl:template>
|
|
212
185
|
<xsl:template match="/">
|
|
213
186
|
<xsl:apply-templates mode="payload" select="/datadom/attributes"/>
|
|
@@ -282,7 +255,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
|
|
|
282
255
|
const slotCall = $(xslDom,'call-template[name="slot"]')
|
|
283
256
|
, slot2xsl = s =>
|
|
284
257
|
{ const v = slotCall.cloneNode(true)
|
|
285
|
-
, name = attr(s,'name')
|
|
258
|
+
, name = attr(s,'name');
|
|
286
259
|
name && v.firstElementChild.setAttribute('select',`'${ name }'`)
|
|
287
260
|
for( let c of s.childNodes)
|
|
288
261
|
v.lastElementChild.append(c)
|
|
@@ -329,16 +302,20 @@ deepEqual(a, b, O=false)
|
|
|
329
302
|
return O
|
|
330
303
|
return true;
|
|
331
304
|
}
|
|
305
|
+
const splitSliceNames = v => v.split('|').map( s=>s.trim() ).filter(s=>s);
|
|
306
|
+
|
|
332
307
|
export const
|
|
333
308
|
assureSlices = ( root, names) =>
|
|
334
|
-
names
|
|
335
|
-
{
|
|
336
|
-
|
|
309
|
+
splitSliceNames(names).map( xp =>
|
|
310
|
+
{ let d = root.ownerDocument
|
|
311
|
+
, append = n=> (root.append(n),n);
|
|
312
|
+
if(xp.includes('/'))
|
|
313
|
+
{ const ret = [], r = d.evaluate( xp, root );
|
|
337
314
|
for( let n; n = r.iterateNext(); )
|
|
338
315
|
ret.push( n )
|
|
339
316
|
return ret
|
|
340
317
|
}
|
|
341
|
-
return [...root.childNodes].find(n=>n.localName === xp) || create(xp);
|
|
318
|
+
return [...root.childNodes].find(n=>n.localName === xp) || append( create(xp,'',d) );
|
|
342
319
|
}).flat();
|
|
343
320
|
|
|
344
321
|
/**
|
|
@@ -351,19 +328,24 @@ assureSlices = ( root, names) =>
|
|
|
351
328
|
export function
|
|
352
329
|
event2slice( x, sliceNames, ev, dce )
|
|
353
330
|
{
|
|
331
|
+
if( ev.sliceProcessed )
|
|
332
|
+
return
|
|
333
|
+
ev.sliceProcessed = 1;
|
|
354
334
|
// evaluate slices[]
|
|
355
335
|
// inject @attributes
|
|
356
336
|
// inject event
|
|
357
337
|
// evaluate slice-value
|
|
358
338
|
// slice[i] = slice-value
|
|
359
|
-
assureSlices(x,sliceNames).map( s =>
|
|
339
|
+
return assureSlices( x, sliceNames ?? '' ).map( s =>
|
|
360
340
|
{
|
|
361
341
|
const d = x.ownerDocument
|
|
362
342
|
, el = ev.sliceEventSource
|
|
363
343
|
, sel = ev.sliceElement
|
|
364
|
-
, cleanSliceValue = ()=>[...s.childNodes].filter(n=>n.nodeType===3 || n.localName==='value').map(n=>n.remove());
|
|
344
|
+
, cleanSliceValue = ()=>[...s.childNodes].filter(n=>n.nodeType===3 || n.localName==='value' || n.localName==='form-data').map(n=>n.remove());
|
|
365
345
|
el.getAttributeNames().map( a => s.setAttribute( a, attr(el,a) ) );
|
|
366
346
|
[...s.childNodes].filter(n=>n.localName==='event').map(n=>n.remove());
|
|
347
|
+
if( 'validationMessage' in el )
|
|
348
|
+
s.setAttribute('validation-message', el.validationMessage);
|
|
367
349
|
ev.type==='init' && cleanSliceValue();
|
|
368
350
|
s.append( obj2node( ev, 'event', d ) );
|
|
369
351
|
if( sel.hasAttribute('slice-value') )
|
|
@@ -375,7 +357,12 @@ event2slice( x, sliceNames, ev, dce )
|
|
|
375
357
|
cleanSliceValue();
|
|
376
358
|
s.append( createText( d, v ) );
|
|
377
359
|
}else
|
|
378
|
-
{
|
|
360
|
+
{ if( 'elements' in el )
|
|
361
|
+
{ cleanSliceValue();
|
|
362
|
+
s.append( obj2node(new FormData(el),'value', s.ownerDocument) )
|
|
363
|
+
return s
|
|
364
|
+
}
|
|
365
|
+
const v = el.value ?? attr( sel, 'value' ) ;
|
|
379
366
|
cleanSliceValue();
|
|
380
367
|
if( v === null || v === undefined )
|
|
381
368
|
[...s.childNodes].filter(n=>n.localName!=='event').map(n=>n.remove());
|
|
@@ -385,6 +372,7 @@ event2slice( x, sliceNames, ev, dce )
|
|
|
385
372
|
else
|
|
386
373
|
s.append( obj2node(v,'value',s.ownerDocument) )
|
|
387
374
|
}
|
|
375
|
+
return s
|
|
388
376
|
})
|
|
389
377
|
}
|
|
390
378
|
|
|
@@ -392,7 +380,6 @@ function forEach$( el, css, cb){
|
|
|
392
380
|
if( el.querySelectorAll )
|
|
393
381
|
[...el.querySelectorAll(css)].forEach(cb)
|
|
394
382
|
}
|
|
395
|
-
const getByHashId = ( n, id )=> ( p => n===p? null: (p && ( p.querySelector(id) || getByHashId(p,id) ) ))( n.getRootNode() )
|
|
396
383
|
const loadTemplateRoots = async ( src, dce )=>
|
|
397
384
|
{
|
|
398
385
|
if( !src || !src.trim() )
|
|
@@ -420,12 +407,7 @@ const loadTemplateRoots = async ( src, dce )=>
|
|
|
420
407
|
}catch (error){ return [dce]}
|
|
421
408
|
}
|
|
422
409
|
export function mergeAttr( from, to )
|
|
423
|
-
{
|
|
424
|
-
{
|
|
425
|
-
if( !isText(to) ){ debugger }
|
|
426
|
-
return
|
|
427
|
-
}
|
|
428
|
-
for( let a of from.attributes)
|
|
410
|
+
{ for( let a of from.attributes)
|
|
429
411
|
{ a.namespaceURI? to.setAttributeNS( a.namespaceURI, a.name, a.value ) : to.setAttribute( a.name, a.value )
|
|
430
412
|
if( a.name === 'value')
|
|
431
413
|
to.value = a.value
|
|
@@ -504,12 +486,18 @@ export const xPathDefaults = x=>
|
|
|
504
486
|
// return xx.length ? `${a}|(${xPathDefaults(xx.join('??'))})[not(${a})]`: a
|
|
505
487
|
}
|
|
506
488
|
export const xPath = (x,root)=>
|
|
507
|
-
{
|
|
489
|
+
{
|
|
490
|
+
const xx = x.split('??');
|
|
491
|
+
if( xx.length > 1 )
|
|
492
|
+
return xPath(xx[0], root) || xPath(xx[1], root);
|
|
493
|
+
|
|
494
|
+
x = xPathDefaults(x);
|
|
508
495
|
|
|
509
496
|
const it = root.ownerDocument.evaluate(x, root);
|
|
510
497
|
switch( it.resultType )
|
|
511
498
|
{ case XPathResult.NUMBER_TYPE: return it.numberValue;
|
|
512
499
|
case XPathResult.STRING_TYPE: return it.stringValue;
|
|
500
|
+
case XPathResult.BOOLEAN_TYPE: return it.booleanValue;
|
|
513
501
|
}
|
|
514
502
|
|
|
515
503
|
let ret = '';
|
|
@@ -561,7 +549,10 @@ CustomElement extends HTMLElement
|
|
|
561
549
|
|
|
562
550
|
const dce = this
|
|
563
551
|
, sliceNodes = [...this.templateNode.querySelectorAll('[slice]')]
|
|
564
|
-
, sliceNames = sliceNodes.map(e=>attr(e,'slice'))
|
|
552
|
+
, sliceNames = sliceNodes.map(e=>attr(e,'slice'))
|
|
553
|
+
.filter(n=>!n.includes('/'))
|
|
554
|
+
.filter((v, i, a)=>a.indexOf(v) === i)
|
|
555
|
+
.map(splitSliceNames).flat()
|
|
565
556
|
, declaredAttributes = templateDocs.reduce( (ret,t) => { if( t.params ) ret.push( ...t.params ); return ret; }, [] );
|
|
566
557
|
|
|
567
558
|
class DceElement extends HTMLElement
|
|
@@ -572,6 +563,8 @@ CustomElement extends HTMLElement
|
|
|
572
563
|
{ let payload = this.childNodes;
|
|
573
564
|
if( this.firstElementChild?.tagName === 'TEMPLATE' )
|
|
574
565
|
{
|
|
566
|
+
if( this.firstElementChild !== this.lastElementChild )
|
|
567
|
+
{ console.error('payload should have TEMPLATE as only child', this.outerHTML ) }
|
|
575
568
|
const t = this.firstElementChild;
|
|
576
569
|
t.remove();
|
|
577
570
|
payload = t.content.childNodes;
|
|
@@ -595,7 +588,7 @@ CustomElement extends HTMLElement
|
|
|
595
588
|
})(x.ownerDocument.createElement( tag ))
|
|
596
589
|
injectData( x, 'payload' , payload , assureSlot );
|
|
597
590
|
this.innerHTML='';
|
|
598
|
-
injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
591
|
+
const attrsRoot = injectData( x, 'attributes' , this.attributes, e => createXmlNode( e.nodeName, e.value ) );
|
|
599
592
|
injectData( x, 'dataset', Object.keys( this.dataset ), k => createXmlNode( k, this.dataset[ k ] ) );
|
|
600
593
|
const sliceRoot = injectData( x, 'slice', sliceNames, k => createXmlNode( k, '' ) )
|
|
601
594
|
, sliceXPath = x => xPath(x, sliceRoot);
|
|
@@ -617,14 +610,12 @@ CustomElement extends HTMLElement
|
|
|
617
610
|
let timeoutID;
|
|
618
611
|
|
|
619
612
|
this.onSlice = ev=>
|
|
620
|
-
{
|
|
621
|
-
ev.sliceEventSource = ev.currentTarget || ev.target;
|
|
622
|
-
sliceEvents.push(ev);
|
|
613
|
+
{ sliceEvents.push(ev);
|
|
623
614
|
if( !timeoutID )
|
|
624
615
|
timeoutID = setTimeout(()=>
|
|
625
616
|
{ applySlices();
|
|
626
617
|
timeoutID =0;
|
|
627
|
-
},
|
|
618
|
+
},1);
|
|
628
619
|
};
|
|
629
620
|
const transform = this.transform = ()=>
|
|
630
621
|
{ if(this.#inTransform){ debugger }
|
|
@@ -651,20 +642,58 @@ CustomElement extends HTMLElement
|
|
|
651
642
|
}
|
|
652
643
|
})
|
|
653
644
|
|
|
654
|
-
forEach$( this,'[slice]', el =>
|
|
645
|
+
forEach$( this,'[slice],[slice-event]', el =>
|
|
655
646
|
{ if( !el.dceInitialized )
|
|
656
647
|
{ el.dceInitialized = 1;
|
|
657
|
-
|
|
658
|
-
(
|
|
659
|
-
|
|
648
|
+
let evs = attr(el,'slice-event');
|
|
649
|
+
if( attr(el,'custom-validity') )
|
|
650
|
+
evs += ' change submit';
|
|
651
|
+
|
|
652
|
+
[...new Set((evs || 'change') .split(' '))]
|
|
660
653
|
.forEach( t=> (el.localName==='slice'? el.parentElement : el)
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
654
|
+
.addEventListener( t, ev=>
|
|
655
|
+
{ ev.sliceElement = el;
|
|
656
|
+
ev.sliceEventSource = ev.currentTarget || ev.target;
|
|
657
|
+
const slices = event2slice( sliceRoot, attr( ev.sliceElement, 'slice'), ev, this );
|
|
658
|
+
|
|
659
|
+
forEach$(this,'[custom-validity]',el =>
|
|
660
|
+
{ if( !el.setCustomValidity )
|
|
661
|
+
return;
|
|
662
|
+
const x = attr( el, 'custom-validity' );
|
|
663
|
+
try
|
|
664
|
+
{ const v = x && xPath( x, attrsRoot );
|
|
665
|
+
el.setCustomValidity( v === true? '': v === false ? 'invalid' : v );
|
|
666
|
+
}catch(err)
|
|
667
|
+
{ console.error(err, 'xPath', x) }
|
|
668
|
+
})
|
|
669
|
+
const x = attr(el,'custom-validity')
|
|
670
|
+
, v = x && xPath( x, attrsRoot )
|
|
671
|
+
, msg = v === true? '' : v;
|
|
672
|
+
|
|
673
|
+
if( x )
|
|
674
|
+
{ el.setCustomValidity ? el.setCustomValidity( msg ) : ( el.validationMessage = msg );
|
|
675
|
+
slices.map( s => s.setAttribute('validation-message', msg ) );
|
|
676
|
+
if( ev.type === 'submit' )
|
|
677
|
+
{ if( v === true )
|
|
678
|
+
return;
|
|
679
|
+
setTimeout(transform,1)
|
|
680
|
+
if( !!v === v )
|
|
681
|
+
{ v || ev.preventDefault();
|
|
682
|
+
return v;
|
|
683
|
+
}
|
|
684
|
+
if( v )
|
|
685
|
+
{ ev.preventDefault();
|
|
686
|
+
return !1
|
|
687
|
+
}
|
|
688
|
+
return ;
|
|
689
|
+
}else
|
|
690
|
+
setTimeout(transform,1)
|
|
691
|
+
}
|
|
692
|
+
this.onSlice(ev);
|
|
693
|
+
} ));
|
|
665
694
|
if( !evs || evs.includes('init') )
|
|
666
695
|
{ if( el.hasAttribute('slice-value') || el.hasAttribute('value') || el.value )
|
|
667
|
-
this.onSlice({type:'init', target: el, sliceElement:el })
|
|
696
|
+
this.onSlice({type:'init', target: el, sliceElement:el, sliceEventSource:el })
|
|
668
697
|
else
|
|
669
698
|
el.value = sliceXPath( attr(el,'slice') )
|
|
670
699
|
}
|
|
@@ -693,12 +722,17 @@ CustomElement extends HTMLElement
|
|
|
693
722
|
|
|
694
723
|
get dce(){ return dce }
|
|
695
724
|
}
|
|
725
|
+
const registerTag = tag =>
|
|
726
|
+
{
|
|
727
|
+
if( window.customElements.get(tag) !== DceElement )
|
|
728
|
+
window.customElements.define( tag, DceElement);
|
|
729
|
+
};
|
|
696
730
|
if(tag)
|
|
697
|
-
|
|
731
|
+
registerTag(tag);
|
|
698
732
|
else
|
|
699
733
|
{ const t = tagName;
|
|
700
734
|
this.setAttribute('tag', t );
|
|
701
|
-
|
|
735
|
+
registerTag(t);
|
|
702
736
|
const el = document.createElement(t);
|
|
703
737
|
this.getAttributeNames().forEach(a=>el.setAttribute(a,this.getAttribute(a)));
|
|
704
738
|
el.append(...[...this.childNodes].filter( e => e.localName!=='style') );
|