@webqit/oohtml 3.2.0 → 4.0.1
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 +189 -78
- 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/data-binding.js +15 -15
- package/dist/data-binding.js.map +3 -3
- package/dist/html-imports.js +1 -1
- package/dist/html-imports.js.map +3 -3
- package/dist/main.js +24 -24
- package/dist/main.js.map +3 -3
- package/dist/main.lite.js +25 -31
- package/dist/main.lite.js.map +3 -3
- package/dist/namespaced-html.js +1 -1
- package/dist/namespaced-html.js.map +3 -3
- package/dist/scoped-css.js +4 -4
- package/dist/scoped-css.js.map +3 -3
- package/dist/scoped-js.js +1 -1
- package/dist/scoped-js.js.map +3 -3
- package/package.json +5 -5
- package/src/data-binding/index.js +4 -4
- package/src/index.js +3 -21
- package/src/{api.global.lite.js → index.lite.js} +1 -1
- package/src/init.js +28 -0
- package/src/namespaced-html/index.js +204 -141
- package/src/scoped-css/index.js +43 -32
- package/src/scoped-js/index.js +20 -15
- package/src/util.js +11 -0
- package/src/api.global.js +0 -11
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"wicg-proposal"
|
|
15
15
|
],
|
|
16
16
|
"homepage": "https://webqit.io/tooling/oohtml",
|
|
17
|
-
"version": "
|
|
17
|
+
"version": "4.0.1",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
"scripts": {
|
|
30
30
|
"test": "mocha --extension .test.js --exit",
|
|
31
31
|
"test:coverage": "c8 --reporter=text-lcov npm run test | coveralls",
|
|
32
|
-
"build": "esbuild main=src/
|
|
33
|
-
"preversion": "npm run build && git add -A dist",
|
|
32
|
+
"build": "esbuild main=src/index.js main.lite=src/index.lite.js context-api=src/context-api/targets.browser.js bindings-api=src/bindings-api/targets.browser.js namespaced-html=src/namespaced-html/targets.browser.js html-imports=src/html-imports/targets.browser.js data-binding=src/data-binding/targets.browser.js scoped-css=src/scoped-css/targets.browser.js scoped-js=src/scoped-js/targets.browser.js --bundle --minify --sourcemap --outdir=dist",
|
|
33
|
+
"preversion": "npm test && npm run build && git add -A dist",
|
|
34
34
|
"postversion": "npm publish",
|
|
35
35
|
"postpublish": "git push && git push --tags"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@webqit/quantum-js": "^4.
|
|
39
|
-
"@webqit/realdom": "^2.1.
|
|
38
|
+
"@webqit/quantum-js": "^4.5.0",
|
|
39
|
+
"@webqit/realdom": "^2.1.23",
|
|
40
40
|
"@webqit/util": "^0.8.11"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -13,11 +13,11 @@ import { _, _init, _splitOuter } from '../util.js';
|
|
|
13
13
|
*/
|
|
14
14
|
export default function init( $config = {} ) {
|
|
15
15
|
const { config, window } = _init.call( this, 'data-binding', $config, {
|
|
16
|
-
attr: {
|
|
16
|
+
attr: { render: 'render', itemIndex: 'data-key' },
|
|
17
17
|
tokens: { nodeType: 'processing-instruction', tagStart: '?{', tagEnd: '}?', stateStart: '; [=', stateEnd: ']' },
|
|
18
18
|
} );
|
|
19
19
|
( { CONTEXT_API: config.CONTEXT_API, BINDINGS_API: config.BINDINGS_API, HTML_IMPORTS: config.HTML_IMPORTS } = window.webqit.oohtml.configs );
|
|
20
|
-
config.attrSelector = `[${ window.CSS.escape( config.attr.
|
|
20
|
+
config.attrSelector = `[${ window.CSS.escape( config.attr.render ) }]`;
|
|
21
21
|
const discreteBindingsMatch = ( start, end ) => {
|
|
22
22
|
const starting = `starts-with(., "${ start }")`;
|
|
23
23
|
const ending = `substring(., string-length(.) - string-length("${ end }") + 1) = "${ end }"`;
|
|
@@ -90,7 +90,7 @@ function cleanup( ...entries ) {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
async function mountDiscreteBindings( config, ...entries ) {
|
|
93
|
-
const window = this
|
|
93
|
+
const window = this;
|
|
94
94
|
const patternMatch = str => {
|
|
95
95
|
const tagStart = config.tokens.tagStart.split( '' ).map( x => `\\${ x }` ).join( '' );
|
|
96
96
|
const tagEnd = config.tokens.tagEnd.split( '' ).map( x => `\\${ x }` ).join( '' );
|
|
@@ -144,7 +144,7 @@ function compileDiscreteBindings( config, str ) {
|
|
|
144
144
|
|
|
145
145
|
async function mountInlineBindings( config, ...entries ) {
|
|
146
146
|
for ( const node of entries ) {
|
|
147
|
-
const compiled = compileInlineBindings( config, node.getAttribute( config.attr.
|
|
147
|
+
const compiled = compileInlineBindings( config, node.getAttribute( config.attr.render ) );
|
|
148
148
|
const { scope, bindings } = createDynamicScope.call( this, config, node );
|
|
149
149
|
const signals = [];
|
|
150
150
|
Object.defineProperty( node, '$oohtml_internal_databinding_signals', { value: signals, configurable: true } );
|
package/src/index.js
CHANGED
|
@@ -1,28 +1,10 @@
|
|
|
1
|
-
|
|
2
1
|
/**
|
|
3
2
|
* @imports
|
|
4
3
|
*/
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import ScopedJS from './scoped-js/index.js';
|
|
8
|
-
import ContextAPI from './context-api/index.js';
|
|
9
|
-
import BindingsAPI from './bindings-api/index.js';
|
|
10
|
-
import HTMLImports from './html-imports/index.js';
|
|
11
|
-
import DataBinding from './data-binding/index.js';
|
|
4
|
+
import * as QuantumJS from `@webqit/quantum-js`;
|
|
5
|
+
import init from './init.js';
|
|
12
6
|
|
|
13
7
|
/**
|
|
14
8
|
* @init
|
|
15
9
|
*/
|
|
16
|
-
|
|
17
|
-
if ( !this.webqit ) { this.webqit = {}; }
|
|
18
|
-
Object.assign( this.webqit, QuantumJS );
|
|
19
|
-
// --------------
|
|
20
|
-
ContextAPI.call( this, ( configs.CONTEXT_API || {} ) );
|
|
21
|
-
BindingsAPI.call( this, ( configs.BINDINGS_API || {} ) ); // Depends on ContextAPI
|
|
22
|
-
NamespacedHTML.call( this, ( configs.NAMESPACED_HTML || {} ) ); // Depends on ContextAPI
|
|
23
|
-
HTMLImports.call( this, ( configs.HTML_IMPORTS || {} ) ); // Depends on ContextAPI
|
|
24
|
-
DataBinding.call( this, ( configs.DATA_BINDING || {} ) ); // Depends on ContextAPI, BindingsAPI, HTMLImports
|
|
25
|
-
ScopedCSS.call( this, ( configs.SCOPED_CSS || {} ) ); // Depends on NamespacedHTML
|
|
26
|
-
ScopedJS.call( this, ( configs.SCOPED_JS || {} ) );
|
|
27
|
-
// --------------
|
|
28
|
-
}
|
|
10
|
+
init.call( window, QuantumJS );
|
package/src/init.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import NamespacedHTML from './namespaced-html/index.js';
|
|
6
|
+
import ScopedCSS from './scoped-css/index.js';
|
|
7
|
+
import ScopedJS from './scoped-js/index.js';
|
|
8
|
+
import ContextAPI from './context-api/index.js';
|
|
9
|
+
import BindingsAPI from './bindings-api/index.js';
|
|
10
|
+
import HTMLImports from './html-imports/index.js';
|
|
11
|
+
import DataBinding from './data-binding/index.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @init
|
|
15
|
+
*/
|
|
16
|
+
export default function init( QuantumJS, configs = {} ) {
|
|
17
|
+
if ( !this.webqit ) { this.webqit = {}; }
|
|
18
|
+
Object.assign( this.webqit, QuantumJS );
|
|
19
|
+
// --------------
|
|
20
|
+
ContextAPI.call( this, ( configs.CONTEXT_API || {} ) );
|
|
21
|
+
BindingsAPI.call( this, ( configs.BINDINGS_API || {} ) ); // Depends on ContextAPI
|
|
22
|
+
NamespacedHTML.call( this, ( configs.NAMESPACED_HTML || {} ) ); // Depends on ContextAPI
|
|
23
|
+
HTMLImports.call( this, ( configs.HTML_IMPORTS || {} ) ); // Depends on ContextAPI
|
|
24
|
+
DataBinding.call( this, ( configs.DATA_BINDING || {} ) ); // Depends on ContextAPI, BindingsAPI, HTMLImports
|
|
25
|
+
ScopedCSS.call( this, ( configs.SCOPED_CSS || {} ) ); // Depends on NamespacedHTML
|
|
26
|
+
ScopedJS.call( this, ( configs.SCOPED_JS || {} ) );
|
|
27
|
+
// --------------
|
|
28
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @imports
|
|
4
4
|
*/
|
|
5
5
|
import DOMNamingContext from './DOMNamingContext.js';
|
|
6
|
-
import { _, _init, _splitOuter, _fromHash, _toHash } from '../util.js';
|
|
6
|
+
import { _, _init, _splitOuter, _fromHash, _toHash, getInternalAttrInteraction, internalAttrInteraction } from '../util.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @init
|
|
@@ -15,52 +15,91 @@ export default function init( $config = {} ) {
|
|
|
15
15
|
attr: { namespace: 'namespace', lid: 'id', },
|
|
16
16
|
api: { namespace: 'namespace', },
|
|
17
17
|
tokens: { lidrefPrefix: '~', lidrefSeparator: ':' },
|
|
18
|
-
target: {
|
|
18
|
+
target: { className: ':target', eventName: ':target', scrolling: true },
|
|
19
19
|
} );
|
|
20
20
|
config.lidSelector = `[${ window.CSS.escape( config.attr.lid ) }]`;
|
|
21
21
|
config.namespaceSelector = `[${ window.CSS.escape( config.attr.namespace ) }]`;
|
|
22
|
+
config.documentNamespaceUUID = getNamespaceUUID( getOwnNamespaceObject.call( window, window.document ) );
|
|
22
23
|
window.webqit.DOMNamingContext = DOMNamingContext;
|
|
23
24
|
exposeAPIs.call( window, config );
|
|
24
25
|
realtime.call( window, config );
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* @init
|
|
30
|
+
*
|
|
31
|
+
* @param Object config
|
|
32
|
+
*
|
|
33
|
+
* @return String
|
|
34
|
+
*/
|
|
35
|
+
function lidUtil( config ) {
|
|
36
|
+
const { lidrefPrefix, lidrefSeparator, } = config.tokens;
|
|
37
|
+
return {
|
|
38
|
+
escape( str, mode = 1 ) { return [ ...str ].map( x => !/\w/.test( x ) ? ( mode === 2 ? `\\\\${ x }` : `\\${ x }` ) : x ).join( '' ); },
|
|
39
|
+
lidrefPrefix( escapeMode = 0 ) { return escapeMode ? this.escape( lidrefPrefix, escapeMode ) : lidrefPrefix; },
|
|
40
|
+
lidrefSeparator( escapeMode = 0 ) { return escapeMode ? this.escape( lidrefSeparator, escapeMode ) : lidrefSeparator; },
|
|
41
|
+
isUuid( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && str.includes( this.lidrefSeparator( escapeMode ) ); },
|
|
42
|
+
isLidref( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && !str.includes( this.lidrefSeparator( escapeMode ) ); },
|
|
43
|
+
toUuid( hash, lid, escapeMode = 0 ) { return hash === config.documentNamespaceUUID ? lid : `${ this.lidrefPrefix( escapeMode ) }${ hash }${ this.lidrefSeparator( escapeMode ) }${ lid }`; },
|
|
44
|
+
uuidToId( str, escapeMode = 0 ) { return this.isUuid( str ) ? str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] : str; },
|
|
45
|
+
uuidToLidref( str, escapeMode = 0 ) { return this.isUuid( str ) ? `${ this.lidrefPrefix( escapeMode ) }${ str.split( this.lidrefSeparator( escapeMode ) )[ 1 ] }` : str; },
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
/**
|
|
28
50
|
* @rewriteSelector
|
|
29
51
|
*
|
|
30
52
|
* @param String selectorText
|
|
53
|
+
* @param String namespaceUUID
|
|
31
54
|
* @param String scopeSelector
|
|
32
|
-
* @param Bool
|
|
55
|
+
* @param Bool escapeMode
|
|
56
|
+
*
|
|
57
|
+
* @return String
|
|
33
58
|
*/
|
|
34
|
-
export function rewriteSelector( selectorText, scopeSelector = null,
|
|
59
|
+
export function rewriteSelector( selectorText, namespaceUUID, scopeSelector = null, escapeMode = 0 ) {
|
|
35
60
|
const window = this, { webqit: { oohtml: { configs: { NAMESPACED_HTML: config } } } } = window;
|
|
36
|
-
const
|
|
61
|
+
const $lidUtil = lidUtil( config );
|
|
37
62
|
// Match :scope and relative ID selector
|
|
38
|
-
const regex = new RegExp( `${ scopeSelector ? `:scope|` : '' }
|
|
63
|
+
const regex = new RegExp( `${ scopeSelector ? `:scope|` : '' }#(${ $lidUtil.lidrefPrefix( escapeMode + 1 ) })?([\\w]+${ $lidUtil.lidrefSeparator( escapeMode + 1 ) })?((?:[\\w-]|\\\\.)+)`, 'g' );
|
|
39
64
|
// Parse potentially combined selectors individually and categorise into categories per whether they have :scope or not
|
|
40
65
|
const [ cat1, cat2 ] = _splitOuter( selectorText, ',' ).reduce( ( [ cat1, cat2 ], selector ) => {
|
|
41
66
|
// The deal: match and replace
|
|
42
67
|
let quotesMatch, hadScopeSelector;
|
|
43
|
-
selector = selector.replace( regex, ( match,
|
|
68
|
+
selector = selector.replace( regex, ( match, lidrefPrefixMatch, lidrefSeparatorMatch, id, index ) => {
|
|
44
69
|
if ( !quotesMatch ) { // Lazy: stuff
|
|
45
70
|
// Match strings between quotes (single or double) and use that qualify matches above
|
|
46
71
|
// The string: String.raw`She said, "Hello, John. I\"m your friend." or "you're he're" 'f\'"j\'"f'jfjf`;
|
|
47
72
|
// Should yield: `"Hello, John. I\\"m your friend."`, `"you're he're"`, `'f\\'"j\\'"f'`
|
|
48
73
|
quotesMatch = [ ...selector.matchAll( /(["'])(?:(?=(\\?))\2.)*?\1/g ) ];
|
|
49
74
|
}
|
|
75
|
+
if ( quotesMatch[ 0 ] )
|
|
50
76
|
// Qualify match
|
|
51
|
-
if ( quotesMatch.some( q => index > q.index && index + match.length < q.index +
|
|
77
|
+
if ( quotesMatch.some( q => index > q.index && index + match.length < q.index + q[ 0 ].length ) ) return match;
|
|
52
78
|
// Replace :scope
|
|
53
79
|
if ( match === ':scope' ) {
|
|
54
80
|
hadScopeSelector = true;
|
|
55
81
|
return scopeSelector;
|
|
56
82
|
}
|
|
57
|
-
|
|
58
|
-
const
|
|
83
|
+
const isLidref = lidrefPrefixMatch && !lidrefSeparatorMatch;
|
|
84
|
+
const isUuid = lidrefPrefixMatch && lidrefSeparatorMatch;
|
|
85
|
+
if ( isUuid ) {
|
|
86
|
+
return `#${ $lidUtil.escape( match.replace( '#', '' ), 1 ) }`;
|
|
87
|
+
}
|
|
88
|
+
// Rewrite relative ID selector
|
|
89
|
+
if ( isLidref ) {
|
|
90
|
+
if ( config.attr.lid === 'id' && namespaceUUID && namespaceUUID !== config.documentNamespaceUUID ) {
|
|
91
|
+
return `#${ $lidUtil.toUuid( namespaceUUID, id, 1 ) }`;
|
|
92
|
+
}
|
|
93
|
+
// Fallback to attr-based
|
|
94
|
+
}
|
|
95
|
+
// Rewrite absolute ID selector
|
|
96
|
+
let rewrite;
|
|
59
97
|
if ( config.attr.lid === 'id' ) {
|
|
60
|
-
|
|
98
|
+
rewrite = `:is(#${ id },[id^="${ $lidUtil.lidrefPrefix( escapeMode ) }"][id$="${ $lidUtil.lidrefSeparator( escapeMode ) }${ id }"])`;
|
|
61
99
|
} else {
|
|
62
|
-
|
|
100
|
+
rewrite = `:is(#${ id },[${ window.CSS.escape( config.attr.lid ) }="${ id }"])`;
|
|
63
101
|
}
|
|
102
|
+
return isLidref ? `:is(${ rewrite }):not(${ scopeSelector ? scopeSelector + ' ' : '' }[${ config.attr.namespace }] *)` : rewrite;
|
|
64
103
|
} );
|
|
65
104
|
// Category 2 has :scope and category 1 does not
|
|
66
105
|
return hadScopeSelector ? [ cat1, cat2.concat( selector ) ] : [ cat1.concat( selector ), cat2 ];
|
|
@@ -76,13 +115,11 @@ export function rewriteSelector( selectorText, scopeSelector = null, isCssSelect
|
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
/**
|
|
79
|
-
* Returns the "namespace" object associated with the given node.
|
|
80
|
-
*
|
|
81
118
|
* @param Element node
|
|
82
119
|
*
|
|
83
120
|
* @return Object
|
|
84
121
|
*/
|
|
85
|
-
function
|
|
122
|
+
export function getOwnNamespaceObject( node ) {
|
|
86
123
|
if ( !_( node ).has( 'namespace' ) ) {
|
|
87
124
|
const namespaceObj = Object.create( null );
|
|
88
125
|
_( node ).set( 'namespace', namespaceObj );
|
|
@@ -90,6 +127,26 @@ function getNamespaceObject( node ) {
|
|
|
90
127
|
return _( node ).get( 'namespace' );
|
|
91
128
|
}
|
|
92
129
|
|
|
130
|
+
/**
|
|
131
|
+
* @param Element node
|
|
132
|
+
* @param Bool forID
|
|
133
|
+
*
|
|
134
|
+
* @return Object
|
|
135
|
+
*/
|
|
136
|
+
export function getOwnerNamespaceObject( node, forID = false ) {
|
|
137
|
+
const window = this, { webqit: { oohtml: { configs: { NAMESPACED_HTML: config } } } } = window;
|
|
138
|
+
return getOwnNamespaceObject( node instanceof window.Document ? node : ( ( forID ? node.parentNode : node )?.closest( `[${ config.attr.namespace }]` ) || node.ownerDocument ) );
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param Object namespaceObj
|
|
143
|
+
*
|
|
144
|
+
* @return String
|
|
145
|
+
*/
|
|
146
|
+
export function getNamespaceUUID( namespaceObj ) {
|
|
147
|
+
return _fromHash( namespaceObj ) || _toHash( namespaceObj );
|
|
148
|
+
}
|
|
149
|
+
|
|
93
150
|
/**
|
|
94
151
|
* Exposes Namespaced HTML with native APIs.
|
|
95
152
|
*
|
|
@@ -104,10 +161,10 @@ function exposeAPIs( config ) {
|
|
|
104
161
|
if ( config.api.namespace in window.Element.prototype ) { throw new Error( `The "Element" class already has a "${ config.api.namespace }" property!` ); }
|
|
105
162
|
// Definitions
|
|
106
163
|
Object.defineProperty( window.document, config.api.namespace, { get: function() {
|
|
107
|
-
return Observer.proxy(
|
|
164
|
+
return Observer.proxy( getOwnNamespaceObject.call( window, window.document ) );
|
|
108
165
|
} });
|
|
109
166
|
Object.defineProperty( window.Element.prototype, config.api.namespace, { get: function() {
|
|
110
|
-
return Observer.proxy(
|
|
167
|
+
return Observer.proxy( getOwnNamespaceObject.call( window, this ) );
|
|
111
168
|
} } );
|
|
112
169
|
}
|
|
113
170
|
|
|
@@ -120,25 +177,6 @@ function exposeAPIs( config ) {
|
|
|
120
177
|
*/
|
|
121
178
|
function realtime( config ) {
|
|
122
179
|
const window = this, { webqit: { Observer, realdom, oohtml: { configs }, DOMNamingContext } } = window;
|
|
123
|
-
const { lidrefPrefix, lidrefSeparator } = config.tokens;
|
|
124
|
-
|
|
125
|
-
// ------------
|
|
126
|
-
// NAMESPACE
|
|
127
|
-
// ------------
|
|
128
|
-
window.document[ configs.CONTEXT_API.api.contexts ].attach( new DOMNamingContext );
|
|
129
|
-
realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( config.namespaceSelector, record => {
|
|
130
|
-
record.exits.forEach( entry => {
|
|
131
|
-
const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
|
|
132
|
-
const ctx = contextsApi.find( DOMNamingContext.kind );
|
|
133
|
-
if ( ctx ) { contextsApi.detach( ctx ); }
|
|
134
|
-
} );
|
|
135
|
-
record.entrants.forEach( entry => {
|
|
136
|
-
const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
|
|
137
|
-
if ( !contextsApi.find( DOMNamingContext.kind ) ) {
|
|
138
|
-
contextsApi.attach( new DOMNamingContext );
|
|
139
|
-
}
|
|
140
|
-
} );
|
|
141
|
-
}, { live: true, timing: 'sync', staticSensitivity: true } );
|
|
142
180
|
|
|
143
181
|
// ------------
|
|
144
182
|
// APIS
|
|
@@ -149,48 +187,62 @@ function realtime( config ) {
|
|
|
149
187
|
const idRefAttrs = [ 'for', 'list', 'form', 'aria-activedescendant', 'aria-details', 'aria-errormessage', ];
|
|
150
188
|
const attrList = [ config.attr.lid, ...idRefsAttrs, ...idRefAttrs ];
|
|
151
189
|
const relMap = { id: 'id'/* just in case it's in attrList */, for: 'htmlFor', 'aria-owns': 'ariaOwns', 'aria-controls': 'ariaControls', 'aria-labelledby': 'ariaLabelledBy', 'aria-describedby': 'ariaDescribedBy', 'aria-flowto': 'ariaFlowto', 'aria-activedescendant': 'ariaActiveDescendant', 'aria-details': 'ariaDetails', 'aria-errormessage': 'ariaErrorMessage' };
|
|
152
|
-
const
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
190
|
+
const $lidUtil = lidUtil( config );
|
|
191
|
+
const uuidsToLidrefs = ( node, attrName, getter ) => {
|
|
192
|
+
if ( !getInternalAttrInteraction( node, attrName ) && _( node, 'attrOriginals' ).has( attrName ) ) {
|
|
193
|
+
return _( node, 'attrOriginals' ).get( attrName );
|
|
194
|
+
}
|
|
195
|
+
const value = getter();
|
|
196
|
+
if ( getInternalAttrInteraction( node, attrName ) ) return value;
|
|
197
|
+
return value && value.split( ' ' ).map( x => ( x = x.trim() ) && ( attrName === config.attr.lid ? $lidUtil.uuidToId : $lidUtil.uuidToLidref ).call( $lidUtil, x ) ).join( ' ' );
|
|
198
|
+
};
|
|
157
199
|
|
|
158
|
-
// Intercept getAttribute()
|
|
159
|
-
const getAttribute = Object.getOwnPropertyDescriptor( window.Element.prototype, 'getAttribute' );
|
|
160
|
-
Object.defineProperty( window.Element.prototype, 'getAttribute', { ...getAttribute, value( attrName ) {
|
|
161
|
-
const value = getAttribute.value.call( this, attrName );
|
|
162
|
-
return !_( this, 'lock' ).get( attrName ) && attrList.includes( attrName ) ? ( attrName === 'id' ? uuidToLid : uuidToLidref )( value ) : value;
|
|
163
|
-
} } );
|
|
164
200
|
// Intercept getElementById()
|
|
165
|
-
const
|
|
166
|
-
Object.defineProperty( window.Document.prototype, 'getElementById', { ...
|
|
167
|
-
|
|
168
|
-
const node = this.querySelector( rewriteSelector.call( window, `#${ id }` ) );
|
|
169
|
-
return node;// !node?.closest( config.namespaceSelector ) ? node : null; // Cool, but not consistent with querySelector(All)() results
|
|
201
|
+
const getElementByIdDescr = Object.getOwnPropertyDescriptor( window.Document.prototype, 'getElementById' );
|
|
202
|
+
Object.defineProperty( window.Document.prototype, 'getElementById', { ...getElementByIdDescr, value( id ) {
|
|
203
|
+
return this.querySelector( `#${ id }` ); // To be rewritten at querySelector()
|
|
170
204
|
} } );
|
|
171
205
|
// Intercept querySelector() and querySelectorAll()
|
|
172
206
|
for ( const queryApi of [ 'querySelector', 'querySelectorAll' ] ) {
|
|
173
207
|
for ( nodeApi of [ window.Document, window.Element ] ) {
|
|
174
|
-
const
|
|
175
|
-
Object.defineProperty( nodeApi.prototype, queryApi, { ...
|
|
176
|
-
return
|
|
208
|
+
const querySelectorDescr = Object.getOwnPropertyDescriptor( nodeApi.prototype, queryApi );
|
|
209
|
+
Object.defineProperty( nodeApi.prototype, queryApi, { ...querySelectorDescr, value( selector ) {
|
|
210
|
+
return querySelectorDescr.value.call( this, rewriteSelector.call( window, selector, getNamespaceUUID( getOwnNamespaceObject.call( window, this ) ) ) );
|
|
177
211
|
} } );
|
|
178
212
|
}
|
|
179
213
|
}
|
|
214
|
+
// Intercept getAttribute()
|
|
215
|
+
const getAttributeDescr = Object.getOwnPropertyDescriptor( window.Element.prototype, 'getAttribute' );
|
|
216
|
+
Object.defineProperty( window.Element.prototype, 'getAttribute', { ...getAttributeDescr, value( attrName ) {
|
|
217
|
+
const getter = () => getAttributeDescr.value.call( this, attrName );
|
|
218
|
+
return attrList.includes( attrName ) && !_( this, 'lock' ).get( attrName ) ? uuidsToLidrefs( this, attrName, getter ) : getter();
|
|
219
|
+
} } );
|
|
220
|
+
// Hide implementation details on the Attr node too.
|
|
221
|
+
const propertyDescr = Object.getOwnPropertyDescriptor( window.Attr.prototype, 'value' );
|
|
222
|
+
Object.defineProperty( window.Attr.prototype, 'value', { ...propertyDescr, get() {
|
|
223
|
+
const getter = () => propertyDescr.get.call( this );
|
|
224
|
+
return attrList.includes( this.name ) ? uuidsToLidrefs( this.ownerElement, this.name, getter ) : getter();
|
|
225
|
+
} } );
|
|
226
|
+
const propertyDescr2 = Object.getOwnPropertyDescriptor( window.Node.prototype, 'nodeValue' );
|
|
227
|
+
Object.defineProperty( window.Node.prototype, 'nodeValue', { ...propertyDescr2, get() {
|
|
228
|
+
const getter = () => propertyDescr2.get.call( this );
|
|
229
|
+
return this instanceof window.Attr && attrList.includes( this.name ) ? uuidsToLidrefs( this.ownerElement, this.name, getter ) : getter();
|
|
230
|
+
} } );
|
|
180
231
|
// These APIs should return LIDREFS minus the hash part
|
|
181
232
|
for ( const attrName of attrList ) {
|
|
182
233
|
if ( !( attrName in relMap ) ) continue;
|
|
183
234
|
const domApis = attrName === 'for' ? [ window.HTMLLabelElement, window.HTMLOutputElement ] : [ window.Element ];
|
|
184
235
|
for ( const domApi of domApis ) {
|
|
185
|
-
const
|
|
186
|
-
if ( !
|
|
187
|
-
Object.defineProperty( domApi.prototype, relMap[ attrName ], { ...
|
|
188
|
-
|
|
236
|
+
const propertyDescr = Object.getOwnPropertyDescriptor( domApi.prototype, relMap[ attrName ] );
|
|
237
|
+
if ( !propertyDescr ) continue;
|
|
238
|
+
Object.defineProperty( domApi.prototype, relMap[ attrName ], { ...propertyDescr, get() {
|
|
239
|
+
const getter = () => propertyDescr.get.call( this, attrName );
|
|
240
|
+
return uuidsToLidrefs( this, attrName, getter );
|
|
189
241
|
} } );
|
|
190
242
|
}
|
|
191
243
|
}
|
|
192
|
-
// Reflect the custom [config.attr.lid] attribute
|
|
193
244
|
if ( config.attr.lid !== 'id' ) {
|
|
245
|
+
// Reflect the custom [config.attr.lid] attribute
|
|
194
246
|
Object.defineProperty( window.Element.prototype, config.attr.lid, { configurable: true, enumerable: true, get() {
|
|
195
247
|
return this.getAttribute( config.attr.lid );
|
|
196
248
|
}, set( value ) {
|
|
@@ -201,94 +253,105 @@ function realtime( config ) {
|
|
|
201
253
|
// ------------
|
|
202
254
|
// LOCAL IDS & IDREFS
|
|
203
255
|
// ------------
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if ( typeof oldValue === 'function' ) oldValue = oldValue();
|
|
210
|
-
// Get down to work
|
|
211
|
-
const namespaceObj = _( entry ).get( 'ownerNamespace' );
|
|
212
|
-
if ( attrName === config.attr.lid ) {
|
|
213
|
-
const lid = uuidToLid( oldValue );
|
|
214
|
-
if ( Observer.get( namespaceObj, lid ) === entry ) { Observer.deleteProperty( namespaceObj, lid ); }
|
|
215
|
-
} else {
|
|
216
|
-
const newAttrValue = oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && uuidToLidref( lid ) ).join( ' ' );
|
|
217
|
-
entry.setAttribute( attrName, newAttrValue );
|
|
218
|
-
}
|
|
219
|
-
// Release locking
|
|
220
|
-
_( entry, 'lock' ).delete( attrName );
|
|
256
|
+
const attrChange = ( entry, attrName, value, callback ) => {
|
|
257
|
+
return internalAttrInteraction( entry, attrName, () => {
|
|
258
|
+
if ( typeof value === 'function' ) value = value();
|
|
259
|
+
return callback( value );
|
|
260
|
+
} );
|
|
221
261
|
};
|
|
222
|
-
const setupBinding = ( entry, attrName, value ) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
262
|
+
const setupBinding = ( entry, attrName, value, newNamespaceObj = null ) => {
|
|
263
|
+
attrChange( entry, attrName, value, value => {
|
|
264
|
+
const isLidAttr = attrName === config.attr.lid;
|
|
265
|
+
const namespaceObj = newNamespaceObj || getOwnerNamespaceObject( entry, isLidAttr );
|
|
266
|
+
const namespaceUUID = getNamespaceUUID( namespaceObj );
|
|
267
|
+
if ( isLidAttr ) {
|
|
268
|
+
const id = $lidUtil.uuidToId( value );
|
|
269
|
+
if ( Observer.get( namespaceObj, id ) !== entry ) {
|
|
270
|
+
const uuid = $lidUtil.toUuid( namespaceUUID, id );
|
|
271
|
+
if ( uuid !== value ) { entry.setAttribute( 'id', uuid ); }
|
|
272
|
+
Observer.set( namespaceObj, id, entry );
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
_( entry, 'attrOriginals' ).set( attrName, value ); // Save original before rewrite
|
|
276
|
+
const newAttrValue = value.split( ' ' ).map( idref => ( idref = idref.trim() ) && !$lidUtil.isLidref( idref ) ? idref : $lidUtil.toUuid( namespaceUUID, idref.replace( $lidUtil.lidrefPrefix(), '' ) ) ).join( ' ' );
|
|
277
|
+
entry.setAttribute( attrName, newAttrValue );
|
|
278
|
+
_( namespaceObj ).set( 'idrefs', _( namespaceObj ).get( 'idrefs' ) || new Set );
|
|
279
|
+
_( namespaceObj ).get( 'idrefs' ).add( entry );
|
|
237
280
|
}
|
|
238
|
-
}
|
|
239
|
-
const newAttrValue = value.split( ' ' ).map( lid => ( lid = lid.trim() ) && !isLidref( lid ) ? lid : toUuid( namespaceUUID, lid.replace( lidrefPrefix, '' ) ) ).join( ' ' );
|
|
240
|
-
entry.setAttribute( attrName, newAttrValue );
|
|
241
|
-
}
|
|
242
|
-
// Release locking
|
|
243
|
-
_( entry, 'lock' ).delete( attrName );
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const cleanupAllBindings = ( entry, total = true ) => {
|
|
247
|
-
for ( const attrName of attrList ) {
|
|
248
|
-
if ( !entry.hasAttribute( attrName ) ) continue;
|
|
249
|
-
cleanupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
|
|
250
|
-
}
|
|
251
|
-
_( entry ).delete( 'ownerNamespace' );
|
|
252
|
-
_( entry ).delete( 'namespaceUUID' );
|
|
253
|
-
if ( total ) {
|
|
254
|
-
_( entry ).get( 'namespaceBinding' ).abort();
|
|
255
|
-
_( entry ).delete( 'namespaceBinding' );
|
|
256
|
-
}
|
|
281
|
+
} );
|
|
257
282
|
};
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
283
|
+
const cleanupBinding = ( entry, attrName, oldValue, prevNamespaceObj = null ) => {
|
|
284
|
+
attrChange( entry, attrName, oldValue, oldValue => {
|
|
285
|
+
const isLidAttr = attrName === config.attr.lid;
|
|
286
|
+
const namespaceObj = prevNamespaceObj || getOwnerNamespaceObject( entry, isLidAttr );
|
|
287
|
+
if ( isLidAttr ) {
|
|
288
|
+
const id = $lidUtil.uuidToId( oldValue );
|
|
289
|
+
if ( Observer.get( namespaceObj, id ) === entry ) {
|
|
290
|
+
Observer.deleteProperty( namespaceObj, id );
|
|
265
291
|
}
|
|
266
|
-
|
|
267
|
-
_( entry ).
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
for ( const attrName of attrList ) {
|
|
275
|
-
if ( !entry.hasAttribute( attrName ) ) continue;
|
|
276
|
-
setupBinding( entry, attrName, () => entry.getAttribute( attrName ) );
|
|
277
|
-
}
|
|
292
|
+
} else {
|
|
293
|
+
const newAttrValue = _( entry, 'attrOriginals' ).get( attrName );// oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
|
|
294
|
+
entry.setAttribute( attrName, newAttrValue );
|
|
295
|
+
_( namespaceObj ).get( 'idrefs' ).delete( entry );
|
|
296
|
+
}
|
|
297
|
+
} );
|
|
278
298
|
};
|
|
279
299
|
|
|
300
|
+
// ------------
|
|
301
|
+
// NAMESPACE
|
|
302
|
+
// ------------
|
|
303
|
+
window.document[ configs.CONTEXT_API.api.contexts ].attach( new DOMNamingContext );
|
|
304
|
+
realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( config.namespaceSelector, record => {
|
|
305
|
+
const reAssociate = ( entry, attrName, oldNamespaceObj, newNamespaceObj ) => {
|
|
306
|
+
if ( !entry.hasAttribute( attrName ) ) return;
|
|
307
|
+
const attrValue = () => entry.getAttribute( attrName );
|
|
308
|
+
cleanupBinding( entry, attrName, attrValue/* Current resolved value as-is */, oldNamespaceObj );
|
|
309
|
+
if ( entry.isConnected ) { setupBinding( entry, attrName, _( entry, 'attrOriginals' ).get( attrName )/* Saved original value */ || attrValue/* Lest it's ID */, newNamespaceObj ); }
|
|
310
|
+
};
|
|
311
|
+
record.exits.forEach( entry => {
|
|
312
|
+
const namespaceObj = getOwnNamespaceObject( entry );
|
|
313
|
+
// Detach ID and IDREF associations
|
|
314
|
+
for ( const node of new Set( [ ...Object.values( namespaceObj ), ..._( namespaceObj ).get( 'idrefs' ) ] ) ) {
|
|
315
|
+
for ( const attrName of attrList ) { reAssociate( node, attrName, namespaceObj ); }
|
|
316
|
+
}
|
|
317
|
+
// Detach ID associations
|
|
318
|
+
const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
|
|
319
|
+
const ctx = contextsApi.find( DOMNamingContext.kind );
|
|
320
|
+
// Detach namespace instance
|
|
321
|
+
if ( ctx ) { contextsApi.detach( ctx ); }
|
|
322
|
+
} );
|
|
323
|
+
record.entrants.forEach( entry => {
|
|
324
|
+
// Claim ID and IDREF associations
|
|
325
|
+
let newSuperNamespaceObj;
|
|
326
|
+
const superNamespaceObj = getOwnerNamespaceObject( entry, true );
|
|
327
|
+
for ( const node of new Set( [ ...Object.values( superNamespaceObj ), ...( _( superNamespaceObj ).get( 'idrefs' ) || [] ) ] ) ) {
|
|
328
|
+
if ( ( newSuperNamespaceObj = getOwnerNamespaceObject( node, true ) ) === superNamespaceObj ) continue;
|
|
329
|
+
for ( const attrName of attrList ) { reAssociate( node, attrName, superNamespaceObj, newSuperNamespaceObj ); }
|
|
330
|
+
}
|
|
331
|
+
// Attach namespace instance
|
|
332
|
+
const contextsApi = entry[ configs.CONTEXT_API.api.contexts ];
|
|
333
|
+
if ( !contextsApi.find( DOMNamingContext.kind ) ) { contextsApi.attach( new DOMNamingContext ); }
|
|
334
|
+
} );
|
|
335
|
+
}, { live: true, timing: 'sync', staticSensitivity: true } );
|
|
336
|
+
|
|
280
337
|
// DOM realtime
|
|
281
338
|
realdom.realtime( window.document ).subtree/*instead of observe(); reason: jsdom timing*/( `[${ attrList.map( attrName => window.CSS.escape( attrName ) ).join( '],[' ) }]`, record => {
|
|
282
|
-
|
|
283
|
-
|
|
339
|
+
for ( const attrName of attrList ) {
|
|
340
|
+
record.exits.forEach( entry => {
|
|
341
|
+
if ( !entry.hasAttribute( attrName ) ) return;
|
|
342
|
+
cleanupBinding( entry, attrName, () => entry.getAttribute( attrName )/* Current resolved value as-is */ );
|
|
343
|
+
} );
|
|
344
|
+
record.entrants.forEach( entry => {
|
|
345
|
+
if ( !entry.hasAttribute( attrName ) ) return;
|
|
346
|
+
setupBinding( entry, attrName, () => entry.getAttribute( attrName )/* Raw value (as-is) that will be saved as original */ );
|
|
347
|
+
} );
|
|
348
|
+
}
|
|
284
349
|
}, { live: true, timing: 'sync' } );
|
|
285
350
|
// Attr realtime
|
|
286
351
|
realdom.realtime( window.document, 'attr' ).observe( attrList, records => {
|
|
287
352
|
for ( const record of records ) {
|
|
288
|
-
if ( record.oldValue ) {
|
|
289
|
-
|
|
290
|
-
if ( record.value ) { setupBinding( record.target, record.name, record.value ); }
|
|
291
|
-
} else { setupAllBindings( entry ); }
|
|
353
|
+
if ( record.oldValue ) { cleanupBinding( record.target, record.name, record.oldValue/* Current resolved value as-is */ ); }
|
|
354
|
+
if ( record.value ) { setupBinding( record.target, record.name, record.value/* Raw value (as-is) that will be saved as original */ ); }
|
|
292
355
|
}
|
|
293
356
|
}, { subtree: true, timing: 'sync', newValue: true, oldValue: true } );
|
|
294
357
|
|
|
@@ -297,13 +360,13 @@ function realtime( config ) {
|
|
|
297
360
|
// ------------
|
|
298
361
|
let prevTarget;
|
|
299
362
|
const activateTarget = () => {
|
|
300
|
-
if ( !window.location.hash?.startsWith( `#${ lidrefPrefix }` ) ) return;
|
|
301
|
-
const path = window.location.hash?.substring( `#${ lidrefPrefix }`.length ).split( '/' ).map( s => s.trim() ).filter( s => s ) || [];
|
|
363
|
+
if ( !window.location.hash?.startsWith( `#${ $lidUtil.lidrefPrefix() }` ) ) return;
|
|
364
|
+
const path = window.location.hash?.substring( `#${ $lidUtil.lidrefPrefix() }`.length ).split( '/' ).map( s => s.trim() ).filter( s => s ) || [];
|
|
302
365
|
const currTarget = path.reduce( ( prev, segment ) => prev && prev[ config.api.namespace ][ segment ], window.document );
|
|
303
|
-
if ( prevTarget && config.target.
|
|
366
|
+
if ( prevTarget && config.target.className ) { prevTarget.classList.toggle( config.target.className, false ); }
|
|
304
367
|
if ( currTarget && currTarget !== window.document ) {
|
|
305
|
-
if ( config.target.
|
|
306
|
-
if ( config.target.
|
|
368
|
+
if ( config.target.className ) { currTarget.classList.toggle( config.target.className, true ); }
|
|
369
|
+
if ( config.target.eventName ) { currTarget.dispatchEvent( new window.CustomEvent( config.target.eventName ) ); }
|
|
307
370
|
if ( config.target.scrolling && path.length > 1 ) { currTarget.scrollIntoView(); }
|
|
308
371
|
prevTarget = currTarget;
|
|
309
372
|
}
|