@webqit/oohtml 2.1.52 → 2.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +14 -18
  2. package/dist/bindings-api.js +1 -1
  3. package/dist/bindings-api.js.map +3 -3
  4. package/dist/context-api.js +1 -1
  5. package/dist/context-api.js.map +3 -3
  6. package/dist/html-bracelets.js +2 -0
  7. package/dist/html-bracelets.js.map +7 -0
  8. package/dist/html-imports.js +1 -1
  9. package/dist/html-imports.js.map +3 -3
  10. package/dist/html-namespaces.js +2 -0
  11. package/dist/html-namespaces.js.map +7 -0
  12. package/dist/main.js +16 -24
  13. package/dist/main.js.map +3 -3
  14. package/dist/scoped-css.js +2 -2
  15. package/dist/scoped-css.js.map +3 -3
  16. package/dist/scoped-js.js +15 -23
  17. package/dist/scoped-js.js.map +3 -3
  18. package/package.json +7 -7
  19. package/src/bindings-api/_HTMLBindingsProvider.js +38 -0
  20. package/src/bindings-api/index.js +49 -26
  21. package/src/context-api/ContextReturnValue.js +22 -0
  22. package/src/context-api/HTMLContext.js +14 -6
  23. package/src/context-api/HTMLContextProvider.js +27 -10
  24. package/src/context-api/_ContextRequestEvent.js +2 -2
  25. package/src/context-api/index.js +26 -10
  26. package/src/html-bracelets/AttrBracelet.js +109 -0
  27. package/src/html-bracelets/Bracelet.js +78 -0
  28. package/src/html-bracelets/HTMLBracelets.js +67 -0
  29. package/src/html-bracelets/TextBracelet.js +69 -0
  30. package/src/html-bracelets/index.js +71 -0
  31. package/src/html-bracelets/targets.browser.js +10 -0
  32. package/src/html-imports/_HTMLExportsManager.js +2 -2
  33. package/src/html-imports/_HTMLImportsProvider.js +3 -9
  34. package/src/html-imports/index.js +8 -8
  35. package/src/{namespace-api → html-namespaces}/index.js +25 -25
  36. package/src/index.js +14 -12
  37. package/src/scoped-js/Hash.js +26 -0
  38. package/src/scoped-js/index.js +63 -63
  39. package/test/index.js +1 -1
  40. package/test/modules.test.js +1 -1
  41. package/test/scoped-js.test.js +1 -1
  42. package/dist/namespace-api.js +0 -2
  43. package/dist/namespace-api.js.map +0 -7
  44. package/src/scoped-js/Compiler.js +0 -299
  45. /package/src/{namespace-api → html-namespaces}/targets.browser.js +0 -0
