@epa-wg/custom-element 0.0.19 → 0.0.20

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/README.md CHANGED
@@ -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-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
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.20",
120
120
  "js-types-syntax": "typescript",
121
121
  "description-markup": "markdown",
122
122
  "contributions": {
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) )
@@ -95,10 +96,12 @@ Json2Xml( o, tag )
95
96
 
96
97
  export function
97
98
  obj2node( o, tag, doc )
98
- {
99
- if( typeof o === 'function'){debugger}
100
- if( typeof o === 'string' )
99
+ { const t = typeof o;
100
+ if( t === 'function'){debugger}
101
+ if( t === 'string' )
101
102
  return create(tag,o,doc);
103
+ if( t === 'number' )
104
+ return create(tag,''+o,doc);
102
105
 
103
106
  if( o instanceof Array )
104
107
  { const ret = create('array');
@@ -364,17 +367,23 @@ event2slice( x, sliceNames, ev, dce )
364
367
  ev.type==='init' && cleanSliceValue();
365
368
  s.append( obj2node( ev, 'event', d ) );
366
369
  if( sel.hasAttribute('slice-value') )
367
- { s.setAttribute('value', el.value );
370
+ { if( el.value === undefined)
371
+ s.removeAttribute('value')
372
+ else
373
+ s.setAttribute('value', el.value );
368
374
  const v = xPath( attr( sel, 'slice-value'),s );
369
375
  cleanSliceValue();
370
376
  s.append( createText( d, v ) );
371
377
  }else
372
- { const v = el.value || attr( sel, 'value' ) ;
378
+ { const v = el.value ?? attr( sel, 'value' ) ;
373
379
  cleanSliceValue();
374
- if( isString(v) )
375
- s.append( createText( d, v) );
380
+ if( v === null || v === undefined )
381
+ [...s.childNodes].filter(n=>n.localName!=='event').map(n=>n.remove());
376
382
  else
377
- s.append( obj2node(v,'value',s.ownerDocument) )
383
+ if( isString(v) )
384
+ s.append( createText( d, v) );
385
+ else
386
+ s.append( obj2node(v,'value',s.ownerDocument) )
378
387
  }
379
388
  })
380
389
  }
@@ -445,6 +454,8 @@ export function assureUnique(n, id=0)
445
454
  }
446
455
  export function merge( parent, fromArr )
447
456
  {
457
+ if(!fromArr.length)
458
+ return removeChildren(parent);
448
459
  const id2old = {};
449
460
  for( let c of parent.childNodes)
450
461
  { ASSERT( !id2old[c.dceId] );
@@ -455,8 +466,8 @@ export function merge( parent, fromArr )
455
466
  id2old[attr(c, 'data-dce-id') || 0] = c;
456
467
  }
457
468
  for( let e of [...fromArr] )
458
- {
459
- const o = id2old[ attr(e, 'data-dce-id') || e.dceId ];
469
+ { const k = attr(e, 'data-dce-id') || e.dceId;
470
+ const o = id2old[ k ];
460
471
  if( o )
461
472
  { if( isText(e) )
462
473
  { if( o.nodeValue !== e.nodeValue )
@@ -466,9 +477,12 @@ export function merge( parent, fromArr )
466
477
  if( o.childNodes.length || e.childNodes.length )
467
478
  merge(o, e.childNodes)
468
479
  }
480
+ delete id2old[ k ]
469
481
  }else
470
482
  parent.append( e )
471
483
  }
484
+ for( let v of Object.values(id2old) )
485
+ v.remove();
472
486
  }
473
487
  export function assureUID(n,attr)
474
488
  { if( !n.hasAttribute(attr) )
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,33 @@
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>
44
+ <custom-element>
45
+ <template>
46
+ <button slice="url-string" slice-value="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click"
47
+ >⬇️https://pokeapi.co/api/v2/pokemon?limit=6</button>
48
+ <input slice="url-string" value="{ //url-string ?? '' }" style="width:100%"/>
49
+ <button slice="fetch-url" slice-event="click" slice-value="//url-string"> GET </button>
50
+ <http-request
51
+ url="{//fetch-url}"
52
+ slice="request_slice"
53
+ type="text"
54
+ mode="cors"
55
+ ></http-request>
56
+ //fetch-url : <code>{//fetch-url}</code>
57
+ <!-- <for-each select="//slice/request_slice/value/*">-->
58
+ <!-- <ul>-->
59
+ <!-- <var data-testid="request-section"><value-of select='name(.)'/></var>-->
60
+ <!-- <for-each select="@*">-->
61
+ <!-- <div>-->
62
+ <!-- <var data-testid="section-attribute">@<value-of select='local-name(.)'/></var>-->
63
+ <!-- =-->
64
+ <!-- <code><value-of select='.'/></code>-->
65
+ <!-- </div>-->
66
+ <!-- </for-each>-->
67
+ <!-- </ul>-->
68
+ <!-- </for-each>-->
69
+ </template>
70
+ </custom-element>
59
71
 
60
72
  </body>
61
73
  </html>
@@ -94,9 +94,9 @@
94
94
  <form>
95
95
  <label>
96
96
  <input type="text"
97
- value="Type time update"
97
+ value="{//txt ?? 'Type time update'}"
98
98
  slice="txt"
99
- slice-event="keyup"/>
99
+ slice-event="init input"/>
100
100
 
101
101
  <span> Character count:
102
102
  <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="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click">⬇️https://pokeapi.co/api/v2/pokemon?limit=6</button>
42
+ <input slice="url-string" value="{ //url-string ?? '' }" style="width:100%"/>
43
+ <button slice="fetch-url" slice-event="click" slice-value="//url-string"> GET </button>
44
+ <http-request
45
+ url="{//fetch-url}"
46
+ slice="request_slice"
47
+ type="text"
48
+ mode="cors"
49
+ ></http-request>
50
+ <code>//fetch-url</code> from <code>{//fetch-url}</code><br/>
51
+ <for-each select="//results">
52
+ <var>
53
+ *{@name}
54
+ </var>
55
+ </for-each>
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 key="overrideKey" slice="override-key" type="text" value="ABC"></local-storage>
55
+ <button onclick="localStorage.setItem('overrideKey','text value')">text value</button>
56
+ <button onclick="localStorage.removeItem('attrKey')">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>
package/demo/s.xml CHANGED
@@ -1,6 +1,93 @@
1
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>
2
+ <datadom>
3
+ <payload>
4
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
5
+ <button xmlns="http://www.w3.org/1999/xhtml" slice="url-string"
6
+ slice-value="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click" slot="">
7
+ ⬇️https://pokeapi.co/api/v2/pokemon?limit=6
8
+ </button>
9
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
10
+ <input xmlns="http://www.w3.org/1999/xhtml" slice="url-string" value="{ //url-string ?? '' }" style="width:100%"
11
+ slot=""/>
12
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
13
+ <button xmlns="http://www.w3.org/1999/xhtml" slice="fetch-url" slice-event="click" slice-value="//url-string"
14
+ slot="">GET
15
+ </button>
16
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
17
+ <http-request xmlns="http://www.w3.org/1999/xhtml" url="{//fetch-url}" slice="request_slice" type="text"
18
+ mode="cors" slot=""></http-request>
19
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
20
+ <code xmlns="http://www.w3.org/1999/xhtml" slot="">//fetch-url</code>
21
+ <span xmlns="http://www.w3.org/1999/xhtml" slot="">from</span>
22
+ <code xmlns="http://www.w3.org/1999/xhtml" slot="">{//fetch-url}</code>
23
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
24
+ <xsl:for-each xmlns="http://www.w3.org/1999/xhtml" select="//slice/request_slice/value/*" slot="">
25
+ <ul>
26
+ <var data-testid="request-section">
27
+ <xsl:value-of select="name(.)"></xsl:value-of>
28
+ </var>
29
+ <xsl:for-each select="@*">
30
+ <div>
31
+ <var data-testid="section-attribute">@
32
+ <xsl:value-of select="local-name(.)"></xsl:value-of>
33
+ </var>
34
+ =
35
+ <code>
36
+ <xsl:value-of select="."></xsl:value-of>
37
+ </code>
38
+ </div>
39
+ </xsl:for-each>
40
+ </ul>
41
+ </xsl:for-each>
42
+ <span xmlns="http://www.w3.org/1999/xhtml" slot=""></span>
43
+ </payload>
44
+ <attributes>
45
+ <tag>dce-5dc8d4a0-d545-4498-9de5-eec25c2b232f</tag>
46
+ </attributes>
47
+ <dataset/>
48
+ <slice>
49
+ <url-string xmlns="" slice="url-string" value="" style="width:100%" data-dce-id="2"
50
+ slice-value="'https://pokeapi.co/api/v2/pokemon?limit=6'" slice-event="click">
51
+ <event isTrusted="true" pointerId="1" width="1" height="1" pressure="0" tiltX="0" tiltY="0" azimuthAngle="0"
52
+ altitudeAngle="1.5707963267948966" tangentialPressure="0" twist="0" pointerType="mouse"
53
+ isPrimary="false" screenX="94" screenY="186" clientX="94" clientY="99" ctrlKey="false"
54
+ shiftKey="false" altKey="false" metaKey="false" button="0" buttons="0" pageX="94" pageY="99" x="94"
55
+ y="99" offsetX="60" offsetY="6" movementX="0" movementY="0" layerX="94" layerY="99" detail="1"
56
+ which="1" type="click" eventPhase="0" bubbles="true" cancelable="true" defaultPrevented="false"
57
+ composed="true" timeStamp="5596.5" returnValue="true" cancelBubble="false" NONE="0"
58
+ CAPTURING_PHASE="1" AT_TARGET="2" BUBBLING_PHASE="3">
59
+ <relatedTarget/>
60
+ <fromElement/>
61
+ <toElement/>
62
+ <sourceCapabilities firesTouchEvents="false"/>
63
+ <currentTarget/>
64
+ </event>
65
+ https://pokeapi.co/api/v2/pokemon?limit=6
66
+ </url-string>
67
+ <fetch-url xmlns="" slice="fetch-url" slice-event="click" slice-value="//url-string" data-dce-id="4" value="">
68
+ <event isTrusted="true" pointerId="1" width="1" height="1" pressure="0" tiltX="0" tiltY="0" azimuthAngle="0"
69
+ altitudeAngle="1.5707963267948966" tangentialPressure="0" twist="0" pointerType="mouse"
70
+ isPrimary="false" screenX="56" screenY="232" clientX="56" clientY="145" ctrlKey="false"
71
+ shiftKey="false" altKey="false" metaKey="false" button="0" buttons="0" pageX="56" pageY="145" x="56"
72
+ y="145" offsetX="23" offsetY="8" movementX="0" movementY="0" layerX="56" layerY="145" detail="1"
73
+ which="1" type="click" eventPhase="0" bubbles="true" cancelable="true" defaultPrevented="false"
74
+ composed="true" timeStamp="6699.100000023842" returnValue="true" cancelBubble="false" NONE="0"
75
+ CAPTURING_PHASE="1" AT_TARGET="2" BUBBLING_PHASE="3">
76
+ <relatedTarget/>
77
+ <fromElement/>
78
+ <toElement/>
79
+ <sourceCapabilities firesTouchEvents="false"/>
80
+ <currentTarget/>
81
+ </event>
82
+ https://pokeapi.co/api/v2/pokemon?limit=6
83
+ </fetch-url>
84
+ <request_slice xmlns="" url="" slice="request_slice" type="text" mode="cors" data-dce-id="5">
85
+ <event type="init"/>
86
+ <value>
87
+ <request xmlns="" url="" type="text" mode="cors" data-dce-id="5">
88
+ <headers/>
89
+ </request>
90
+ </value>
91
+ </request_slice>
92
+ </slice>
93
+ </datadom>
package/demo/s.xslt CHANGED
@@ -6,13 +6,15 @@
6
6
  <xsl:otherwise><xsl:value-of select="def"/></xsl:otherwise>
7
7
  </xsl:choose>
8
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>
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"><local-storage xmlns="" key="dateKey" slice="date-key" type="date" live="live" data-dce-id="2"/><local-storage xmlns="" key="timeKey" slice="time-key" type="time" live="live" data-dce-id="3"/><local-storage xmlns="" key="localDateTimeKey" slice="local-date-time" type="datetime-local" live="live" data-dce-id="4"/><local-storage xmlns="" key="numberKey" slice="number-key" type="number" live="live" data-dce-id="5"/><local-storage xmlns="" key="jsonKey" slice="json-key" type="json" live="live" data-dce-id="6"/><input xmlns="" id="typesinput" placeholder="set value" data-dce-id="7"/><button xmlns="" onclick="&#10; 'dateKey,timeKey,localDateTimeKey,numberKey,jsonKey'.split(',')&#10; .map( k=&gt; localStorage.setItem(k, typesinput.value) )&#10; " data-dce-id="8"> set to all</button><br xmlns="" data-dce-id="9"/><hr xmlns="" data-dce-id="10"/><dce-text xmlns="" data-dce-id="11">
10
+ date-key:
11
+ </dce-text><button xmlns="" onclick="localStorage.setItem('dateKey', '2024-04-20T03:58:42.131Z')" data-dce-id="12">2024-04-21T03:58:42.131Z </button><button xmlns="" onclick="localStorage.setItem('dateKey', new Date(Date.now()).toISOString())" data-dce-id="13">now </button><button xmlns="" onclick="localStorage.setItem('dateKey', 'ABC' )" data-dce-id="14">date ABC - invalid </button><code xmlns="" data-dce-id="15"><xsl:value-of select="//date-key "/></code><br xmlns="" data-dce-id="16"/><dce-text xmlns="" data-dce-id="17">
12
+ time-key:
13
+ </dce-text><button xmlns="" onclick="localStorage.setItem('timeKey', '13:30')" data-dce-id="18">13:30 </button><code xmlns="" data-dce-id="19"><xsl:value-of select="//time-key "/></code><br xmlns="" data-dce-id="20"/><dce-text xmlns="" data-dce-id="21">
14
+ local-date-time:
15
+ </dce-text><button xmlns="" onclick="localStorage.setItem('localDateTimeKey', '1977-04-01T14:00:30')" data-dce-id="22">21977-04-01T14:00:30 - local </button><code xmlns="" data-dce-id="23"><xsl:value-of select="//local-date-time"/></code><br xmlns="" data-dce-id="24"/><dce-text xmlns="" data-dce-id="25">
16
+ number-key:
17
+ </dce-text><button xmlns="" onclick="localStorage.setItem('numberKey', '2024')" data-dce-id="26">2024 - number </button><button xmlns="" onclick="localStorage.setItem('numberKey', '24')" data-dce-id="27">24 - number </button><code xmlns="" data-dce-id="28"><xsl:value-of select="//number-key "/></code><br xmlns="" data-dce-id="29"/><fieldset xmlns="" data-dce-id="30"><legend data-dce-id="31">json-key: </legend><button onclick="localStorage.setItem('jsonKey', jsonStringSample)" data-dce-id="32"> a:1,b:'B' - json </button><xsl:apply-templates xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="//json-key/value/@*" mode="json"/></fieldset></dce-root></xsl:template>
16
18
  <xsl:template match="/">
17
19
  <xsl:apply-templates mode="payload" select="/datadom/attributes"/>
18
20
  </xsl:template>
@@ -34,4 +36,4 @@
34
36
  <xsl:with-param name="defaultvalue"/>
35
37
  </xsl:call-template>
36
38
  </xsl:variable>
37
- </xsl:stylesheet>
39
+ <xsl:template xmlns:xsl="http://www.w3.org/1999/XSL/Transform" mode="json" match="*|@*"><div xmlns="" data-dce-id="33"><xsl:value-of select="name()"/> : <xsl:value-of select="."/></div></xsl:template></xsl:stylesheet>
package/demo/ss.html CHANGED
@@ -1,32 +1,52 @@
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"/>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <dce-root data-dce-id="1" xmlns="http://www.w3.org/1999/xhtml" xmlns:dce="urn:schemas-epa-wg:dce"
3
+ xmlns:xhtml="http://www.w3.org/1999/xhtml">
4
+ <local-storage key="dateKey" slice="date-key" type="date" live="live" data-dce-id="2" xmlns=""/>
5
+ <local-storage key="timeKey" slice="time-key" type="time" live="live" data-dce-id="3" xmlns=""/>
6
+ <local-storage key="localDateTimeKey" slice="local-date-time" type="datetime-local" live="live" data-dce-id="4"
7
+ xmlns=""/>
8
+ <local-storage key="numberKey" slice="number-key" type="number" live="live" data-dce-id="5" xmlns=""/>
9
+ <local-storage key="jsonKey" slice="json-key" type="json" live="live" data-dce-id="6" xmlns=""/>
10
+ <input id="typesinput" placeholder="set value" data-dce-id="7" xmlns=""/>
11
+ <button onclick="&#10; 'dateKey,timeKey,localDateTimeKey,numberKey,jsonKey'.split(',')&#10; .map( k=&gt; localStorage.setItem(k, typesinput.value) )&#10; "
12
+ data-dce-id="8" xmlns=""> set to all
13
+ </button>
14
+ <br data-dce-id="9" xmlns=""/>
15
+ <hr data-dce-id="10" xmlns=""/>
16
+ <dce-text data-dce-id="11" xmlns="">
17
+ date-key:
18
+ </dce-text>
19
+ <button onclick="localStorage.setItem('dateKey', '2024-04-20T03:58:42.131Z')" data-dce-id="12" xmlns="">
20
+ 2024-04-21T03:58:42.131Z
21
+ </button>
22
+ <button onclick="localStorage.setItem('dateKey', new Date(Date.now()).toISOString())" data-dce-id="13" xmlns="">
23
+ now
24
+ </button>
25
+ <button onclick="localStorage.setItem('dateKey', 'ABC' )" data-dce-id="14" xmlns="">date ABC - invalid</button>
26
+ <code data-dce-id="15" xmlns="">
7
27
 
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
28
 
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>
29
+ </code><br data-dce-id="16" xmlns=""/>
30
+ <dce-text data-dce-id="17" xmlns="">
31
+ time-key:
32
+ </dce-text>
33
+ <button onclick="localStorage.setItem('timeKey', '13:30')" data-dce-id="18" xmlns="">13:30</button>
34
+ <code data-dce-id="19" xmlns=""/><br data-dce-id="20" xmlns=""/>
35
+ <dce-text data-dce-id="21" xmlns="">
36
+ local-date-time:
37
+ </dce-text>
38
+ <button onclick="localStorage.setItem('localDateTimeKey', '1977-04-01T14:00:30')" data-dce-id="22" xmlns="">
39
+ 21977-04-01T14:00:30 - local
40
+ </button>
41
+ <code data-dce-id="23" xmlns=""/><br data-dce-id="24" xmlns=""/>
42
+ <dce-text data-dce-id="25" xmlns="">
43
+ number-key:
44
+ </dce-text>
45
+ <button onclick="localStorage.setItem('numberKey', '2024')" data-dce-id="26" xmlns="">2024 - number</button>
46
+ <button onclick="localStorage.setItem('numberKey', '24')" data-dce-id="27" xmlns="">24 - number</button>
47
+ <code data-dce-id="28" xmlns=""/><br data-dce-id="29" xmlns=""/>
48
+ <fieldset data-dce-id="30" xmlns="">
49
+ <legend data-dce-id="31">json-key:</legend>
50
+ <button onclick="localStorage.setItem('jsonKey', jsonStringSample)" data-dce-id="32"> a:1,b:'B' - json</button>
51
+ </fieldset>
52
+ </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,59 @@ 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('custom-element') )
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');
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
+ slice.data = await response.json();
65
+ update();
49
66
  }
67
+
68
+ attributeChangedCallback(name, oldValue, newValue)
69
+ { if( name === 'url' )
70
+ { if( newValue && oldValue !== newValue)
71
+ {
72
+ oldValue && this.#destroy?.();
73
+ setTimeout(()=>this.fetch(),0)
74
+ }
75
+ }
76
+ }
77
+
50
78
  }
51
79
 
52
80
  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.20",
5
5
  "js-types-syntax": "typescript",
6
6
  "description-markup": "markdown",
7
7
  "contributions": {
@@ -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.20",
5
5
  "js-types-syntax": "typescript",
6
6
  "description-markup": "markdown",
7
7
  "contributions": {
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
65
  , fromStorage = ()=>
31
- { this.value = string2value( attr('type'), localStorage.getItem( attr( 'key' ) ) );
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
  }
@@ -2,13 +2,13 @@ const attr = (el, attr)=> el.getAttribute(attr);
2
2
 
3
3
  export class LocationElement 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
- , 'live' // monitors location change
9
- , 'src' // URL to be parsed, defaults to `window.location`
10
- ];
11
- }
5
+ static observedAttributes=
6
+ [ 'value' // populated from localStorage, if defined initially, sets the value in storage
7
+ , 'slice'
8
+ , 'key'
9
+ , 'type' // `text|json`, defaults to text, other types are compatible with INPUT field
10
+ , 'live' // monitors localStorage change
11
+ ];
12
12
 
13
13
  constructor()
14
14
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epa-wg/custom-element",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
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",