@epa-wg/custom-element 0.0.19 → 0.0.21

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.19/coverage/coverage.svg
352
- [coverage-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.19/coverage/lcov-report/index.html
353
- [storybook-url]: https://unpkg.com/@epa-wg/custom-element-test@0.0.19/storybook-static/index.html?path=/story/welcome--introduction
351
+ [coverage-image]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.21/coverage/src/custom-element/coverage.svg
352
+ [coverage-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.21/coverage/src/custom-element/index.html
353
+ [storybook-url]: https://unpkg.com/@epa-wg/custom-element-dist@0.0.21/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.19",
119
+ "version": "0.0.21",
120
120
  "js-types-syntax": "typescript",
121
121
  "description-markup": "markdown",
122
122
  "contributions": {
@@ -1,4 +1,5 @@
1
1
  export function log(x: any): void;
2
+ export function deepEqual(a: any, b:any): boolean|0;
2
3
 
3
4
  /**
4
5
  * @summary Declarative Custom Element as W3C proposal PoC with native(XSLT) based templating
package/custom-element.js CHANGED
@@ -11,7 +11,8 @@ const attr = (el, attr)=> el.getAttribute?.(attr)
11
11
  , isNode = e => e && typeof e.nodeType === 'number'
12
12
  , create = ( tag, t = '', d=document ) => ( e => ((t && e.append(createText(d.ownerDocument||d, t))),e) )((d.ownerDocument || d ).createElement( tag ))
13
13
  , createText = ( d, t) => (d.ownerDocument || d ).createTextNode( t )
14
- , emptyNode = n => { while(n.firstChild) n.firstChild.remove(); n.getAttributeNames().map( a => n.removeAttribute(a) ); return n; }
14
+ , removeChildren = n => { while(n.firstChild) n.firstChild.remove(); return n; }
15
+ , emptyNode = n => { n.getAttributeNames().map( a => n.removeAttribute(a) ); return removeChildren(n); }
15
16
  , createNS = ( ns, tag, t = '' ) => ( e => ((e.innerText = t||''),e) )(document.createElementNS( ns, tag ))
16
17
  , xslNs = x => ( x?.setAttribute('xmlns:xsl', XSL_NS_URL ), x )
17
18
  , xslHtmlNs = x => ( x?.setAttribute('xmlns:xhtml', HTML_NS_URL ), xslNs(x) )
@@ -62,43 +63,14 @@ assureSlot( e )
62
63
  return e;
63
64
  }
64
65
 
65
- export function
66
- Json2Xml( o, tag )
67
- {
68
- if( typeof o === 'string' )
69
- return o;
70
-
71
- const noTag = "string" != typeof tag;
72
-
73
- if( o instanceof Array )
74
- { noTag && (tag = 'array');
75
- return "<"+tag+">"+o.map(function(el){ return Json2Xml(el,tag); }).join()+"</"+tag+">";
76
- }
77
- noTag && (tag = 'r');
78
- tag=tag.replace( /[^a-z0-9\-]/gi,'_' );
79
- var oo = {}
80
- , ret = [ "<"+tag+" "];
81
- for( let k in o )
82
- if( typeof o[k] == "object" )
83
- oo[k] = o[k];
84
- else
85
- ret.push( k.replace( /[^a-z0-9\-]/gi,'_' ) + '="'+o[k].toString().replace(/&/gi,'&#38;')+'"');
86
- if( oo )
87
- { ret.push(">");
88
- for( let k in oo )
89
- ret.push( Json2Xml( oo[k], k ) );
90
- ret.push("</"+tag+">");
91
- }else
92
- ret.push("/>");
93
- return ret.join('\n');
94
- }
95
-
96
66
  export function
97
67
  obj2node( o, tag, doc )
98
- {
99
- if( typeof o === 'function'){debugger}
100
- if( typeof o === 'string' )
68
+ { const t = typeof o;
69
+ if( t === 'function'){debugger}
70
+ if( t === 'string' )
101
71
  return create(tag,o,doc);
72
+ if( t === 'number' )
73
+ return create(tag,''+o,doc);
102
74
 
103
75
  if( o instanceof Array )
104
76
  { const ret = create('array');
@@ -163,8 +135,8 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
163
135
  <xsl:template match="*[name()='template']"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
164
136
  <xsl:template match="*"><xsl:apply-templates mode="sanitize" select="*|text()"/></xsl:template>
165
137
  <xsl:template match="*[name()='svg']|*[name()='math']"><xsl:apply-templates mode="sanitize" select="."/></xsl:template>
166
- <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>
167
- <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>
138
+ <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>
139
+ <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>
168
140
  <xsl:template mode="sanitize" match="*|@*"><xsl:copy><xsl:apply-templates mode="sanitize" select="*|@*|text()"/></xsl:copy></xsl:template>
169
141
  <xsl:template mode="sanitize" match="text()[normalize-space(.) = '']"/>
170
142
  <xsl:template mode="sanitize" match="text()"><dce-text><xsl:copy/></dce-text></xsl:template>
@@ -203,8 +175,7 @@ createXsltFromDom( templateNode, S = 'xsl:stylesheet' )
203
175
  <xsl:choose>
204
176
  <xsl:when test="//attr">{//attr}</xsl:when>
205
177
  <xsl:otherwise>{def}</xsl:otherwise>
206
- </xsl:choose>
207
- <xsl:value-of select="."/></xsl:template>
178
+ </xsl:choose><xsl:value-of select="."></xsl:value-of></xsl:template>
208
179
  <xsl:template mode="payload" match="attributes"></xsl:template>
209
180
  <xsl:template match="/">
210
181
  <xsl:apply-templates mode="payload" select="/datadom/attributes"/>
@@ -364,17 +335,23 @@ event2slice( x, sliceNames, ev, dce )
364
335
  ev.type==='init' && cleanSliceValue();
365
336
  s.append( obj2node( ev, 'event', d ) );
366
337
  if( sel.hasAttribute('slice-value') )
367
- { s.setAttribute('value', el.value );
338
+ { if( el.value === undefined)
339
+ s.removeAttribute('value')
340
+ else
341
+ s.setAttribute('value', el.value );
368
342
  const v = xPath( attr( sel, 'slice-value'),s );
369
343
  cleanSliceValue();
370
344
  s.append( createText( d, v ) );
371
345
  }else
372
- { const v = el.value || attr( sel, 'value' ) ;
346
+ { const v = el.value ?? attr( sel, 'value' ) ;
373
347
  cleanSliceValue();
374
- if( isString(v) )
375
- s.append( createText( d, v) );
348
+ if( v === null || v === undefined )
349
+ [...s.childNodes].filter(n=>n.localName!=='event').map(n=>n.remove());
376
350
  else
377
- s.append( obj2node(v,'value',s.ownerDocument) )
351
+ if( isString(v) )
352
+ s.append( createText( d, v) );
353
+ else
354
+ s.append( obj2node(v,'value',s.ownerDocument) )
378
355
  }
379
356
  })
380
357
  }
@@ -445,6 +422,8 @@ export function assureUnique(n, id=0)
445
422
  }
446
423
  export function merge( parent, fromArr )
447
424
  {
425
+ if(!fromArr.length)
426
+ return removeChildren(parent);
448
427
  const id2old = {};
449
428
  for( let c of parent.childNodes)
450
429
  { ASSERT( !id2old[c.dceId] );
@@ -455,8 +434,8 @@ export function merge( parent, fromArr )
455
434
  id2old[attr(c, 'data-dce-id') || 0] = c;
456
435
  }
457
436
  for( let e of [...fromArr] )
458
- {
459
- const o = id2old[ attr(e, 'data-dce-id') || e.dceId ];
437
+ { const k = attr(e, 'data-dce-id') || e.dceId;
438
+ const o = id2old[ k ];
460
439
  if( o )
461
440
  { if( isText(e) )
462
441
  { if( o.nodeValue !== e.nodeValue )
@@ -466,9 +445,12 @@ export function merge( parent, fromArr )
466
445
  if( o.childNodes.length || e.childNodes.length )
467
446
  merge(o, e.childNodes)
468
447
  }
448
+ delete id2old[ k ]
469
449
  }else
470
450
  parent.append( e )
471
451
  }
452
+ for( let v of Object.values(id2old) )
453
+ v.remove();
472
454
  }
473
455
  export function assureUID(n,attr)
474
456
  { if( !n.hasAttribute(attr) )
@@ -679,12 +661,17 @@ CustomElement extends HTMLElement
679
661
 
680
662
  get dce(){ return dce }
681
663
  }
664
+ const registerTag = tag =>
665
+ {
666
+ if( window.customElements.get(tag) !== DceElement )
667
+ window.customElements.define( tag, DceElement);
668
+ };
682
669
  if(tag)
683
- window.customElements.define( tag, DceElement);
670
+ registerTag(tag);
684
671
  else
685
672
  { const t = tagName;
686
673
  this.setAttribute('tag', t );
687
- window.customElements.define( t, DceElement);
674
+ registerTag(t);
688
675
  const el = document.createElement(t);
689
676
  this.getAttributeNames().forEach(a=>el.setAttribute(a,this.getAttribute(a)));
690
677
  el.append(...[...this.childNodes].filter( e => e.localName!=='style') );
package/demo/a.html CHANGED
@@ -6,6 +6,7 @@
6
6
  <title>custom-element Declarative Custom Element implementation demo</title>
7
7
  <link rel="icon" href="./wc-square.svg"/>
8
8
  <script type="module" src="../http-request.js"></script>
9
+ <script type="module" src="../local-storage.js"></script>
9
10
  <script type="module" src="../custom-element.js"></script>
10
11
 
11
12
  <style>
@@ -40,22 +41,23 @@
40
41
  </head>
41
42
  <body>
42
43
 
43
- <custom-element>
44
- <template>
45
- <button slice="clickcount"
46
- slice-event="click"
47
- slice-value="//clickcount + 1" >
48
- +
49
- </button>
50
- <button slice="clickcount"
51
- slice-event="click"
52
- slice-value="//clickcount - 1" >
53
- -
54
- </button>
55
- <input slice="clickcount" type="number" value="{//clickcount ?? 0}" />
56
- {//clickcount}
57
- </template>
58
- </custom-element>
59
44
 
45
+ <custom-element tag="dce-1">
46
+ <template >
47
+ <style >
48
+ color:red;
49
+ </style>
50
+
51
+ <u ><slot>red</slot></u>
52
+ </template>
53
+ </custom-element>
54
+ <dce-1></dce-1> *
55
+ <dce-1><template >
56
+ <style >
57
+ color:green;
58
+ </style>
59
+
60
+ <u >green</u>
61
+ </template></dce-1>
60
62
  </body>
61
63
  </html>
@@ -6,7 +6,6 @@
6
6
  <link rel="icon" href="./wc-square.svg"/>
7
7
 
8
8
  <script type="module" src="../http-request.js"></script>
9
- <script type="module" src="../input-text.js"></script>
10
9
  <script type="module" src="../custom-element.js"></script>
11
10
  <style>
12
11
  @import "./demo.css";
@@ -6,7 +6,6 @@
6
6
  <link rel="icon" href="./wc-square.svg"/>
7
7
 
8
8
  <script type="module" src="../http-request.js"></script>
9
- <script type="module" src="../input-text.js"></script>
10
9
  <script type="module" src="../custom-element.js"></script>
11
10
  <style>
12
11
  @import "./demo.css";
@@ -94,9 +93,9 @@
94
93
  <form>
95
94
  <label>
96
95
  <input type="text"
97
- value="Type time update"
96
+ value="{//txt ?? 'Type time update'}"
98
97
  slice="txt"
99
- slice-event="keyup"/>
98
+ slice-event="init input"/>
100
99
 
101
100
  <span> Character count:
102
101
  <b> {string-length(//slice/txt)} </b>
@@ -31,6 +31,33 @@
31
31
  <a href="../index.html"><h3><code>custom-element</code> demo</h3></a>
32
32
  </nav>
33
33
 
34
+
35
+ <html-demo-element legend="0. url from text to http-request "
36
+ description="read data from arbitrary URL">
37
+ <template>
38
+ <custom-element>
39
+ <template>
40
+
41
+ <button slice="url-string" slice-value="''" slice-event="click">⬇️set blank</button>
42
+ <button slice="url-string" slice-value="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click">⬇️https://pokeapi.co/api/v2/pokemon?limit=6</button>
43
+ <button slice="url-string" slice-value="'https://pokeapi.co/api/v2/pokemon?limit=3'" slice-event="click">⬇️https://pokeapi.co/api/v2/pokemon?limit=3</button>
44
+ <input slice="url-string" value="{ //url-string ?? '' }" style="width:100%"/>
45
+ <button slice="fetch-url" slice-event="click" slice-value="//url-string"> GET </button>
46
+ <http-request
47
+ url="{//fetch-url}"
48
+ slice="request_slice"
49
+ type="text"
50
+ mode="cors"
51
+ ></http-request>
52
+ <code>//fetch-url</code> from <code>{//fetch-url}</code><br/>
53
+ <ul><for-each select="//results">
54
+ <li>{@name}</li>
55
+ </for-each></ul>
56
+ </template>
57
+ </custom-element>
58
+ </template>
59
+ </html-demo-element>
60
+
34
61
  <html-demo-element legend="1. http-request simplest"
35
62
  description="load the list of pokemons">
36
63
  <p>Should display 6 image buttons with pokemon name </p>
@@ -16,14 +16,119 @@
16
16
  td,th{text-align: right; }
17
17
  caption{ padding: 1rem; font-weight: bolder; font-family: sans-serif; }
18
18
  </style>
19
+
20
+ <script>
21
+ window.JsonSample = {a:1,b:'B'};
22
+ </script>
19
23
  </head>
20
24
  <body>
21
25
 
22
26
  <nav>
23
27
  <a href="../index.html"><h3><code>custom-element</code> demo</h3></a>
24
28
  </nav>
29
+ <main>
30
+ <code>local-storage</code> allows to read and write its value to the key in <code>localStorage</code>.
31
+ The <code>type</code> attribute allows to place the validation constrains to the value: when the value does not
32
+ match the expected type, it would not be assigned, keeping empty <code>value</code> instead.
33
+ </main>
34
+ <html-demo-element legend="0. read localStorage text value"
35
+ description="click should set text-key slice via localStorage change.">
36
+ <template>
37
+ <custom-element>
38
+ <template>
39
+ <local-storage key="textKey" slice="text-key" type="text" live="live"></local-storage>
40
+ <button onclick="localStorage.setItem('textKey','text value')">text value</button>
41
+ <button onclick="localStorage.setItem('textKey','another value')">another value</button>
42
+ //text-key: <code>{//text-key}</code>
43
+ </template>
44
+ </custom-element>
45
+ </template>
46
+ </html-demo-element>
47
+
48
+ <html-demo-element legend="1. always override "
49
+ description="value in localStorage[] should be always reset to ABC. click should set text-key slice via localStorage change.">
50
+ <template>
51
+ <custom-element>
52
+ <template>
53
+ <!-- always reset -->
54
+ <local-storage slice="override-key" key="overrideKey" type="text" value="ABC"></local-storage>
55
+ <button onclick="localStorage.setItem( 'overrideKey','text value')">text value</button>
56
+ <button onclick="localStorage.removeItem('overrideKey' )">clear key</button>
57
+ //override-key: <code>{ //override-key }</code>
58
+ </template>
59
+ </custom-element>
60
+ </template>
61
+ </html-demo-element>
62
+
63
+ <html-demo-element legend="2. from storage with default "
64
+ description="default overridden by button, refresh should preserve updated value">
65
+ <template>
66
+ <custom-element>
67
+ <template>
68
+ <!-- initially set value to DEF and update by button. On reload the value picked from localStorage -->
69
+ <local-storage key="attr2Key" slice="attr2-key" type="text" live="live" slice-value="@value ?? 'DEF'"></local-storage>
70
+ <button onclick="localStorage.clear()">clear localStorage</button>
71
+ <button onclick="localStorage.removeItem('attr2Key')">clear key</button>
72
+ <button onclick="localStorage.setItem('attr2Key','text value')">updated value</button>
73
+ //attr2-key: <code>{//attr2-key}</code>
74
+ </template>
75
+ </custom-element>
76
+ </template>
77
+ </html-demo-element>
78
+
79
+ <html-demo-element legend="3. localStorage type"
80
+ description="type validation happy path. Invalid for type value in storage would be treated as null">
81
+ <template>
82
+ <custom-element>
83
+ <template>
84
+ <local-storage key="dateKey" slice="date-key" type="date" live="live"></local-storage>
85
+ <local-storage key="timeKey" slice="time-key" type="time" live="live"></local-storage>
86
+ <local-storage key="localDateTimeKey" slice="local-date-time" type="datetime-local" live="live"></local-storage>
87
+ <local-storage key="numberKey" slice="number-key" type="number" live="live"></local-storage>
88
+ <local-storage key="jsonKey" slice="json-key" type="json" live="live"></local-storage>
89
+ <input id="typesinput" placeholder="set value" /><button onclick="
90
+ 'dateKey,timeKey,localDateTimeKey,numberKey,jsonKey'.split(',')
91
+ .map( k=> localStorage.setItem(k, typesinput.value) )
92
+ "> set to all</button><br/>
93
+ <hr/>
94
+ date-key:
95
+ <button onclick="localStorage.setItem('dateKey', '2024-04-20T03:58:42.131Z')" >2024-04-21T03:58:42.131Z </button>
96
+ <button onclick="localStorage.setItem('dateKey', new Date(Date.now()).toISOString())" >now </button>
97
+ <button onclick="localStorage.setItem('dateKey', 'ABC' )" >date ABC - invalid </button>
98
+ <code>{//date-key }</code><br/>
99
+ time-key:
100
+ <button onclick="localStorage.setItem('timeKey', '13:30')" >13:30 </button>
101
+ <code>{//time-key }</code><br/>
102
+ local-date-time:
103
+ <button onclick="localStorage.setItem('localDateTimeKey', '1977-04-01T14:00:30')" >21977-04-01T14:00:30 - local </button>
104
+ <code>{//local-date-time}</code><br/>
105
+ number-key:
106
+ <button onclick="localStorage.setItem('numberKey', '2024' )" >2024 - number </button>
107
+ <button onclick="localStorage.setItem('numberKey', '24' )" >24 - number </button>
108
+ <button onclick="localStorage.setItem('numberKey', '1.23456e+5' )" >1.23456e+5 </button>
109
+ <button onclick="localStorage.setItem('numberKey', '0001' )" >0001 </button>
110
+ <button onclick="localStorage.setItem('numberKey', '000' )" >000 </button>
111
+ <button onclick="localStorage.setItem('numberKey', '0' )" >0 </button>
112
+ <button onclick="localStorage.setItem('numberKey', 'ABC' )" >ABC - invalid, NaN </button>
113
+ <code>{//number-key }</code> <br/>
114
+ <fieldset>
115
+ <legend>json-key: </legend>
116
+
117
+ <button onclick="localStorage.setItem('jsonKey', JSON.stringify('ABC'))" >'ABC' - string </button>
118
+ <button onclick="localStorage.setItem('jsonKey', JSON.stringify(12.345))" >12.345 - number </button>
119
+ <button onclick="localStorage.setItem('jsonKey', JSON.stringify(window.JsonSample) )" >a:1,b:'B' -json </button>
120
+ <button onclick="localStorage.setItem('jsonKey', 'ABC' )" >ABC - invalid </button><br/>
121
+ json-key:<code><xsl:apply-templates select="//json-key/value/@*|//json-key/text()|//json-key/value/text()" mode="json"></xsl:apply-templates></code>
122
+ </fieldset>
123
+ <xsl:template mode="json" match="*|@*">
124
+ <div>{name()} : {.}</div>
125
+ </xsl:template>
126
+ </template>
127
+ </custom-element>
128
+ </template>
129
+ </html-demo-element>
25
130
 
26
- <html-demo-element legend="1. localStorage simplest"
131
+ <html-demo-element legend="3. localStorage simplest"
27
132
  description="local-storage read only during initial and only render, does not track the changes.">
28
133
  <p>Has to produce 12🍒</p>
29
134
  <template>
@@ -30,9 +30,9 @@
30
30
  <input name="p2" value="def"/>
31
31
  <input type="submit" value="params"/>
32
32
  </form>
33
- <button onclick="history.pushState( {},'', 'location.html?pushstate')"
33
+ <button onclick="history.pushState( {},'', 'location-element.html?pushstate')"
34
34
  >history.pushState</button>
35
- <button onclick="history.replaceState( {},'', 'location.html?replaceState#dce1')"
35
+ <button onclick="history.replaceState( {},'', 'location-element.html?replaceState#dce1')"
36
36
  >history.replaceState</button>
37
37
 
38
38
  </template>
@@ -113,7 +113,7 @@
113
113
  </html-demo-element>
114
114
 
115
115
 
116
- <html-demo-element legend="3. External URL as SRC attribute"
116
+ <html-demo-element legend="3. External URL as HREF attribute"
117
117
  description="url parsed and populated into slice."
118
118
  id="dce3">
119
119
  <p>Has to produce URL properties</p>
@@ -121,12 +121,12 @@
121
121
  <custom-element tag="dce-3" hidden>
122
122
  <template>
123
123
 
124
- <location-element slice="src-url" src="https://my.example?a=1&b=2#3"></location-element>
124
+ <location-element slice="href-url" href="https://my.example?a=1&b=2#3"></location-element>
125
125
 
126
126
  <xhtml:table>
127
127
  <xhtml:tbody>
128
128
  <xhtml:tr><xhtml:th><h3>URL properties</h3></xhtml:th></xhtml:tr>
129
- <for-each select="//slice/src-url/value/@*">
129
+ <for-each select="//slice/href-url/value/@*">
130
130
  <xhtml:tr>
131
131
  <xhtml:th>{name()}</xhtml:th>
132
132
  <xhtml:td>{.}</xhtml:td>
@@ -135,7 +135,7 @@
135
135
  </xhtml:tbody>
136
136
  <xhtml:tbody>
137
137
  <xhtml:tr><xhtml:th><h3>URL parameters</h3></xhtml:th></xhtml:tr>
138
- <for-each select="//slice/src-url/value/params/*">
138
+ <for-each select="//slice/href-url/value/params/*">
139
139
  <xhtml:tr>
140
140
  <xhtml:th>{name()}</xhtml:th>
141
141
  <xhtml:td>{.}</xhtml:td>
package/demo/s.xml CHANGED
@@ -1,6 +1,14 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <datadom><payload><span xmlns="http://www.w3.org/1999/xhtml" slot=""> </span><button xmlns="http://www.w3.org/1999/xhtml" slice="clickcount" slice-event="click" slice-value="//clickcount + 1" slot="">
3
- +
4
- </button><span xmlns="http://www.w3.org/1999/xhtml" slot=""> </span><button xmlns="http://www.w3.org/1999/xhtml" slice="clickcount" slice-event="click" slice-value="//clickcount - 1" slot="">
5
- -
6
- </button><span xmlns="http://www.w3.org/1999/xhtml" slot=""> </span><input xmlns="http://www.w3.org/1999/xhtml" slice="clickcount" type="number" value="{//clickcount ?? 0}" slot="" /><span xmlns="http://www.w3.org/1999/xhtml" slot=""> {//clickcount} </span></payload><attributes><tag>dce-72e0d208-b3b4-4b0a-8798-a094beaf5fe1</tag></attributes><dataset/><slice><clickcount xmlns="" slice="clickcount" type="number" value="" data-dce-id="2" slice-event="click" slice-value="//clickcount + 1"><event isTrusted="true" pointerId="1" width="1" height="1" pressure="0" tiltX="0" tiltY="0" azimuthAngle="0" altitudeAngle="1.5707963267948966" tangentialPressure="0" twist="0" pointerType="mouse" isPrimary="false" screenX="19" screenY="99" clientX="19" clientY="12" ctrlKey="false" shiftKey="false" altKey="false" metaKey="false" button="0" buttons="0" pageX="19" pageY="12" x="19" y="12" offsetX="10" offsetY="0" movementX="0" movementY="0" layerX="19" layerY="12" detail="1" which="1" type="click" eventPhase="0" bubbles="true" cancelable="true" defaultPrevented="false" composed="true" timeStamp="5460750.8000000715" returnValue="true" cancelBubble="false" NONE="0" CAPTURING_PHASE="1" AT_TARGET="2" BUBBLING_PHASE="3"><relatedTarget/><fromElement/><toElement/><sourceCapabilities firesTouchEvents="false"/><currentTarget/></event>46</clickcount></slice></datadom>
1
+ <div xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><!-- wrapping into template to prevent images loading within DCE declaration -->
2
+ <local-storage key="undefined" slice="s" }=""></local-storage>
3
+
4
+ <input placeholder="value for localStorage" slice="s" value="{ //s ?? 'undefined' }" />
5
+ <button>set</button>
6
+ <button slice="sv" slice-value="''" slice-event="click">set blank</button>
7
+ <button slice="sv" slice-value="'/reflect'" slice-event="click">/reflect</button>
8
+ <button slice="sv" slice-value="'/pokemon?limit=6'" slice-event="click">/pokemon?limit=6</button>
9
+ <button slice="sv" slice-value="'/pokemon?limit=3'" slice-event="click">/pokemon?limit=3</button>
10
+ <br />
11
+ <var>undefined</var>:<code>{ //slice/s }</code>
12
+ <br />
13
+ undefined
14
+ </div>
package/demo/s.xslt CHANGED
@@ -1,18 +1,57 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:dce="urn:schemas-epa-wg:dce" xmlns:exsl="http://exslt.org/common" version="1.0" exclude-result-prefixes="exsl">
1
+ <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xhtml="http://www.w3.org/1999/xhtml"
2
+ xmlns:dce="urn:schemas-epa-wg:dce" xmlns:exsl="http://exslt.org/common" version="1.0"
3
+ exclude-result-prefixes="exsl">
3
4
  <xsl:template match="ignore">
4
5
  <xsl:choose>
5
- <xsl:when test="//attr"><xsl:value-of select="//attr"/></xsl:when>
6
- <xsl:otherwise><xsl:value-of select="def"/></xsl:otherwise>
6
+ <xsl:when test="//attr">
7
+ <xsl:value-of select="//attr"/>
8
+ </xsl:when>
9
+ <xsl:otherwise>
10
+ <xsl:value-of select="def"/>
11
+ </xsl:otherwise>
7
12
  </xsl:choose>
8
- <xsl:value-of select="."/></xsl:template>
9
- <xsl:template mode="payload" match="attributes"><dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" data-dce-id="1"><button xmlns="" slice="clickcount" slice-event="click" slice-value="//clickcount + 1" data-dce-id="2">
10
- +
11
- </button><button xmlns="" slice="clickcount" slice-event="click" slice-value="//clickcount - 1" data-dce-id="3">
12
- -
13
- </button><input xmlns="" slice="clickcount" type="number" value="{concat( //clickcount , substring( 0 , (1+string-length( 0 )) * string-length( //clickcount ) ) )}" data-dce-id="4"/><dce-text xmlns="" data-dce-id="5">
14
- <xsl:value-of select="//clickcount"/>
15
- </dce-text></dce-root></xsl:template>
13
+ <xsl:value-of select="."/>
14
+ </xsl:template>
15
+ <xsl:template mode="payload" match="attributes">
16
+ <dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" data-dce-id="1">
17
+ <http-request xmlns="" url="{concat(//s , '') }" slice="s" data-dce-id="2"/>
18
+ <p xmlns="" data-dce-id="3">Pokemon Count:
19
+ <xsl:value-of select="count(/datadom/slice/s//results)"/>
20
+ </p>
21
+ <xsl:if xmlns:xsl="http://www.w3.org/1999/XSL/Transform" test="count(/datadom/slice/s//results) &lt; 0">
22
+ <h3 xmlns="" data-dce-id="4">loading...</h3>
23
+ </xsl:if>
24
+ <xsl:for-each xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="/datadom/slice/s//results">
25
+ <xsl:variable name="pokeid"
26
+ select="substring-before( substring-after( @url, 'https://pokeapi.co/api/v2/pokemon/'),'/')"/>
27
+ <button xmlns="" data-dce-id="5">
28
+ <xsl:value-of select="@name">
29
+ </xsl:value-of>
30
+ </button>
31
+ </xsl:for-each>
32
+ <xsl:for-each xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="//slice/s/value/*">
33
+ <ul xmlns="" data-dce-id="6">
34
+ <var data-testid="request-section" data-dce-id="7">
35
+ <dce-text data-dce-id="8">
36
+ <xsl:value-of select="name(.)"/>
37
+ </dce-text>
38
+ </var>
39
+ <xsl:for-each select="@*">
40
+ <div data-dce-id="9">
41
+ <var data-dce-id="10">@<xsl:value-of select="local-name(.)"/>
42
+ </var>
43
+ <dce-text data-dce-id="11">
44
+ =
45
+ </dce-text>
46
+ <code data-testid="attr-{local-name(.)}" data-dce-id="12">
47
+ <xsl:value-of select="."/>
48
+ </code>
49
+ </div>
50
+ </xsl:for-each>
51
+ </ul>
52
+ </xsl:for-each>
53
+ </dce-root>
54
+ </xsl:template>
16
55
  <xsl:template match="/">
17
56
  <xsl:apply-templates mode="payload" select="/datadom/attributes"/>
18
57
  </xsl:template>
@@ -21,7 +60,7 @@
21
60
  <xsl:param name="defaultvalue"/>
22
61
  <xsl:choose>
23
62
  <xsl:when test="//payload/*[@slot=$slotname]">
24
- <xsl:copy-of select="//payload/*[@slot=$slotname]"/>
63
+ <xsl:copy-of select="//payload/*[@slot=$slotname]"/>
25
64
  </xsl:when>
26
65
  <xsl:otherwise>
27
66
  <xsl:copy-of select="$defaultvalue"/>
@@ -6,7 +6,6 @@
6
6
  <link rel="icon" href="./wc-square.svg"/>
7
7
 
8
8
  <script type="module" src="../http-request.js"></script>
9
- <script type="module" src="../input-text.js"></script>
10
9
  <script type="module" src="../custom-element.js"></script>
11
10
  <style>
12
11
  @import "./demo.css";
package/demo/ss.html CHANGED
@@ -1,32 +1,57 @@
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
- <head>
4
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
5
- <title>CSS scoping - Declarative Custom Element implementation demo</title>
6
- <link rel="icon" href="./wc-square.svg"/>
7
-
8
- <script type="module" src="../http-request.js"></script>
9
- <script type="module" src="../input-text.js"></script>
10
- <script type="module" src="../custom-element.js"></script>
11
- <style>
12
- @import "./demo.css";
13
- </style>
14
- </head>
15
- <body>
16
-
17
-
18
- <custom-element src="hex-grid-dce.html#hex-grid-template">
19
- <template>
20
- <style>nav{--hex-grid-size: 4rem;}</style>
21
- <img src="wc-square.svg" alt="DCE" href="https://github.com/EPA-WG/custom-element"/>
22
- <img src="https://upload.wikimedia.org/wikipedia/commons/a/a7/React-icon.svg" alt="React" href="https://react.dev/"/>
23
- <img src="https://angularjs.org/favicon.ico" alt="Angular" href="https://angularjs.org/"/>
24
- <img src="https://open-wc.org/35ded306.svg" alt="Open WC" href="https://open-wc.org/"/>
25
- <img src="https://storage.googleapis.com/cms-storage-bucket/4fd0db61df0567c0f352.png" alt="Flutter" href="https://flutter.dev/"/>
26
- <img src="https://lit.dev/images/logo.svg#flame" alt="Lit"/>
27
- <img src="https://redux.js.org/img/redux.svg" alt="Redux"/>
28
- </template>
29
- </custom-element>
30
-
31
- </body>
32
- </html>
1
+ <dce-root xmlns="http://www.w3.org/1999/xhtml" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:dce="urn:schemas-epa-wg:dce" data-dce-id="1"><http-request xmlns="" url="/reflect" slice="page" data-dce-id="2"></http-request><p xmlns="" data-dce-id="3">Pokemon Count: 6</p>
2
+ <ul xmlns="" data-dce-id="6">
3
+ <var data-testid="request-section" data-dce-id="7"><dce-text data-dce-id="8">request</dce-text></var><div data-dce-id="9">
4
+ <var data-dce-id="10">@url</var><dce-text data-dce-id="11">
5
+ =
6
+ </dce-text><code data-testid="attr-url" data-dce-id="12">/reflect</code>
7
+ </div>
8
+ <div data-dce-id="9-1">
9
+ <var data-dce-id="10">@data-dce-id</var><dce-text data-dce-id="11">
10
+ =
11
+ </dce-text><code data-testid="attr-data-dce-id" data-dce-id="12">2</code>
12
+ </div>
13
+ </ul>
14
+ <ul xmlns="" data-dce-id="6-1">
15
+ <var data-testid="request-section" data-dce-id="7"><dce-text data-dce-id="8">response</dce-text></var><div data-dce-id="9">
16
+ <var data-dce-id="10">@ok</var><dce-text data-dce-id="11">
17
+ =
18
+ </dce-text><code data-testid="attr-ok" data-dce-id="12">true</code>
19
+ </div>
20
+ <div data-dce-id="9-1">
21
+ <var data-dce-id="10">@status</var><dce-text data-dce-id="11">
22
+ =
23
+ </dce-text><code data-testid="attr-status" data-dce-id="12">200</code>
24
+ </div>
25
+ <div data-dce-id="9-2">
26
+ <var data-dce-id="10">@statusText</var><dce-text data-dce-id="11">
27
+ =
28
+ </dce-text><code data-testid="attr-statusText" data-dce-id="12">OK</code>
29
+ </div>
30
+ <div data-dce-id="9-3">
31
+ <var data-dce-id="10">@type</var><dce-text data-dce-id="11">
32
+ =
33
+ </dce-text><code data-testid="attr-type" data-dce-id="12">basic</code>
34
+ </div>
35
+ <div data-dce-id="9-4">
36
+ <var data-dce-id="10">@url</var><dce-text data-dce-id="11">
37
+ =
38
+ </dce-text><code data-testid="attr-url" data-dce-id="12">http://localhost:5173/reflect</code>
39
+ </div>
40
+ <div data-dce-id="9-5">
41
+ <var data-dce-id="10">@redirected</var><dce-text data-dce-id="11">
42
+ =
43
+ </dce-text><code data-testid="attr-redirected" data-dce-id="12">false</code>
44
+ </div>
45
+ </ul><button xmlns="" data-dce-id="5">bulbasaur</button><button xmlns="" data-dce-id="5-1">ivysaur</button><button xmlns="" data-dce-id="5-2">venusaur</button><button xmlns="" data-dce-id="5-3">charmander</button><button xmlns="" data-dce-id="5-4">charmeleon</button><button xmlns="" data-dce-id="5-5">charizard</button>
46
+ <ul xmlns="" data-dce-id="6-2">
47
+ <var data-testid="request-section" data-dce-id="7"><dce-text data-dce-id="8">data</dce-text></var><div data-dce-id="9">
48
+ <var data-dce-id="10">@count</var><dce-text data-dce-id="11">
49
+ =
50
+ </dce-text><code data-testid="attr-count" data-dce-id="12">1279</code>
51
+ </div>
52
+ <div data-dce-id="9-1">
53
+ <var data-dce-id="10">@next</var><dce-text data-dce-id="11">
54
+ =
55
+ </dce-text><code data-testid="attr-next" data-dce-id="12">https://pokeapi.co/api/v2/pokemon?offset=6&amp;limit=6</code>
56
+ </div>
57
+ </ul></dce-root>
package/http-request.js CHANGED
@@ -2,14 +2,13 @@ const attr = (el, attr)=> el.getAttribute(attr);
2
2
 
3
3
  export class HttpRequestElement extends HTMLElement
4
4
  {
5
- static get observedAttributes() {
6
- return [ 'value' // populated from localStorage, if defined initially, sets the value in storage
7
- , 'slice'
8
- , 'url'
9
- , 'method'
10
- , 'header-accept'
11
- ]
12
- }
5
+ static observedAttributes =
6
+ [ 'value' // populated from localStorage, if defined initially, sets the value in storage
7
+ , 'slice'
8
+ , 'url'
9
+ , 'method'
10
+ , 'header-accept'
11
+ ];
13
12
 
14
13
  get requestHeaders()
15
14
  { const ret = {};
@@ -23,30 +22,67 @@ export class HttpRequestElement extends HTMLElement
23
22
  return ret
24
23
  }
25
24
 
26
- disconnectedCallback(){ this._destroy?.(); }
25
+ disconnectedCallback(){ this.#destroy?.(); }
27
26
 
28
27
  connectedCallback()
29
- { const controller = new AbortController();
30
- this._destroy = ()=> controller.abort(this.localName+' disconnected');
28
+ {
29
+ setTimeout(()=>this.fetch(),0)
30
+ }
31
+ #inProgressUrl = ''
32
+ #destroy = ()=>{}
33
+
34
+ async fetch()
35
+ {
36
+ if( !this.closest('body') )
37
+ return;
38
+ const url = attr(this, 'url') || '';
39
+ if( !url )
40
+ { this.#destroy?.();
41
+ return this.value = {};
42
+ }
43
+
44
+ if( this.#inProgressUrl === url )
45
+ return ;
46
+
47
+ this.#inProgressUrl = url;
48
+ const controller = new AbortController();
49
+ this.#destroy = ()=> { controller.abort(this.localName+' disconnected'); this.#inProgressUrl = ''; }
31
50
 
32
- const url = attr(this, 'url') || ''
33
- , request = { ...this.requestProps, headers: this.requestHeaders }
51
+ const request = { ...this.requestProps, headers: this.requestHeaders }
34
52
  , slice = { request }
35
53
  , update = () => this.dispatchEvent( new Event('change') );
36
54
  this.value = slice;
37
- setTimeout( async ()=>
38
- { update();
39
- const response = await fetch(url,{ ...this.requestProps, signal: controller.signal, headers: this.requestHeaders })
40
- , r = {headers: {}};
41
- [...response.headers].map( ([k,v]) => r.headers[k] = v );
42
- 'ok,status,statusText,type,url,redirected'.split(',').map( k=> r[k] = response[k] )
43
-
44
- slice.response = r;
45
- update();
46
- slice.data = await response.json();
47
- update();
48
- },0 );
55
+
56
+ update();
57
+ const response = await fetch(url,{ ...this.requestProps, signal: controller.signal, headers: this.requestHeaders })
58
+ , r = {headers: {}};
59
+ [...response.headers].map( ([k,v]) => r.headers[k] = v );
60
+ 'ok,status,statusText,type,url,redirected'.split(',').map( k=> r[k] = response[k] )
61
+
62
+ slice.response = r;
63
+ update();
64
+ if( r.headers['content-type']?.includes('json'))
65
+ try
66
+ { slice.data = await response.json();
67
+ update();
68
+ }catch(_e){}
49
69
  }
70
+
71
+ attributeChangedCallback(name, oldValue, newValue)
72
+ { if( name === 'url' )
73
+ { if( oldValue !== newValue)
74
+ {
75
+ oldValue && this.#destroy?.();
76
+ if( newValue )
77
+ setTimeout(()=>this.fetch(),10)
78
+ else
79
+ { this.value = {}
80
+ setTimeout(()=>this.dispatchEvent( new Event('change') ),10)
81
+ }
82
+ }
83
+ }
84
+ }
85
+
50
86
  }
51
87
 
52
88
  window.customElements.define( 'http-request', HttpRequestElement );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json.schemastore.org/web-types",
3
3
  "name": "@epa-wg/custom-element",
4
- "version": "0.0.19",
4
+ "version": "0.0.21",
5
5
  "js-types-syntax": "typescript",
6
6
  "description-markup": "markdown",
7
7
  "contributions": {
@@ -108,4 +108,4 @@
108
108
  ]
109
109
  }
110
110
  }
111
- }
111
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json.schemastore.org/web-types",
3
3
  "name": "@epa-wg/custom-element",
4
- "version": "0.0.19",
4
+ "version": "0.0.21",
5
5
  "js-types-syntax": "typescript",
6
6
  "description-markup": "markdown",
7
7
  "contributions": {
@@ -864,4 +864,4 @@
864
864
  ]
865
865
  }
866
866
  }
867
- }
867
+ }
package/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import CustomElement from "./custom-element.js";
2
+ export default CustomElement;
3
+
4
+ export * from "./custom-element.js";
5
+ export * from "./http-request.js";
6
+ export * from "./local-storage.js";
7
+ export * from "./location-element.js";
package/local-storage.js CHANGED
@@ -2,44 +2,81 @@ const string2value = (type, v) =>
2
2
  { if( type === 'text')
3
3
  return v;
4
4
  if( type === 'json')
5
- return JSON.parse( v );
5
+ try{ return JSON.parse( v );}
6
+ catch(err){ return null }
6
7
  const el = document.createElement('input');
7
8
  el.setAttribute('type',type);
8
- el.setAttribute('value', v );
9
- return type==='number'? el.valueAsNumber : 'date|time|dateTimeLocal'.includes(type)? el.valueAsDate: el.value;
9
+ if( 'number' === type )
10
+ { el.value = v;
11
+ return el.valueAsNumber;
12
+ }
13
+ if( 'date' === type )
14
+ { if(!v) return null;
15
+ el.valueAsDate = new Date( v );
16
+ return el.value;
17
+ }
18
+ el.value = v;
19
+ return el.value;
10
20
  };
11
21
 
22
+ let originalSetItem,originalRemoveItem,originalClear;
23
+
24
+ function ensureTrackLocalStorage()
25
+ { if( originalSetItem )
26
+ return;
27
+ originalSetItem = localStorage.setItem;
28
+ localStorage.setItem = function( key, value, ...rest )
29
+ { originalSetItem.apply(this, [ key, value, ...rest ]);
30
+ window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
31
+ };
32
+ originalRemoveItem = localStorage.removeItem;
33
+ localStorage.removeItem = function( key, ...rest )
34
+ { originalRemoveItem.apply(this, [ key, ...rest ]);
35
+ window.dispatchEvent( new CustomEvent('local-storage',{detail:{key}}) );
36
+ };
37
+ originalClear = localStorage.clear;
38
+ localStorage.clear = function( ...rest )
39
+ { originalClear.apply(this, [ ...rest ]);
40
+ window.dispatchEvent( new CustomEvent('local-storage',{detail:{}}) );
41
+ };
42
+ }
43
+
12
44
  export function localStorageSetItem(key, value)
13
45
  { localStorage.setItem(key, value);
14
46
  window.dispatchEvent( new CustomEvent('local-storage',{detail:{key,value}}) );
15
47
  }
16
48
  export class LocalStorageElement extends HTMLElement
17
49
  {
18
- static get observedAttributes() {
19
- return [ 'value' // populated from localStorage, if defined initially, sets the value in storage
50
+ static observedAttributes=
51
+ [ 'value' // populated from localStorage, if defined initially, sets the value in storage
20
52
  , 'slice'
21
53
  , 'key'
22
54
  , 'type' // `text|json`, defaults to text, other types are compatible with INPUT field
23
55
  , 'live' // monitors localStorage change
24
56
  ];
25
- }
57
+
58
+ #value;
59
+ get value(){ return this.#value ===null ? undefined: this.#value }
60
+ set value(o){ return this.#value = o; }
26
61
 
27
62
  async connectedCallback()
28
63
  {
29
64
  const attr = attr => this.getAttribute(attr)
30
- , fromStorage = ()=>
31
- { this.value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
65
+ , fromStorage = ()=>
66
+ { this.#value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
32
67
  this.dispatchEvent( new Event('change') )
33
68
  }
34
- // todo apply type
69
+ this.#value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
70
+
35
71
  if( this.hasAttribute('value'))
36
- localStorageSetItem( attr( this, 'key' ) )
72
+ localStorageSetItem( attr( 'key' ), this.#value = attr( 'value' ) )
37
73
  else
38
74
  fromStorage()
39
75
 
40
76
  if( this.hasAttribute('live') )
41
- { const listener = (e => e.detail.key === attr( 'key' ) && fromStorage());
77
+ { const listener = (e => (e.detail.key === attr( 'key' ) || !e.detail.key ) && fromStorage());
42
78
  window.addEventListener( 'local-storage', listener );
79
+ ensureTrackLocalStorage();
43
80
  this._destroy = ()=> window.removeEventListener('local-storage', listener );
44
81
  }
45
82
  }
@@ -47,4 +84,4 @@ export class LocalStorageElement extends HTMLElement
47
84
  }
48
85
 
49
86
  window.customElements.define( 'local-storage', LocalStorageElement );
50
- export default LocalStorageElement;
87
+ export default LocalStorageElement;
@@ -1,23 +1,42 @@
1
- const attr = (el, attr)=> el.getAttribute(attr);
1
+ const attr = ( el, attr )=> el.getAttribute( attr );
2
+
3
+ let originalHistory;
4
+
5
+ function ensureTrackLocationChange()
6
+ { if( originalHistory )
7
+ return;
8
+ originalHistory = {};
9
+ 'back,forward,go,pushState,replaceState'.split(',').forEach( k =>
10
+ {
11
+ originalHistory[ k ] = history[ k ];
12
+ history[ k ] = function(...rest )
13
+ {
14
+ originalHistory[k].apply( history, rest );
15
+ window.dispatchEvent( new CustomEvent('dce-location',{detail:{ k }}) );
16
+ }
17
+ });
18
+ }
2
19
 
3
20
  export class LocationElement extends HTMLElement
4
21
  {
5
- static get observedAttributes()
6
- { return [ 'value' // populated from localStorage, if defined initially, sets the value in storage
7
- , 'slice'
8
- , 'live' // monitors location change
9
- , 'src' // URL to be parsed, defaults to `window.location`
10
- ];
11
- }
22
+ static observedAttributes=
23
+ [ 'value' // populated from url
24
+ , 'slice'
25
+ , 'href' // url to be parsed. When omitted window.location is used.
26
+ , 'type' // `text|json`, defaults to text, other types are compatible with INPUT field
27
+ , 'live' // monitors history change, applicable only when href is omitted.
28
+ ];
12
29
 
13
30
  constructor()
14
31
  {
15
32
  super();
16
33
  const state = {}
17
- , listener = e=> propagateSlice(e)
34
+ , listener = () => setTimeout( propagateSlice,1 )
18
35
  , propagateSlice = ()=>
19
- { const urlStr = attr(this,'src')
20
- const url = urlStr? new URL(urlStr) : window.location
36
+ { const urlStr = attr(this,'href')
37
+ if(!urlStr)
38
+ ensureTrackLocationChange();
39
+ const url = urlStr? new URL(urlStr, window.location) : window.location;
21
40
 
22
41
  const params= {}
23
42
  const search = new URLSearchParams(url.search);
@@ -36,24 +55,30 @@ export class LocationElement extends HTMLElement
36
55
  {
37
56
  if( !state.listener && this.hasAttribute('live') )
38
57
  { state.listener = 1;
39
- window.addEventListener( 'popstate' , listener );
40
- window.addEventListener( 'hashchange', listener );
58
+ window.navigation?.addEventListener("navigate", listener );
59
+ window.addEventListener( 'popstate' , listener );
60
+ window.addEventListener( 'hashchange' , listener );
61
+ window.addEventListener( 'dce-location' , listener );
41
62
  }
42
63
  propagateSlice();
43
64
  return s || {}
44
65
  }
45
66
  this._destroy = ()=>
46
67
  {
47
- if( !state.listener )
48
- return;
49
- if(state.listener)
50
- { window.removeEventListener('popstate' , listener);
51
- window.removeEventListener('hashchange', listener);
52
- }
68
+ window.removeEventListener('popstate' , listener);
69
+ window.removeEventListener('hashchange' , listener);
70
+ window.removeEventListener('dce-location', listener);
53
71
  delete state.listener;
54
72
  };
55
73
 
56
74
  }
75
+ attributeChangedCallback(name, oldValue, newValue)
76
+ {
77
+ if('href'!== name)
78
+ return;
79
+ this.sliceInit && this.sliceInit();
80
+ }
81
+
57
82
  connectedCallback(){ this.sliceInit() }
58
83
  disconnectedCallback(){ this._destroy() }
59
84
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@epa-wg/custom-element",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Declarative Custom Element as W3C proposal PoC with native(XSLT) based templating",
5
5
  "browser": "custom-element.js",
6
6
  "module": "custom-element.js",
7
7
  "exports": {
8
- ".": "./custom-element.js",
8
+ ".": "./index.js",
9
9
  "./package.json": "./package.json",
10
10
  "./CustomElement": "./custom-element.js"
11
11
  },
@@ -16,7 +16,7 @@
16
16
  "dev:help": "echo \"needed for sandbox demo\"",
17
17
  "dev": "bash bin/stackblitz.sh",
18
18
  "start": "npm i --no-save @web/dev-server && web-dev-server --node-resolve",
19
- "test": "echo \"test would reside in https://github.com/EPA-WG/custom-element-test\" && exit 0",
19
+ "test": "echo \"test would reside in https://github.com/EPA-WG/custom-element-dist\" && exit 0",
20
20
  "typings": "npx -p typescript tsc custom-element.js --declaration --allowJs --emitDeclarationOnly "
21
21
  },
22
22
  "type": "module",
package/input-text.js DELETED
@@ -1,17 +0,0 @@
1
- export class InputTextElement extends HTMLElement
2
- {
3
- constructor()
4
- {
5
- super();
6
- const i = this.ownerDocument.createElement('input');
7
- for(let a of this.attributes)
8
- a.namespaceURI ? i.setAttributeNS(a.namespaceURI,a.name,a.value) : i.setAttribute(a.name,a.value)
9
- this.append(i)
10
- }
11
- get value(){ return this.firstChild.value }
12
- set value(v){ return this.firstChild.value = v }
13
- disconnectedCallback(){ }
14
- }
15
-
16
- window.customElements.define( 'input-text', InputTextElement );
17
- export default InputTextElement;