@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.
Files changed (47) hide show
  1. package/.idea/inspectionProfiles/Project_Default.xml +22 -7
  2. package/.idea/misc.xml +4 -5
  3. package/.vs/VSWorkspaceState.json +8 -0
  4. package/.vs/custom-element/FileContentIndex/1487e471-3751-47bc-a499-d78eda924eda.vsidx +0 -0
  5. package/.vs/custom-element/v17/.wsuo +0 -0
  6. package/.vs/slnx.sqlite +0 -0
  7. package/README.md +71 -19
  8. package/custom-element.js +241 -66
  9. package/demo/a.html +25 -11
  10. package/demo/confused.svg +36 -36
  11. package/demo/dom-merge.html +124 -0
  12. package/demo/embed-1.html +2 -2
  13. package/demo/external-template.html +9 -7
  14. package/demo/hex-grid-dce.html +183 -0
  15. package/demo/hex-grid-transform.png +0 -0
  16. package/demo/hex-grid.html +66 -0
  17. package/demo/html-template.html +125 -12
  18. package/demo/html-template.xhtml +44 -44
  19. package/demo/html-template.xml +44 -44
  20. package/demo/http-request.html +44 -57
  21. package/demo/local-storage.html +23 -18
  22. package/demo/location-element.html +59 -58
  23. package/demo/s.xml +1 -0
  24. package/demo/s.xslt +159 -0
  25. package/demo/scoped-css.html +170 -0
  26. package/demo/ss.html +32 -0
  27. package/demo/table.xml +24 -24
  28. package/demo/table.xsl +292 -292
  29. package/demo/template.xsl +45 -45
  30. package/demo/tree.xml +24 -24
  31. package/demo/tree.xsl +32 -32
  32. package/demo/xhtml-template.xhtml +44 -44
  33. package/demo/z.html +42 -0
  34. package/demo/z.xml +60 -0
  35. package/http-request.js +28 -37
  36. package/index.html +23 -19
  37. package/input-text.js +17 -0
  38. package/local-storage.js +28 -35
  39. package/location-element.js +15 -16
  40. package/package.json +1 -1
  41. package/0/a.html +0 -19
  42. package/0/a.xml +0 -10
  43. package/0/a.xsl +0 -66
  44. package/0/a1.xsl +0 -38
  45. package/0/ab.xsl +0 -23
  46. package/0/az.xml +0 -30
  47. 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="12">
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="custom-element" />
15
- <item index="7" class="java.lang.String" itemvalue="dce-external-4" />
16
- <item index="8" class="java.lang.String" itemvalue="html-demo-element" />
17
- <item index="9" class="java.lang.String" itemvalue="dce-external" />
18
- <item index="10" class="java.lang.String" itemvalue="dce-internal" />
19
- <item index="11" class="java.lang.String" itemvalue="dce-hash" />
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
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectRootManager">
4
- <output url="file://$PROJECT_DIR$/out" />
5
- </component>
1
+ <project version="4">
2
+ <component name="ProjectRootManager">
3
+ <output url="file://$PROJECT_DIR$/out" />
4
+ </component>
6
5
  </project>
@@ -0,0 +1,8 @@
1
+ {
2
+ "ExpandedNodes": [
3
+ "",
4
+ "\\demo"
5
+ ],
6
+ "SelectedNode": "\\demo",
7
+ "PreviewInSolutionExplorer": false
8
+ }
Binary file
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><xsl:value-of select="title"/></h3> <!-- title is an attribute in instance
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: <xsl:value-of select='//smile'/></div>
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 missmatch to given
169
- in template. In such cases the `html:` prefix in front of troubled tag would solve the parsing.
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
- <local-storage key="basket" slice="basket"></local-storage>
174
- <html:table>
175
- <xsl:for-each select="//slice/basket/@*">
176
- <html:tr>
177
- <html:th><xsl:value-of select="name()"/></html:th>
178
- <html:td><xsl:value-of select="."/></html:td>
179
- </html:tr>
180
- </xsl:for-each>
181
- </html:table>
182
- count:<xsl:value-of select="count(//slice/basket/@*)"/>
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:<xsl:value-of select='name()'/></b>
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.11/coverage/coverage.svg
222
- [coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.11/coverage/lcov-report/index.html
223
- [storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.11/storybook-static/index.html?path=/story/welcome--introduction
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 XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>'
2
- , XSL_NS_URL = 'http://www.w3.org/1999/XSL/Transform'
3
- , DCE_NS_URL ="urn:schemas-epa-wg:dce";
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
- , createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, 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) );
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( XML_DECLARATION + xmlString, "application/xml" )
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 dom = xml2dom(
81
- `<xsl:stylesheet version="1.0"
82
- xmlns:xsl="${ XSL_NS_URL }"
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:output method="html" />
85
-
159
+ <xsl:template mode="payload" match="attributes"></xsl:template>
86
160
  <xsl:template match="/">
87
- <xsl:for-each select="//attributes">
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
- <xsl:copy-of select="//payload/*[@slot=$slotname]"/>
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:template name="attributes"></xsl:template>
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
- const attrsTemplate = dom.documentElement.lastElementChild.previousElementSibling
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 slot2xsl = s =>
126
- { const v = dom.firstElementChild.lastElementChild.lastElementChild.cloneNode(true);
127
- v.firstElementChild.setAttribute('select',`'${s.name}'`)
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
- for( const s of attrsTemplate.querySelectorAll('slot') )
134
- s.parentNode.replaceChild( slot2xsl(s), s )
208
+ forEach$( payload,'slot', s => s.parentNode.replaceChild( slot2xsl(s), s ) )
135
209
 
136
- // apply bodyXml changes
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
- ? create(s, data)
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
- for( let n of el.querySelectorAll(css) )
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
- , templateDocs = templateRoots.map( n => createXsltFromDom( n ) )
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: ()=>xp.map( td => xmlString(td) ).join('\n') });
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
- { const x = createNS( DCE_NS_URL,'datadom' );
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
- injectData( x, 'attributes' , this.attributes, e => create( e.nodeName, e.value ) );
239
- injectData( x, 'dataset', Object.keys( this.dataset ), k => create( k, this.dataset[ k ] ) );
240
- const sliceRoot = injectData( x, 'slice', sliceNames, k => create( k, '' ) );
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 => p.transformToFragment(x, document) );
275
- this.innerHTML = '';
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
- { [ ...f.childNodes ].forEach( e => this.append( e ) );
278
-
279
- forEach$( this,'[slice]', el =>
280
- { if( 'function' === typeof el.sliceInit )
281
- { const s = attr( el,'slice' );
282
- slices[s] = el.sliceInit( slices[s] );
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 = 'dce-'+crypto.randomUUID()
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
  }