@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 ADDED
@@ -0,0 +1,11 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ indent_size = 4
7
+ indent_style = space
8
+ insert_final_newline = true
9
+ max_line_length = 120
10
+ tab_width = 4
11
+ trim_trailing_whitespace = true
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-test
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-test@0.0.20/coverage/coverage.svg
352
- [coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.20/coverage/lcov-report/index.html
353
- [storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.20/storybook-static/index.html?path=/story/welcome--introduction
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
@@ -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.20",
119
+ "version": "0.0.22",
120
120
  "js-types-syntax": "typescript",
121
121
  "description-markup": "markdown",
122
122
  "contributions": {
@@ -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,'&#38;')+'"');
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].filter( e=>e.nodeType === 3 ).forEach( e=>
126
- { if( e.parentNode.localName === 'style' )
127
- return;
128
- const m = e.data.matchAll( /{([^}]*)}/g );
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 < e.data.length)
142
- tt.push( txt( e.data.substring(l,e.data.length) ));
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()"/></xsl:copy></xsl:template>
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()"/></xsl:element></xsl:template>
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.split('|').map(n=>n.trim()).map( xp =>
335
- { if(xp.includes('/'))
336
- { const ret = [], r = root.ownerDocument.evaluate( xp, root );
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
- { const v = el.value ?? attr( sel, 'value' ) ;
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
- { if( isText(from) )
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
- { x = xPathDefaults(x);
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')).filter(n=>!n.includes('/')).filter((v, i, a)=>a.indexOf(v) === i)
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
- { ev.stopPropagation?.();
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
- },10);
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
- const evs = attr(el,'slice-event');
658
- (evs || 'change')
659
- .split(' ')
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
- .addEventListener( t, ev=>
662
- { ev.sliceElement = el;
663
- this.onSlice(ev)
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
- window.customElements.define( tag, DceElement);
731
+ registerTag(tag);
698
732
  else
699
733
  { const t = tagName;
700
734
  this.setAttribute('tag', t );
701
- window.customElements.define( t, DceElement);
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') );