@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.
- package/README.md +14 -18
- package/dist/bindings-api.js +1 -1
- package/dist/bindings-api.js.map +3 -3
- package/dist/context-api.js +1 -1
- package/dist/context-api.js.map +3 -3
- package/dist/html-bracelets.js +2 -0
- package/dist/html-bracelets.js.map +7 -0
- package/dist/html-imports.js +1 -1
- package/dist/html-imports.js.map +3 -3
- package/dist/html-namespaces.js +2 -0
- package/dist/html-namespaces.js.map +7 -0
- package/dist/main.js +16 -24
- package/dist/main.js.map +3 -3
- package/dist/scoped-css.js +2 -2
- package/dist/scoped-css.js.map +3 -3
- package/dist/scoped-js.js +15 -23
- package/dist/scoped-js.js.map +3 -3
- package/package.json +7 -7
- package/src/bindings-api/_HTMLBindingsProvider.js +38 -0
- package/src/bindings-api/index.js +49 -26
- package/src/context-api/ContextReturnValue.js +22 -0
- package/src/context-api/HTMLContext.js +14 -6
- package/src/context-api/HTMLContextProvider.js +27 -10
- package/src/context-api/_ContextRequestEvent.js +2 -2
- package/src/context-api/index.js +26 -10
- package/src/html-bracelets/AttrBracelet.js +109 -0
- package/src/html-bracelets/Bracelet.js +78 -0
- package/src/html-bracelets/HTMLBracelets.js +67 -0
- package/src/html-bracelets/TextBracelet.js +69 -0
- package/src/html-bracelets/index.js +71 -0
- package/src/html-bracelets/targets.browser.js +10 -0
- package/src/html-imports/_HTMLExportsManager.js +2 -2
- package/src/html-imports/_HTMLImportsProvider.js +3 -9
- package/src/html-imports/index.js +8 -8
- package/src/{namespace-api → html-namespaces}/index.js +25 -25
- package/src/index.js +14 -12
- package/src/scoped-js/Hash.js +26 -0
- package/src/scoped-js/index.js +63 -63
- package/test/index.js +1 -1
- package/test/modules.test.js +1 -1
- package/test/scoped-js.test.js +1 -1
- package/dist/namespace-api.js +0 -2
- package/dist/namespace-api.js.map +0 -7
- package/src/scoped-js/Compiler.js +0 -299
- /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
|
+
}
|
|
@@ -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', '
|
|
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 === '
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
80
|
-
return importRequest( window.document,
|
|
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
|
|
83
|
-
return importRequest( this,
|
|
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
|
|
86
|
-
const request = _HTMLImportsProvider.createRequest( { detail: ref
|
|
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, '
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
}
|
package/src/scoped-js/index.js
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
|
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 }[
|
|
31
|
+
return selector.concat( `script${ qualifier }[scoped],script${ qualifier }[stateful]` );
|
|
29
32
|
}, [] ).join( ',' );
|
|
30
|
-
window.webqit
|
|
31
|
-
window.webqit.
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ( '
|
|
100
|
-
Object.defineProperty( script, '
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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="
|
|
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>
|
package/test/modules.test.js
CHANGED
|
@@ -166,7 +166,7 @@ describe(`HTML Modules`, function() {
|
|
|
166
166
|
};
|
|
167
167
|
// -------
|
|
168
168
|
const contextRequest = ( el, params, callback ) => {
|
|
169
|
-
const request = { type: '
|
|
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
|
} );
|
package/test/scoped-js.test.js
CHANGED
|
@@ -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
|
|
15
|
+
<script scoped stateful>
|
|
16
16
|
testRecords.push( this );
|
|
17
17
|
console.log('-------scoped JS here.');
|
|
18
18
|
</script>`;
|