@@ -0,0 +1,67 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { HTMLContext } from '../context-api/index.js';
6
+ import _HTMLBindingsProvider from '../bindings-api/_HTMLBindingsProvider.js';
7
+ import Bracelet from './Bracelet.js';
8
+ import { _ } from '../util.js';
9
+
10
+ export default class HTMLBracelets extends Set {
11
+
12
+ /**
13
+ * @instance
14
+ */
15
+ static instance( host ) {
16
+ return _( host ).get( 'bracelets::instance' ) || new this( host );
17
+ }
18
+
19
+ /**
20
+ * @constructor
21
+ */
22
+ constructor( host ) {
23
+ super();
24
+ _( host ).get( `bracelets::instance` )?.dispose();
25
+ _( host ).set( `bracelets::instance`, this );
26
+ const priv = { host, bindings: Object.create( null ) };
27
+ Object.defineProperty( this, '#', { get: () => priv } );
28
+ }
29
+
30
+ add( bracelet ) {
31
+ if ( !( bracelet instanceof Bracelet ) ) throw new Error( `Argument must be instance of Bracelet.` );
32
+ const returnValue = super.add( bracelet );
33
+ const bindings = this[ '#' ].bindings;
34
+ bracelet.refs.forEach( ref => {
35
+ const $ref = ref.join( '.' );
36
+ if ( !( $ref in bindings ) ) {
37
+ bindings[ $ref ] = { subs: new Set, controller: new AbortController };
38
+ const request = _HTMLBindingsProvider.createRequest( { detail: ref, live: true, signal: bindings[ $ref ].signal } );
39
+ HTMLContext.instance( this[ '#' ].host ).request( request, value => {
40
+ bindings[ $ref ].value = value;
41
+ bindings[ $ref ].subs.forEach( bracelet => bracelet.render( bindings ) );
42
+ } );
43
+ }
44
+ bindings[ $ref ].subs.add( bracelet );
45
+ } );
46
+ bracelet.render( bindings );
47
+ return returnValue;
48
+ }
49
+
50
+ delete( bracelet ) {
51
+ if ( !( bracelet instanceof Bracelet ) ) throw new Error( `Argument must be instance of Bracelet.` );
52
+ const returnValue = super.delete( bracelet );
53
+ const bindings = this[ '#' ].bindings;
54
+ bracelet.refs.forEach( ref => {
55
+ bindings[ ref ].subs.delete( bracelet );
56
+ if ( !bindings[ ref ].subs.size ) {
57
+ bindings[ ref ].controller.abort();
58
+ delete bindings[ ref ];
59
+ }
60
+ } );
61
+ return returnValue;
62
+ }
63
+
64
+ clear() {
65
+ for ( const bracelet of this ) { this.delete( bracelet ); }
66
+ }
67
+ }
@@ -0,0 +1,69 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import HTMLBracelets from './HTMLBracelets.js';
6
+ import Bracelet from './Bracelet.js';
7
+ import { _ } from '../util.js';
8
+
9
+ export default class TextBracelet extends Bracelet {
10
+ static get query() { return `text()[not(ancestor::script) and ${ this.tokens.contains }]`; }
11
+
12
+ static parse( ...nodes ) {
13
+ return nodes.reduce( ( nodes, node ) => {
14
+ let $node = node, $rest = node, startIndex, endIndex;
15
+ while ( $rest && ( startIndex = $rest.nodeValue.indexOf( this.tokens.startTag ) ) > -1 ) {
16
+ if ( startIndex > 0 ) { $node = $rest.splitText( startIndex ); }
17
+ if ( ( endIndex = $node.nodeValue.indexOf( this.tokens.endTag ) + this.tokens.endTag.length ) !== $node.nodeValue.length ) {
18
+ $rest = $node.splitText( endIndex );
19
+ } else { $rest = null; }
20
+ nodes.push( new this( $node ) );
21
+ }
22
+ return nodes;
23
+ }, [] );
24
+ }
25
+
26
+ static mount( ...bracelets ) {
27
+ for ( const bracelet of bracelets ) {
28
+ _( bracelet.node ).set( 'text-bracelet', bracelet );
29
+ HTMLBracelets.instance( bracelet.ownerElement ).add( bracelet );
30
+ }
31
+ }
32
+
33
+ static cleanup( ...nodes ) {
34
+ for ( const node of nodes ) {
35
+ const bracelet = _( node ).get( 'text-bracelet' );
36
+ if ( !bracelet ) continue;
37
+ bracelet.disconnect();
38
+ HTMLBracelets.instance( bracelet.ownerElement ).delete( bracelet );
39
+ _( node ).delete( 'text-bracelet' );
40
+ }
41
+ }
42
+
43
+ constructor( node ) {
44
+ super();
45
+ const expr = [ ...node.nodeValue.match( new RegExp( this.constructor.tokens.regex ) ) ][ 1 ].trim();
46
+ const $refs = [], $expr = this.parseExpr( expr, $refs );
47
+ Object.defineProperties( this, {
48
+ _value: { value: node.nodeValue, writable: true },
49
+ _dirty: { value: false, writable: true },
50
+ type: { get: () => 'text' },
51
+ expr: { get: () => $expr },
52
+ refs: { get: () => $refs },
53
+ node: { get: () => node },
54
+ ownerElement: { get: () => node.parentNode },
55
+ originalValue: { value: node.nodeValue },
56
+ } );
57
+ }
58
+
59
+ get value() { return this._value; }
60
+ set value( value ) {
61
+ if ( this.disconnected || value === this._value ) return;
62
+ this._value = value;
63
+ this._dirty = true;
64
+ this.node.nodeValue = value;
65
+ }
66
+
67
+ get nextSibling() { return this.node.nextSibling; }
68
+ get dirty() { return this._dirty; }
69
+ }
@@ -0,0 +1,71 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import { _, _init } from '../util.js';
6
+ import AttrBracelet from './AttrBracelet.js';
7
+ import TextBracelet from './TextBracelet.js';
8
+ import HTMLBracelets from './HTMLBracelets.js';
9
+
10
+ /**
11
+ * Initializes DOM Parts.
12
+ *
13
+ * @param $config Object
14
+ *
15
+ * @return Void
16
+ */
17
+ export default function init( $config = {} ) {
18
+ const { config, realdom, window } = _init.call( this, 'html-bracelets', $config, {
19
+ api: { bracelets: 'bracelets' },
20
+ isomorphic: true,
21
+ } );
22
+ exposeAPIs.call( window, config );
23
+ realtime.call( window, config );
24
+ }
25
+
26
+ /**
27
+ * Exposes DOM Parts with native APIs.
28
+ *
29
+ * @param Object config
30
+ *
31
+ * @return Void
32
+ */
33
+ function exposeAPIs( config ) {
34
+ const window = this;
35
+ // Assertions
36
+ if ( config.api.bracelets in window.document ) { throw new Error( `document already has a "${ config.api.bracelets }" property!` ); }
37
+ if ( config.api.bracelets in window.HTMLElement.prototype ) { throw new Error( `The "HTMLElement" class already has a "${ config.api.bracelets }" property!` ); }
38
+ // Definitions
39
+ Object.defineProperty( window.document, config.api.bracelets, { get: function() {
40
+ return HTMLBracelets.instance( window.document );
41
+ } } );
42
+ Object.defineProperty( window.HTMLElement.prototype, config.api.bracelets, { get: function() {
43
+ return HTMLBracelets.instance( this );
44
+ } } );
45
+ }
46
+
47
+ /**
48
+ * Performs realtime capture of elements and their attributes
49
+ *
50
+ * @param Object config
51
+ *
52
+ * @return Void
53
+ */
54
+ function realtime( config ) {
55
+ const window = this, { realdom } = window.webqit;
56
+ realdom.realtime( window.document ).observe( `(${ TextBracelet.query })`, record => {
57
+ TextBracelet.cleanup( ...record.exits );
58
+ TextBracelet.mount( ...TextBracelet.parse( ...record.entrants.filter( node => !_( node ).has( 'text-bracelet' )/** generated text nodes during parse() */ ) ) );
59
+ }, { subtree: true } );
60
+ realdom.realtime( window.document, 'attr' ).observe( records => {
61
+ for ( const record of records ) {
62
+ if ( _( record.target ).get( 'attr-bracelets' )?.active.some( p => p.attr.nodeName === record.name ) ) continue;
63
+ if ( record.oldValue ) { AttrBracelet.cleanup( record.value ? record.target.attributes[ record.name ] : { ownerElement: record.target, nodeName: record.name } ); }
64
+ if ( record.value ) { AttrBracelet.mount( ...AttrBracelet.parse( record.target.attributes[ record.name ] ) ); }
65
+ }
66
+ }, { subtree: true, newValue: true, oldValue: true, timing: 'sync' } );
67
+ realdom.realtime( window.document ).observe( `(${ AttrBracelet.query })`, record => {
68
+ AttrBracelet.cleanup( ...record.exits.reduce( ( attrs, node ) => [ ...attrs, ...node.attributes ], [] ) );
69
+ AttrBracelet.mount( ...AttrBracelet.parse( ...record.entrants.reduce( ( attrs, node ) => [ ...attrs, ...node.attributes ], [] ) ) );
70
+ }, { subtree: true } );
71
+ }
@@ -0,0 +1,10 @@
1
+
2
+ /**
3
+ * @imports
4
+ */
5
+ import init from './index.js';
6
+
7
+ /**
8
+ * @init
9
+ */
10
+ init.call( window );
@@ -165,9 +165,9 @@ export default class _HTMLExportsManager {
165
165
  const handleInherited = records => {
166
166
  records.forEach( record => {
167
167
  if ( Observer.get( this.modules, record.key ) !== record.oldValue ) return;
168
- if ( [ 'get'/*initial get*/, 'set', 'defineProperty' ].includes( record.type ) ) {
168
+ if ( [ 'get'/*initial get*/, 'set', 'def' ].includes( record.type ) ) {
169
169
  Observer[ record.type.replace( 'get', 'set' ) ]( this.modules, record.key, record.value );
170
- } else if ( record.type === 'deleteProperty' ) {
170
+ } else if ( record.type === 'delete' ) {
171
171
  Observer.deleteProperty( this.modules, record.key );
172
172
  }
173
173
  } );
@@ -9,19 +9,13 @@ import { _ } from '../util.js';
9
9
 
10
10
  export default class _HTMLImportsProvider extends HTMLContextProvider {
11
11
 
12
- /**
13
- * @createId
14
- */
15
- static createId( host, fields = {} ) {
16
- if ( !( 'type' in fields ) ) fields = { type: 'htmlimports', ...fields };
17
- return super.createId( host, fields );
18
- }
12
+ static type = 'html-imports';
19
13
 
20
14
  /**
21
15
  * @createRequest
22
16
  */
23
17
  static createRequest( fields = {} ) {
24
- const request = { type: 'htmlimports', ...fields };
18
+ const request = super.createRequest( fields );
25
19
  if ( !request.contextName && request.detail?.startsWith( '/' ) ) { request.contextName = 'root'; }
26
20
  else if ( request.detail?.startsWith( '@' ) ) {
27
21
  const [ contextName, ...detail ] = request.detail.slice( 1 ).split( /(?<=\w)(?=\/|#)/ ).map( s => s.trim() );
@@ -65,7 +59,7 @@ export default class _HTMLImportsProvider extends HTMLContextProvider {
65
59
  return Observer.reduce( this.contextModules, path, Observer.get, result => {
66
60
  return event.respondWith( Array.isArray( result ) ? result : result.value );
67
61
  }, { signal, ...options } );
68
- }, options );
62
+ }, { lifecycleSignals: true, ...options } );
69
63
  }
70
64
 
71
65
  /**
@@ -32,7 +32,7 @@ export default function init( $config = {} ) {
32
32
  static get config() { return config; }
33
33
  };
34
34
  window.webqit.Observer = Observer;
35
- exposeModulesObjects.call( window, config );
35
+ exposeAPIs.call( window, config );
36
36
  realdom.ready( () => hydration.call( window, config ) );
37
37
  realtime.call( window, config );
38
38
  }
@@ -62,7 +62,7 @@ export function getModulesObject( node, autoCreate = true ) {
62
62
  *
63
63
  * @return Void
64
64
  */
65
- function exposeModulesObjects( config ) {
65
+ function exposeAPIs( config ) {
66
66
  const window = this;
67
67
  // Assertions
68
68
  if ( config.template.api.modules in window.HTMLTemplateElement.prototype ) { throw new Error( `The "HTMLTemplateElement" class already has a "${ config.template.api.modules }" property!` ); }
@@ -76,14 +76,14 @@ function exposeModulesObjects( config ) {
76
76
  Object.defineProperty( window.HTMLTemplateElement.prototype, config.template.api.moduledef, { get: function() {
77
77
  return this.getAttribute( config.template.attr.moduledef );
78
78
  } } );
79
- Object.defineProperty( window.document, config.context.api.import, { value: function( ref, callback, options = {} ) {
80
- return importRequest( window.document, ref, callback, options );
79
+ Object.defineProperty( window.document, config.context.api.import, { value: function( ref, callback = null ) {
80
+ return importRequest( window.document, ...arguments );
81
81
  } } );
82
- Object.defineProperty( window.HTMLElement.prototype, config.context.api.import, { value: function( ref, callback, options = {} ) {
83
- return importRequest( this, ref, callback, options );
82
+ Object.defineProperty( window.HTMLElement.prototype, config.context.api.import, { value: function( ref, callback = null ) {
83
+ return importRequest( this, ...arguments );
84
84
  } } );
85
- function importRequest( context, ref, callback, options ) {
86
- const request = _HTMLImportsProvider.createRequest( { detail: ref, ...options } );
85
+ function importRequest( context, ref, callback = null ) {
86
+ const request = _HTMLImportsProvider.createRequest( { detail: ref } );
87
87
  return HTMLContext.instance( context ).request( request, callback );
88
88
  }
89
89
  }
@@ -5,13 +5,15 @@
5
5
  import Observer from '@webqit/observer';
6
6
  import { _, _init } from '../util.js';
7
7
 
8
+ export { Observer }
9
+
8
10
  /**
9
11
  * @init
10
12
  *
11
13
  * @param Object $config
12
14
  */
13
15
  export default function init( $config = {} ) {
14
- const { config, window } = _init.call( this, 'namespace-api', $config, {
16
+ const { config, window } = _init.call( this, 'html-namespaces', $config, {
15
17
  id: { attr: 'id' },
16
18
  namespace: { attr: 'namespace', api: 'namespace', },
17
19
  target: { attr: ':target', event: ':target', scrolling: true },
@@ -21,33 +23,10 @@ export default function init( $config = {} ) {
21
23
  config.idSelector = `[${ window.CSS.escape( config.id.attr ) }]`;
22
24
  config.namespaceSelector = `[${ window.CSS.escape( config.namespace.attr ) }]`;
23
25
  window.webqit.Observer = Observer;
24
- exposeNamespaceObjects.call( window, config );
26
+ exposeAPIs.call( window, config );
25
27
  realtime.call( window, config );
26
28
  }
27
29
 
28
- export { Observer }
29
-
30
- /**
31
- * Exposes Namespaced HTML with native APIs.
32
- *
33
- * @param Object config
34
- *
35
- * @return Void
36
- */
37
- function exposeNamespaceObjects( config ) {
38
- const window = this;
39
- // Assertions
40
- if ( config.namespace.api in window.document ) { throw new Error( `document already has a "${ config.namespace.api }" property!` ); }
41
- if ( config.namespace.api in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.namespace.api }" property!` ); }
42
- // Definitions
43
- Object.defineProperty( window.document, config.namespace.api, { get: function() {
44
- return Observer.proxy( getNamespaceObject.call( window, window.document, config ) );
45
- } });
46
- Object.defineProperty( window.Element.prototype, config.namespace.api, { get: function() {
47
- return Observer.proxy( getNamespaceObject.call( window, this, config ) );
48
- } } );
49
- }
50
-
51
30
  /**
52
31
  * Returns the "namespace" object associated with the given node.
53
32
  *
@@ -78,6 +57,27 @@ function getNamespaceObject( node, config ) {
78
57
  return _( node ).get( 'namespace' );
79
58
  }
80
59
 
60
+ /**
61
+ * Exposes Namespaced HTML with native APIs.
62
+ *
63
+ * @param Object config
64
+ *
65
+ * @return Void
66
+ */
67
+ function exposeAPIs( config ) {
68
+ const window = this;
69
+ // Assertions
70
+ if ( config.namespace.api in window.document ) { throw new Error( `document already has a "${ config.namespace.api }" property!` ); }
71
+ if ( config.namespace.api in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.namespace.api }" property!` ); }
72
+ // Definitions
73
+ Object.defineProperty( window.document, config.namespace.api, { get: function() {
74
+ return Observer.proxy( getNamespaceObject.call( window, window.document, config ) );
75
+ } });
76
+ Object.defineProperty( window.Element.prototype, config.namespace.api, { get: function() {
77
+ return Observer.proxy( getNamespaceObject.call( window, this, config ) );
78
+ } } );
79
+ }
80
+
81
81
  /**
82
82
  * Performs realtime capture of elements and builds their relationships.
83
83
  *
package/src/index.js CHANGED
@@ -3,12 +3,18 @@
3
3
  * @imports
4
4
  */
5
5
  import Observer from '@webqit/observer';
6
- import BindingsAPI from './bindings-api/index.js';
7
6
  import ContextAPI from './context-api/index.js';
7
+ import BindingsAPI from './bindings-api/index.js';
8
+ import HTMLBracelets from './html-bracelets/index.js';
9
+ import HTMLNamespaces from './html-namespaces/index.js';
10
+ import HTMLImports from './html-imports/index.js';
8
11
  import ScopedCSS from './scoped-css/index.js';
9
12
  import ScopedJS from './scoped-js/index.js';
10
- import NamespaceAPI from './namespace-api/index.js';
11
- import HTMLImports from './html-imports/index.js';
13
+
14
+ /**
15
+ * @exports
16
+ */
17
+ export { Observer }
12
18
 
13
19
  /**
14
20
  * @init
@@ -16,16 +22,12 @@ import HTMLImports from './html-imports/index.js';
16
22
  export default function init( configs = {} ) {
17
23
  if ( !this.webqit ) { this.webqit = {}; }
18
24
  // --------------
19
- BindingsAPI.call( this, ( configs.BINDINGS_API || {} ) );
20
25
  ContextAPI.call( this, ( configs.CONTEXT_API || {} ) );
21
- ScopedJS.call( this, ( configs.SCOPED_JS || {} ) );
22
- ScopedCSS.call( this, ( configs.SCOPED_CSS || {} ) );
23
- NamespaceAPI.call( this, ( configs.NAMESPACE_API || {} ) );
26
+ BindingsAPI.call( this, ( configs.BINDINGS_API || {} ) );
27
+ HTMLBracelets.call( this, ( configs.HTML_BRACELETS || {} ) );
28
+ HTMLNamespaces.call( this, ( configs.HTML_NAMESPACES || {} ) );
24
29
  HTMLImports.call( this, ( configs.HTML_IMPORTS || {} ) );
30
+ ScopedCSS.call( this, ( configs.SCOPED_CSS || {} ) );
31
+ ScopedJS.call( this, ( configs.SCOPED_JS || {} ) );
25
32
  // --------------
26
33
  }
27
-
28
- /**
29
- * @exports
30
- */
31
- export { Observer }
@@ -0,0 +1,26 @@
1
+
2
+ export default class Hash {
3
+
4
+ // Unique ID generator
5
+ static hashTable = new Map;
6
+ static uniqId = () => (0|Math.random()*9e6).toString(36);
7
+
8
+ // Hash of anything generator
9
+ static toHash( val ) {
10
+ let hash;
11
+ if ( !( hash = this.hashTable.get( val ) ) ) {
12
+ hash = this.uniqId();
13
+ this.hashTable.set( val, hash );
14
+ }
15
+ return hash;
16
+ }
17
+
18
+ // Value of any hash
19
+ static fromHash( hash ) {
20
+ let val;
21
+ this.hashTable.forEach( ( _hash, _val ) => {
22
+ if ( _hash === hash ) val = _val;
23
+ } );
24
+ return val;
25
+ }
26
+ }
@@ -2,13 +2,20 @@
2
2
  /**
3
3
  * @imports
4
4
  */
5
- import { _isTypeObject } from '@webqit/util/js/index.js';
6
- import { resolveParams } from '@webqit/reflex-functions/src/params.js';
7
- import ReflexFunction from '@webqit/reflex-functions/src/ReflexFunctionLite.js';
5
+ import { resolveParams } from '@webqit/stateful-js/src/params.js';
6
+ import { StatefulAsyncFunction, StatefulAsyncScript, StatefulModule, State } from '@webqit/stateful-js/src/index.async.js';
8
7
  import Observer from '@webqit/observer';
9
- import Compiler from './Compiler.js';
8
+ import Hash from './Hash.js';
10
9
  import { _init } from '../util.js';
11
10
 
11
+ export {
12
+ StatefulAsyncFunction,
13
+ StatefulAsyncScript,
14
+ StatefulModule,
15
+ State,
16
+ Observer,
17
+ }
18
+
12
19
  /**
13
20
  * @init
14
21
  *
@@ -17,68 +24,47 @@ import { _init } from '../util.js';
17
24
  export default function init( { advanced = {}, ...$config } ) {
18
25
  const { config, window } = _init.call( this, 'scoped-js', $config, {
19
26
  script: { retention: 'retain', mimeType: '' },
20
- advanced: resolveParams( advanced, {
21
- parserParams: { allowReturnOutsideFunction: false, allowSuperOutsideMethod: false },
22
- compilerParams: { globalsNoObserve: [ 'alert' ] },
23
- runtimeParams: { apiVersion: 2 },
24
- } ),
27
+ advanced: resolveParams( advanced ),
25
28
  } );
26
29
  config.scriptSelector = ( Array.isArray( config.script.mimeType ) ? config.script.mimeType : [ config.script.mimeType ] ).reduce( ( selector, mm ) => {
27
30
  const qualifier = mm ? `[type=${ window.CSS.escape( mm ) }]` : '';
28
- return selector.concat( `script${ qualifier }[scoped],script${ qualifier }[reflex]` );
31
+ return selector.concat( `script${ qualifier }[scoped],script${ qualifier }[stateful]` );
29
32
  }, [] ).join( ',' );
30
- window.webqit.oohtml.Script = { compileCache: [ new Map, new Map, ] };
31
- window.webqit.ReflexFunction = ReflexFunction;
32
- window.webqit.Observer = Observer;
33
+ Object.assign( window.webqit, { StatefulAsyncFunction, StatefulAsyncScript, StatefulModule, State, Observer } );
34
+ window.webqit.oohtml.Script = {
35
+ compileCache: [ new Map, new Map, ],
36
+ execute: execute.bind( window, config ),
37
+ };
33
38
  realtime.call( window, config );
34
39
  }
35
40
 
36
- export {
37
- ReflexFunction,
38
- Observer,
39
- }
40
-
41
- // ------------------
42
41
  // Script runner
43
- export function execute( compiledScript, thisContext, script ) {
44
- if ( !compiledScript.function ) throw new Error( `Input script must already be compiled!` );
45
- const _try = ( callback, isRerender = false ) => {
46
- return callback();
47
- };
48
- // Execute...
49
- const returnValue = compiledScript.function.call( thisContext );
50
- if ( script.reflex ) {
51
- // Rerending processes,,,
52
- Object.defineProperty( script, 'reflect', { value: ( ...args ) => _await( returnValue, ( [ , reflect ] ) => reflect( ...args ) ) } );
53
- _await( script.properties, properties => {
54
- const _env = { 'this': thisContext };
55
- const getPaths = ( base, record_s ) => ( Array.isArray( record_s ) ? record_s : [ record_s ] ).map( record => [ ...base, ...( record.path || [ record.key ] ) ] );
56
- properties.processes = properties.dependencies.map( path => {
57
- if ( _isTypeObject( _env[ path[ 0 ] ] ) ) {
58
- if ( path.length === 1 ) return;
59
- return Observer.reduce( _env[ path[ 0 ] ], path.slice( 1 ), Observer.observe, record_s => {
60
- script.reflect( ...getPaths( [ path[ 0 ] ], record_s ) );
61
- } );
62
- }
63
- return Observer.reduce( globalThis, path, Observer.observe, record_s => {
64
- script.reflect( ...getPaths( [], record_s ) );
65
- } );
66
- } );
67
- } );
68
- }
42
+ async function execute( config, execHash ) {
69
43
  const window = this, { realdom } = window.webqit;
44
+ const exec = Hash.fromHash( execHash );
45
+ if ( !exec ) throw new Error( `Argument must be a valid exec hash.` );
46
+ const { script, compiledScript, thisContext } = exec;
47
+ // Honour retention flag
48
+ if ( config.script.retention === 'dispose' ) {
49
+ script.remove();
50
+ } else if ( config.script.retention === 'dispose' ) {
51
+ script.textContent = `"source hidden"`;
52
+ } else {
53
+ script.textContent = await compiledScript.toString();
54
+ }
55
+ // Execute and save state
56
+ const state = ( await compiledScript.bind( thisContext ) ).execute();
57
+ if ( thisContext instanceof window.Element && script.scoped ) {
58
+ if ( !thisContext.scripts ) { Object.defineProperty( thisContext, 'scripts', { value: [] } ); }
59
+ thisContext.scripts.push( state );
60
+ }
61
+ // Observe DOM removal
70
62
  if ( !( thisContext instanceof window.Node ) ) return script;
71
63
  realdom.realtime( window.document ).observe( thisContext, () => {
72
- if ( script.reflex ) {
73
- // Rerending processes,,,
74
- _await( script.properties, properties => {
75
- properties.processes.forEach( process => process?.abort() );
76
- } );
77
- }
78
64
  thisContext.dispatchEvent( new window.CustomEvent( 'remove' ) );
79
- thisContext.scripts.delete( script );
65
+ state.dispose();
66
+ thisContext.scripts.splice( thisContext.scripts.indexOf( state, 1 ) );
80
67
  }, { subtree: true, timing: 'sync', generation: 'exits' } );
81
- return script;
82
68
  }
83
69
 
84
70
  /**
@@ -89,25 +75,39 @@ export function execute( compiledScript, thisContext, script ) {
89
75
  * @return Void
90
76
  */
91
77
  function realtime( config ) {
92
- const window = this, { realdom } = window.webqit;
78
+ const window = this, { oohtml, realdom } = window.webqit;
93
79
  if ( !window.HTMLScriptElement.supports ) { window.HTMLScriptElement.supports = () => false; }
94
80
  const potentialManualTypes = [ 'module' ].concat( config.script.mimeType || [] );
95
- const compiler = new Compiler( window, config, execute ), handled = () => {};
96
81
  realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( config.scriptSelector, record => {
97
82
  record.entrants.forEach( script => {
98
83
  if ( script.cloned ) return;
99
- if ( 'reflex' in script ) return handled( script );
100
- Object.defineProperty( script, 'reflex', { value: script.hasAttribute( 'reflex' ) } );
84
+ if ( 'stateful' in script ) return handled( script );
85
+ Object.defineProperty( script, 'stateful', { value: script.hasAttribute( 'stateful' ) } );
101
86
  if ( 'scoped' in script ) return handled( script );
102
87
  Object.defineProperty( script, 'scoped', { value: script.hasAttribute( 'scoped' ) } );
103
- if ( /*record.type === 'query' ||*/ ( potentialManualTypes.includes( script.type ) && !window.HTMLScriptElement.supports( script.type ) ) ) {
104
- Object.defineProperty( script, 'handling', { value: 'manual' } );
88
+ // Do compilation
89
+ const textContent = ( script._ = script.textContent.trim() ) && script._.startsWith( '/*@oohtml*/if(false){' ) && script._.endsWith( '}/*@oohtml*/' ) ? script._.slice( 21, -12 ) : script.textContent;
90
+ const sourceHash = Hash.toHash( textContent );
91
+ const compileCache = oohtml.Script.compileCache[ script.stateful ? 0 : 1 ];
92
+ let compiledScript;
93
+ if ( !( compiledScript = compileCache.get( sourceHash ) ) ) {
94
+ const { parserParams, compilerParams, runtimeParams } = config.advanced;
95
+ compiledScript = new ( script.type === 'module' ? StatefulModule : StatefulAsyncScript )( textContent, {
96
+ packageName: script.id,
97
+ parserParams,
98
+ compilerParams: { ...compilerParams, startStatic: !script.stateful },
99
+ runtimeParams,
100
+ } );
101
+ compileCache.set( sourceHash, compiledScript );
105
102
  }
103
+ // Run now!!!
106
104
  const thisContext = script.scoped ? script.parentNode || record.target : ( script.type === 'module' ? undefined : window );
107
- compiler.compile( script, thisContext );
105
+ const execHash = Hash.toHash( { script, compiledScript, thisContext } );
106
+ const manualHandling = record.type === 'query' || ( potentialManualTypes.includes( script.type ) && !window.HTMLScriptElement.supports( script.type ) );
107
+ if ( manualHandling ) { oohtml.Script.execute( execHash ); } else {
108
+ script.textContent = `webqit.oohtml.Script.execute( '${ execHash }' );`;
109
+ }
108
110
  } );
109
111
  }, { live: true, timing: 'intercept', generation: 'entrants', eventDetails: true } );
110
112
  // ---
111
- }
112
-
113
- const _await = ( value, callback ) => value instanceof Promise ? value.then( callback ) : callback( value );
113
+ }
package/test/index.js CHANGED
@@ -21,7 +21,7 @@ export function createDocument( head = '', body = '', callback = null, ) {
21
21
  <!DOCTYPE html>
22
22
  <html>
23
23
  <head>
24
- <meta name="reflex-compiler-url" content="../reflex-functions/dist/compiler.js">
24
+ <meta name="stateful-compiler-url" content="../stateful-js/dist/compiler.js">
25
25
  <script ssr src="/dist/main.js"></script>
26
26
  ${ head }
27
27
  </head>
@@ -166,7 +166,7 @@ describe(`HTML Modules`, function() {
166
166
  };
167
167
  // -------
168
168
  const contextRequest = ( el, params, callback ) => {
169
- const request = { type: 'htmlimports', live: true, ...params };
169
+ const request = { type: 'html-imports', live: true, ...params };
170
170
  const event = new document.context.ContextRequestEvent( request, callback, {
171
171
  bubbles: true,
172
172
  } );
@@ -12,7 +12,7 @@ describe(`Test: Scoped JS`, function() {
12
12
  it(`Should do basic observe`, async function() {
13
13
  const head = '', body = `
14
14
  <h1>Hello World!</h1>
15
- <script scoped reflex>
15
+ <script scoped stateful>
16
16
  testRecords.push( this );
17
17
  console.log('-------scoped JS here.');
18
18
  </script>`;