@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
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"wicg-proposal"
|
|
15
15
|
],
|
|
16
16
|
"homepage": "https://webqit.io/tooling/oohtml",
|
|
17
|
-
"version": "2.1.
|
|
17
|
+
"version": "2.1.53",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
@@ -32,19 +32,19 @@
|
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "mocha --extension .test.js --exit",
|
|
34
34
|
"test:coverage": "c8 --reporter=text-lcov npm run test | coveralls",
|
|
35
|
-
"build": "esbuild main=src/targets.browser.js
|
|
36
|
-
"preversion": "npm run
|
|
35
|
+
"build": "esbuild main=src/targets.browser.js context-api=src/context-api/targets.browser.js bindings-api=src/bindings-api/targets.browser.js html-bracelets=src/html-bracelets/targets.browser.js html-namespaces=src/html-namespaces/targets.browser.js html-imports=src/html-imports/targets.browser.js scoped-js=src/scoped-js/targets.browser.js scoped-css=src/scoped-css/targets.browser.js --bundle --minify --sourcemap --outdir=dist",
|
|
36
|
+
"preversion": "npm run build && git add -A dist",
|
|
37
37
|
"postversion": "npm publish",
|
|
38
38
|
"postpublish": "git push && git push --tags"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@webqit/observer": "^2.
|
|
42
|
-
"@webqit/realdom": "^2.1.
|
|
43
|
-
"@webqit/
|
|
41
|
+
"@webqit/observer": "^2.2.9",
|
|
42
|
+
"@webqit/realdom": "^2.1.17-0",
|
|
43
|
+
"@webqit/stateful-js": "^3.0.14-0",
|
|
44
44
|
"@webqit/util": "^0.8.11"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@webqit/oohtml-ssr": "^1.2.
|
|
47
|
+
"@webqit/oohtml-ssr": "^1.2.16",
|
|
48
48
|
"chai": "^4.3.4",
|
|
49
49
|
"coveralls": "^3.1.1",
|
|
50
50
|
"esbuild": "^0.14.43",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import Observer from '@webqit/observer';
|
|
6
|
+
import { HTMLContextProvider } from '../context-api/index.js';
|
|
7
|
+
|
|
8
|
+
export default class _HTMLBindingsProvider extends HTMLContextProvider {
|
|
9
|
+
|
|
10
|
+
static type = 'bindings';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @matchesRequest
|
|
14
|
+
*/
|
|
15
|
+
static matchRequest( id, request ) {
|
|
16
|
+
return super.matchRequest( id, request ) && ( !request.detail || !id.detail || ( Array.isArray( request.detail ) ? request.detail[ 0 ] === id.detail : request.detail === id.detail ) );
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @bindingsObj
|
|
21
|
+
*/
|
|
22
|
+
get bindingsObj() {
|
|
23
|
+
return this.host[ this.constructor.config.api.bindings ];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @handle()
|
|
28
|
+
*/
|
|
29
|
+
handle( event ) {
|
|
30
|
+
// Any existing event.request.controller? Abort!
|
|
31
|
+
event.request.controller?.abort();
|
|
32
|
+
if ( !( event.request.detail + '' ).trim() ) return event.respondWith( this.bindingsObj );
|
|
33
|
+
event.request.controller = Observer.reduce( this.bindingsObj, Array.isArray( event.request.detail ) ? event.request.detail : [ event.request.detail ], Observer.get, descriptor => {
|
|
34
|
+
if ( this.disposed ) return; // If already scheduled but aborted as in provider unmounting
|
|
35
|
+
event.respondWith( descriptor.value );
|
|
36
|
+
}, { live: event.request.live, descripted: true } );
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
5
|
import Observer from '@webqit/observer';
|
|
6
|
+
import _HTMLBindingsProvider from './_HTMLBindingsProvider.js';
|
|
6
7
|
import { _, _init } from '../util.js';
|
|
7
8
|
|
|
9
|
+
export { Observer }
|
|
10
|
+
|
|
8
11
|
/**
|
|
9
12
|
* @init
|
|
10
13
|
*
|
|
@@ -12,14 +15,16 @@ import { _, _init } from '../util.js';
|
|
|
12
15
|
*/
|
|
13
16
|
export default function init( $config = {} ) {
|
|
14
17
|
const { config, window } = _init.call( this, 'bindings-api', $config, {
|
|
18
|
+
context: { attr: { contextname: 'contextname' }, },
|
|
15
19
|
api: { bind: 'bind', bindings: 'bindings', },
|
|
16
20
|
} );
|
|
21
|
+
window.webqit.HTMLBindingsProvider = class extends _HTMLBindingsProvider {
|
|
22
|
+
static get config() { return config; }
|
|
23
|
+
};
|
|
17
24
|
window.webqit.Observer = Observer;
|
|
18
25
|
exposeAPIs.call( window, config );
|
|
19
26
|
}
|
|
20
27
|
|
|
21
|
-
export { Observer }
|
|
22
|
-
|
|
23
28
|
/**
|
|
24
29
|
* @Exports
|
|
25
30
|
*
|
|
@@ -30,10 +35,48 @@ function getBindingsObject( node ) {
|
|
|
30
35
|
if ( !_( node ).has( 'bindings' ) ) {
|
|
31
36
|
const bindingsObj = Object.create( null );
|
|
32
37
|
_( node ).set( 'bindings', bindingsObj );
|
|
38
|
+
Observer.observe( bindingsObj, mutations => {
|
|
39
|
+
for ( const mutation of mutations ) {
|
|
40
|
+
if ( mutation.type === 'delete' ) {
|
|
41
|
+
detachBindingsContext.call( this, node, mutation.key );
|
|
42
|
+
} else { attachBindingsContext.call( this, node, mutation.key ); }
|
|
43
|
+
}
|
|
44
|
+
} );
|
|
33
45
|
}
|
|
34
46
|
return _( node ).get( 'bindings' );
|
|
35
47
|
}
|
|
36
48
|
|
|
49
|
+
function attachBindingsContext( host, key ) {
|
|
50
|
+
const window = this, { HTMLBindingsProvider } = window.webqit;
|
|
51
|
+
const contextId = HTMLBindingsProvider.createId( host, { detail: key } );
|
|
52
|
+
HTMLBindingsProvider.attachTo( host, contextId );
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function detachBindingsContext( host, key ) {
|
|
56
|
+
const window = this, { HTMLBindingsProvider } = window.webqit;
|
|
57
|
+
const contextId = HTMLBindingsProvider.createId( host, { detail: key } );
|
|
58
|
+
HTMLBindingsProvider.detachFrom( host, contextId );
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Exposes Bindings with native APIs.
|
|
63
|
+
*
|
|
64
|
+
* @param document|Element target
|
|
65
|
+
* @param Object bindings
|
|
66
|
+
* @param Object params
|
|
67
|
+
*
|
|
68
|
+
* @return Void
|
|
69
|
+
*/
|
|
70
|
+
function applyBindings( target, bindings, { merge, diff, namespace } = {} ) {
|
|
71
|
+
const bindingsObj = getBindingsObject.call( this, target );
|
|
72
|
+
const $params = { diff, namespace };
|
|
73
|
+
const exitingKeys = merge ? [] : Observer.ownKeys( bindingsObj, $params ).filter( key => !( key in bindings ) );
|
|
74
|
+
return Observer.batch( bindingsObj, () => {
|
|
75
|
+
if ( exitingKeys.length ) { Observer.deleteProperties( bindingsObj, exitingKeys, $params ); }
|
|
76
|
+
return Observer.set( bindingsObj, bindings, $params );
|
|
77
|
+
}, $params );
|
|
78
|
+
}
|
|
79
|
+
|
|
37
80
|
/**
|
|
38
81
|
* Exposes Bindings with native APIs.
|
|
39
82
|
*
|
|
@@ -50,35 +93,15 @@ function exposeAPIs( config ) {
|
|
|
50
93
|
if ( config.api.bindings in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.api.bindings }" property!` ); }
|
|
51
94
|
// Definitions
|
|
52
95
|
Object.defineProperty( window.document, config.api.bind, { value: function( bindings, config = {} ) {
|
|
53
|
-
return applyBindings( window.document, bindings, config );
|
|
96
|
+
return applyBindings.call( window, window.document, bindings, config );
|
|
54
97
|
} });
|
|
55
98
|
Object.defineProperty( window.document, config.api.bindings, { get: function() {
|
|
56
|
-
return Observer.proxy( getBindingsObject( window.document ) );
|
|
99
|
+
return Observer.proxy( getBindingsObject.call( window, window.document ) );
|
|
57
100
|
} });
|
|
58
101
|
Object.defineProperty( window.Element.prototype, config.api.bind, { value: function( bindings, config = {} ) {
|
|
59
|
-
return applyBindings( this, bindings, config );
|
|
102
|
+
return applyBindings.call( window, this, bindings, config );
|
|
60
103
|
} });
|
|
61
104
|
Object.defineProperty( window.Element.prototype, config.api.bindings, { get: function() {
|
|
62
|
-
return Observer.proxy( getBindingsObject( this ) );
|
|
105
|
+
return Observer.proxy( getBindingsObject.call( window, this ) );
|
|
63
106
|
} } );
|
|
64
107
|
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Exposes Bindings with native APIs.
|
|
68
|
-
*
|
|
69
|
-
* @param document|Element target
|
|
70
|
-
* @param Object bindings
|
|
71
|
-
* @param Object params
|
|
72
|
-
*
|
|
73
|
-
* @return Void
|
|
74
|
-
*/
|
|
75
|
-
function applyBindings( target, bindings, { merge, diff, namespace } = {} ) {
|
|
76
|
-
const bindingsObj = getBindingsObject( target );
|
|
77
|
-
const $params = { diff, namespace };
|
|
78
|
-
if ( merge ) return Observer.set( bindingsObj, bindings, $params );;
|
|
79
|
-
const exitingKeys = Observer.ownKeys( bindingsObj, $params ).filter( key => !( key in bindings ) );
|
|
80
|
-
return Observer.batch( bindingsObj, () => {
|
|
81
|
-
if ( exitingKeys.length ) { Observer.deleteProperty( bindingsObj, exitingKeys, $params ); }
|
|
82
|
-
return Observer.set( bindingsObj, bindings, $params );
|
|
83
|
-
}, $params );
|
|
84
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import Observer from "@webqit/observer";
|
|
6
|
+
|
|
7
|
+
export default class ContextReturnValue {
|
|
8
|
+
constructor( request, hostElement ) {
|
|
9
|
+
this.request = request;
|
|
10
|
+
this.hostElement = hostElement;
|
|
11
|
+
if ( request.live && !request.signal ) {
|
|
12
|
+
Object.defineProperty( this, 'abortController', { value: new AbortController } );
|
|
13
|
+
request.signal = this.abortController.signal;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
callback( response ) { Observer.defineProperty( this, 'value', { value: response, configurable: true, enumerable: true } ); }
|
|
17
|
+
abort() {
|
|
18
|
+
if ( this.abortController ) { return this.abortController.abort(); }
|
|
19
|
+
const window = this.hostElement.ownerDocument?.defaultView || this.hostElement.defaultView;
|
|
20
|
+
if ( this.request.signal ) { return this.request.signal.dispatchEvent( new window.Event( 'abort' ) ); }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
|
+
import ContextReturnValue from './ContextReturnValue.js';
|
|
5
6
|
import _ContextRequestEvent from './_ContextRequestEvent.js';
|
|
6
7
|
import { _ } from '../util.js';
|
|
7
8
|
|
|
@@ -25,8 +26,7 @@ export default class HTMLContext {
|
|
|
25
26
|
const ContextRequestEvent = _ContextRequestEvent.call( host.ownerDocument?.defaultView || host.defaultView );
|
|
26
27
|
Object.defineProperty( this, 'ContextRequestEvent', { get: () => ContextRequestEvent } );
|
|
27
28
|
this[ Symbol.iterator ] = function*() {
|
|
28
|
-
|
|
29
|
-
yield it.next().value;
|
|
29
|
+
yield* priv.contexts;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -63,10 +63,18 @@ export default class HTMLContext {
|
|
|
63
63
|
/**
|
|
64
64
|
* @request()
|
|
65
65
|
*/
|
|
66
|
-
request( request, callback, options = {} ) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
request( request, callback = null, options = {} ) {
|
|
67
|
+
if ( typeof callback === 'object' ) {
|
|
68
|
+
options = callback;
|
|
69
|
+
callback = null;
|
|
70
|
+
}
|
|
71
|
+
let contextReturnValue;
|
|
72
|
+
if ( !callback ) {
|
|
73
|
+
contextReturnValue = new ContextReturnValue( request, this[ '#' ].host );
|
|
74
|
+
callback = contextReturnValue.callback.bind( contextReturnValue );
|
|
75
|
+
}
|
|
76
|
+
const returnValue = this[ '#' ].host.dispatchEvent( new this.ContextRequestEvent( request, callback, { bubbles: true, ...options } ) );
|
|
77
|
+
return contextReturnValue ?? returnValue;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
/**
|
|
@@ -7,6 +7,16 @@ import HTMLContext from './HTMLContext.js';
|
|
|
7
7
|
|
|
8
8
|
export default class HTMLContextProvider {
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* For reference purposes
|
|
12
|
+
*/
|
|
13
|
+
static providers = new Map;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* To be implemented by subclasses
|
|
17
|
+
*/
|
|
18
|
+
static type;
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* @config
|
|
12
22
|
*/
|
|
@@ -18,20 +28,21 @@ export default class HTMLContextProvider {
|
|
|
18
28
|
* @attachTo
|
|
19
29
|
*/
|
|
20
30
|
static attachTo( host, Id, multiple = false ) {
|
|
31
|
+
this.providers.set( this.type, this );
|
|
21
32
|
let provider, contextMgr = HTMLContext.instance( host );
|
|
22
|
-
if ( !multiple && ( provider = contextMgr.findProvider(
|
|
33
|
+
if ( !multiple && ( provider = contextMgr.findProvider( provider => this.matchId( provider.id, Id ) ) ) ) return provider;
|
|
23
34
|
return contextMgr.attachProvider( new this( Id ) );
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
/**
|
|
27
38
|
* @detachFrom
|
|
28
39
|
*/
|
|
29
|
-
static detachFrom( host, Id,
|
|
40
|
+
static detachFrom( host, Id, multipleOrFilter = false ) {
|
|
30
41
|
let provider, contextMgr = HTMLContext.instance( host );
|
|
31
42
|
for ( provider of contextMgr[ '#' ].contexts ) {
|
|
32
|
-
if ( !this.
|
|
43
|
+
if ( !this.matchId( provider.id, Id ) || ( typeof multipleOrFilter === 'function' && !multipleOrFilter( provider ) ) ) continue;
|
|
33
44
|
contextMgr.detachProvider( provider );
|
|
34
|
-
if ( typeof multiple !== 'function' && !
|
|
45
|
+
if ( typeof multiple !== 'function' && !multipleOrFilter ) return provider;
|
|
35
46
|
}
|
|
36
47
|
}
|
|
37
48
|
|
|
@@ -39,7 +50,7 @@ export default class HTMLContextProvider {
|
|
|
39
50
|
* @createId
|
|
40
51
|
*/
|
|
41
52
|
static createId( host, fields = {} ) {
|
|
42
|
-
const id = { ...fields };
|
|
53
|
+
const id = { type: this.type, ...fields };
|
|
43
54
|
if ( id.contextName ) return id;
|
|
44
55
|
if ( host.getAttribute && !( id.contextName = ( host.getAttribute( this.config.context.attr.contextname ) || '' ).trim() ) ) {
|
|
45
56
|
delete id.contextName;
|
|
@@ -48,20 +59,26 @@ export default class HTMLContextProvider {
|
|
|
48
59
|
}
|
|
49
60
|
return id;
|
|
50
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @matchId
|
|
65
|
+
*/
|
|
66
|
+
static matchId( a, b ) {
|
|
67
|
+
return _compare( a, b, 1, true );
|
|
68
|
+
}
|
|
51
69
|
|
|
52
70
|
/**
|
|
53
71
|
* @createRequest
|
|
54
72
|
*/
|
|
55
73
|
static createRequest( fields = {} ) {
|
|
56
|
-
return { ...fields };
|
|
74
|
+
return { type: this.type, ...fields };
|
|
57
75
|
}
|
|
58
76
|
|
|
59
77
|
/**
|
|
60
78
|
* @matchesRequest
|
|
61
79
|
*/
|
|
62
|
-
static matchRequest( id, request
|
|
63
|
-
|
|
64
|
-
return request.type === id.type && !request.contextName || request.contextName === id.contextName;
|
|
80
|
+
static matchRequest( id, request ) {
|
|
81
|
+
return request.type === id.type && ( !request.contextName || request.contextName === id.contextName );
|
|
65
82
|
}
|
|
66
83
|
|
|
67
84
|
/**
|
|
@@ -108,7 +125,7 @@ export default class HTMLContextProvider {
|
|
|
108
125
|
*/
|
|
109
126
|
handleEvent( event ) {
|
|
110
127
|
if ( this.disposed || ( event.target === this.host && event.request?.superContextOnly )
|
|
111
|
-
|| !event.request || typeof event.
|
|
128
|
+
|| !( typeof event.request === 'object' && event.request ) || typeof event.respondWith !== 'function' || !this.constructor.matchRequest( this.id, event.request ) ) return;
|
|
112
129
|
event.stopPropagation();
|
|
113
130
|
if ( event.type === 'contextclaim' ) {
|
|
114
131
|
const claims = new Set;
|
|
@@ -17,8 +17,8 @@ export default function() {
|
|
|
17
17
|
*/
|
|
18
18
|
respondWith( response, ...rest ) {
|
|
19
19
|
if ( this.request.diff ) {
|
|
20
|
-
if ( '
|
|
21
|
-
|
|
20
|
+
if ( 'prevValue' in this && this.prevValue === response ) return;
|
|
21
|
+
Object.defineProperty( this, 'prevValue', { value: response, configurable: true } );
|
|
22
22
|
}
|
|
23
23
|
return this.callback( response, ...rest );
|
|
24
24
|
}
|
package/src/context-api/index.js
CHANGED
|
@@ -6,6 +6,14 @@ import { _init } from '../util.js';
|
|
|
6
6
|
import HTMLContext from './HTMLContext.js';
|
|
7
7
|
import HTMLContextProvider from './HTMLContextProvider.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @exports
|
|
11
|
+
*/
|
|
12
|
+
export {
|
|
13
|
+
HTMLContextProvider,
|
|
14
|
+
HTMLContext,
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
/**
|
|
10
18
|
* Initializes HTML Modules.
|
|
11
19
|
*
|
|
@@ -19,7 +27,7 @@ export default function init( $config = {} ) {
|
|
|
19
27
|
} );
|
|
20
28
|
window.webqit.HTMLContextProvider = HTMLContextProvider;
|
|
21
29
|
window.webqit.HTMLContext = HTMLContext;
|
|
22
|
-
|
|
30
|
+
exposeAPIs.call( window, config );
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
/**
|
|
@@ -29,7 +37,7 @@ export default function init( $config = {} ) {
|
|
|
29
37
|
*
|
|
30
38
|
* @return Void
|
|
31
39
|
*/
|
|
32
|
-
function
|
|
40
|
+
function exposeAPIs( config ) {
|
|
33
41
|
const window = this;
|
|
34
42
|
// Assertions
|
|
35
43
|
if ( config.api.context in window.document ) { throw new Error( `document already has a "${ config.api.context }" property!` ); }
|
|
@@ -41,12 +49,20 @@ function exposeModulesObjects( config ) {
|
|
|
41
49
|
Object.defineProperty( window.HTMLElement.prototype, config.api.context, { get: function() {
|
|
42
50
|
return HTMLContext.instance( this );
|
|
43
51
|
} } );
|
|
52
|
+
const waitlist = new Set;
|
|
53
|
+
window.addEventListener( 'contextrequest', event => {
|
|
54
|
+
if ( !( typeof event.request === 'object' && event.request ) || typeof event.respondWith !== 'function' ) return;
|
|
55
|
+
waitlist.add( event );
|
|
56
|
+
event.respondWith();
|
|
57
|
+
} );
|
|
58
|
+
window.addEventListener( 'contextclaim', event => {
|
|
59
|
+
if ( !( typeof event.request === 'object' && event.request ) || typeof event.respondWith !== 'function' ) return;
|
|
60
|
+
const claims = new Set;
|
|
61
|
+
waitlist.forEach( subscriptionEvent => {
|
|
62
|
+
if ( !HTMLContextProvider.providers.get( event.request.type ).matchRequest( event.request/*provider ID*/, subscriptionEvent.request/*request ID*/ ) ) return;
|
|
63
|
+
waitlist.delete( subscriptionEvent );
|
|
64
|
+
claims.add( subscriptionEvent );
|
|
65
|
+
} );
|
|
66
|
+
event.respondWith( claims );
|
|
67
|
+
} );
|
|
44
68
|
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* @exports
|
|
48
|
-
*/
|
|
49
|
-
export {
|
|
50
|
-
HTMLContextProvider,
|
|
51
|
-
HTMLContext,
|
|
52
|
-
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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 AttrBracelet extends Bracelet {
|
|
10
|
+
static get query() { return `*[@*[${ this.tokens.contains }]]`; }
|
|
11
|
+
|
|
12
|
+
static parse( ...attrs ) {
|
|
13
|
+
return attrs.reduce( ( attrs, attr ) => {
|
|
14
|
+
return attrs.concat( [ ...attr.nodeValue.matchAll( new RegExp( this.tokens.regex, 'g' ) ) ].reduce( ( bracelets, match ) => {
|
|
15
|
+
const bracelet = new this( attr, match[ 0 ], match.index, match[ 1 ].trim() );
|
|
16
|
+
const prev = bracelets.slice( -1 )[ 0 ];
|
|
17
|
+
if ( prev ) { prev._nextSibling = bracelet; }
|
|
18
|
+
return bracelets.concat( bracelet );
|
|
19
|
+
}, [] ) );
|
|
20
|
+
}, [] );
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static mount( ...bracelets ) {
|
|
24
|
+
for ( const bracelet of bracelets ) {
|
|
25
|
+
// Add to attr-specific registry
|
|
26
|
+
let attrBraceletsRegistry = _( bracelet.ownerElement ).get( 'attr-bracelets' );
|
|
27
|
+
if ( !attrBraceletsRegistry ) {
|
|
28
|
+
attrBraceletsRegistry = new Map;
|
|
29
|
+
attrBraceletsRegistry.active = [];
|
|
30
|
+
_( bracelet.ownerElement ).set( 'attr-bracelets', attrBraceletsRegistry );
|
|
31
|
+
}
|
|
32
|
+
let attrBracelets = attrBraceletsRegistry.get( bracelet.attr.nodeName );
|
|
33
|
+
if ( !attrBracelets ) {
|
|
34
|
+
attrBracelets = new Set;
|
|
35
|
+
attrBraceletsRegistry.set( bracelet.attr.nodeName, attrBracelets );
|
|
36
|
+
}
|
|
37
|
+
attrBracelets.add( bracelet );
|
|
38
|
+
attrBraceletsRegistry.active[ 0 ]?._nested.add( bracelet );
|
|
39
|
+
// Add to general registry
|
|
40
|
+
HTMLBracelets.instance( bracelet.ownerElement ).add( bracelet );
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static cleanup( ...attrs ) {
|
|
45
|
+
for ( const attr of attrs ) {
|
|
46
|
+
// Remove from attr-specific registry
|
|
47
|
+
const attrBraceletsRegistry = _( attr.ownerElement ).get( 'attr-bracelets' );
|
|
48
|
+
attrBraceletsRegistry?.get( attr.nodeName )?.forEach( bracelet => {
|
|
49
|
+
bracelet.disconnect();
|
|
50
|
+
// Remove from general registry
|
|
51
|
+
HTMLBracelets.instance( bracelet.ownerElement ).delete( bracelet );
|
|
52
|
+
} );
|
|
53
|
+
attrBraceletsRegistry?.delete( attr.nodeName );
|
|
54
|
+
if ( attrBraceletsRegistry && !attrBraceletsRegistry.size ) { _( attr.ownerElement ).delete( 'attr-bracelets' ); }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor( attr, _value, startIndex, expr ) {
|
|
59
|
+
super();
|
|
60
|
+
const $refs = [], $expr = this.parseExpr( expr, $refs );
|
|
61
|
+
Object.defineProperties( this, {
|
|
62
|
+
type: { get: () => 'attr' },
|
|
63
|
+
expr: { get: () => $expr },
|
|
64
|
+
refs: { get: () => $refs },
|
|
65
|
+
attr: { get: () => attr },
|
|
66
|
+
ownerElement: { get: () => attr.ownerElement },
|
|
67
|
+
originalValue: { value: _value },
|
|
68
|
+
_value: { value: _value, writable: true },
|
|
69
|
+
_dirty: { value: false, writable: true },
|
|
70
|
+
_startIndex: { value: undefined, writable: true },
|
|
71
|
+
_endIndex: { value: undefined, writable: true },
|
|
72
|
+
_nextSibling: { value: undefined, writable: true },
|
|
73
|
+
_nested: { value: new Set },
|
|
74
|
+
} );
|
|
75
|
+
this.startIndex = startIndex;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get startIndex() { return this._startIndex; }
|
|
79
|
+
set startIndex( value ) {
|
|
80
|
+
this._startIndex = value;
|
|
81
|
+
this.endIndex = this._startIndex + this.value.length;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
get endIndex() { return this._endIndex; }
|
|
85
|
+
set endIndex( value ) {
|
|
86
|
+
if ( value === this._endIndex ) return;
|
|
87
|
+
if ( this.nextSibling ) { this.nextSibling.startIndex += value - this._endIndex; }
|
|
88
|
+
this._endIndex = value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
get value() { return this._value; }
|
|
92
|
+
set value( value ) {
|
|
93
|
+
if ( this.disconnected || value === this._value ) return;
|
|
94
|
+
this._value = value;
|
|
95
|
+
this._dirty = true;
|
|
96
|
+
// Set attribute; but first disconnect any "nested"
|
|
97
|
+
this._nested.forEach( p => p.disconnect() );
|
|
98
|
+
const attrBraceletsRegistry = _( this.ownerElement ).get( 'attr-bracelets' );
|
|
99
|
+
attrBraceletsRegistry.active.unshift( this );
|
|
100
|
+
this.ownerElement.setAttribute( this.attr.nodeName, this.attr.nodeValue.substring( 0, this.startIndex ) + value + this.attr.nodeValue.substring( this.endIndex ) );
|
|
101
|
+
attrBraceletsRegistry.active.shift();
|
|
102
|
+
// Reindex
|
|
103
|
+
const newEndIndex = this.startIndex + value.length;
|
|
104
|
+
if ( newEndIndex !== this.endIndex ) { this.endIndex = newEndIndex; }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get nextSibling() { return this._nextSibling; }
|
|
108
|
+
get dirty() { return this._dirty; }
|
|
109
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
|
|
2
|
+
export default class Bracelet {
|
|
3
|
+
static tokens = {
|
|
4
|
+
startTag: `{`, endTag: `}`,
|
|
5
|
+
get contains () { return `contains(., "${ this.startTag }") and contains(substring-after(., "${ this.startTag }"), "${ this.endTag }")`; },
|
|
6
|
+
get startsAndEnds() { return `starts-with(., "${ this.startTag }") and substring(., string-length(.) - string-length("${ this.endTag }") + 1) = "${ this.endTag }"`; },
|
|
7
|
+
get regex() {
|
|
8
|
+
const startTag = this.startTag.split( '' ).map( s => '\\' + s );
|
|
9
|
+
const endTag = this.endTag.split( '' ).map( s => '\\' + s );
|
|
10
|
+
return `${ startTag.join( '' ) }([^${ startTag[ 0 ] }]+)${ endTag.join( '' ) }`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
parseExpr( expr, refs ) {
|
|
15
|
+
const tokens = expr.match( /[\+\-\*\/&|\?\:\=\<\>\%]+|(['"])[^'"]*\1|\[[^\]]*\]|\([^)]*\)|\S+/g ).map( s => s.trim() );
|
|
16
|
+
return tokens.map( s => {
|
|
17
|
+
let meta = {};
|
|
18
|
+
if ( s[ 0 ] === '!' ) { meta = { negation: true }; s = s.slice( 1 ); }
|
|
19
|
+
if ( /^\(.*\)$/.test( s ) ) return { type: 'expr', value: this.parseExpr( s.slice( 1, -1 ), refs ), ...meta }; // must be before the operator test
|
|
20
|
+
if ( [ '"', "'" ].includes( s[ 0 ] ) ) return { type: 'literal', value: /(['"])(.*?)\1/g.exec( s )[ 2 ], ...meta }; // must be before the operator test
|
|
21
|
+
if ( /[\+\-\*\/&|\?\:\=\<\>\%]/.test( s ) ) return { type: 'operator', value: s, ...meta };
|
|
22
|
+
if ( !isNaN( s ) ) return { type: 'literal', value: parseFloat( s ), ...meta };
|
|
23
|
+
const ref = s.match( /[^\.\[\]]+/g );
|
|
24
|
+
refs.push( ref );
|
|
25
|
+
return { type: 'ref', value: ref, ...meta };
|
|
26
|
+
} );
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
renderExpr( expr, bindings ) {
|
|
30
|
+
return expr.reduce( ( [ prev, operator, state ], token ) => {
|
|
31
|
+
// No further evaluations allowed?
|
|
32
|
+
if ( state === 'end' ) return [ prev, null, 'end' ];
|
|
33
|
+
// Mode has been consequent and we've now hit the alternate block?
|
|
34
|
+
if ( state === 'consequent' && token.type === 'operator' && token.value === ':' ) return [ prev, null, 'end' ];
|
|
35
|
+
// Always return operators at this level
|
|
36
|
+
if ( token.type === 'operator' ) return [ prev, token, state ];
|
|
37
|
+
// Still expecting to hit the alternate block?
|
|
38
|
+
if ( state === 'alternate' && operator?.value !== ':' ) return [ null, null, 'alternate' ];
|
|
39
|
+
// Main...
|
|
40
|
+
let value, render = ( token, val ) => token.negation ? !val : val;
|
|
41
|
+
switch ( token.type ) {
|
|
42
|
+
case 'ref': value = render( token, bindings[ token.value.join( '.' ) ].value ); break;
|
|
43
|
+
case 'expr': value = render( token, this.renderExpr( token.value, bindings ) ); break;
|
|
44
|
+
default: value = render( token, token.value );
|
|
45
|
+
}
|
|
46
|
+
switch ( operator?.value ) {
|
|
47
|
+
case '-': return [ prev - value, null, state ];
|
|
48
|
+
case '+': return [ prev + value, null, state ];
|
|
49
|
+
case '/': return [ prev / value, null, state ];
|
|
50
|
+
case '*': return [ prev * value, null, state ];
|
|
51
|
+
case '%': return [ prev % value, null, state ];
|
|
52
|
+
case '===': return [ prev === value, null, state ];
|
|
53
|
+
case '==': return [ render( operator, prev == value ), null, state ];
|
|
54
|
+
case '>=': return [ prev >= value, null, state ];
|
|
55
|
+
case '<=': return [ prev <= value, null, state ];
|
|
56
|
+
case '>': return [ prev > value, null, state ];
|
|
57
|
+
case '<': return [ prev < value, null, state ];
|
|
58
|
+
case '||': return [ prev || value, null, state ];
|
|
59
|
+
case '&&': return [ prev && value, null, state ];
|
|
60
|
+
case '?': return prev ? [ value, null, 'consequent' ] : [ null, null, 'alternate' ];
|
|
61
|
+
case '??': return [ prev ?? value, null, state ];
|
|
62
|
+
default: return [ value ];
|
|
63
|
+
}
|
|
64
|
+
}, [ null, null ] )[ 0 ];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
render( bindings ) {
|
|
68
|
+
const value = this.renderExpr( this.expr, bindings );
|
|
69
|
+
if ( typeof value === 'undefined' ) {
|
|
70
|
+
if ( !this.dirty ) return;
|
|
71
|
+
this.value = this.originalValue;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.value = value + '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
disconnect() { this.disconnected = true; }
|
|
78
|
+
}
|