@webqit/oohtml 5.0.0 → 5.0.2
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 +42 -35
- package/dist/bindings-api.js +1 -1
- package/dist/bindings-api.js.map +1 -1
- package/dist/context-api.js.map +1 -1
- package/dist/data-binding.js +14 -14
- package/dist/data-binding.js.map +2 -2
- package/dist/html-imports.js +1 -1
- package/dist/html-imports.js.map +2 -2
- package/dist/main.js +29 -29
- package/dist/main.js.map +2 -2
- package/dist/main.lite.js +50 -42
- package/dist/main.lite.js.map +3 -3
- package/dist/namespaced-html.js +1 -1
- package/dist/namespaced-html.js.map +2 -2
- package/dist/scoped-css.js +2 -2
- package/dist/scoped-css.js.map +2 -2
- package/dist/scoped-js.js.map +1 -1
- package/package.json +1 -1
- package/src/data-binding/index.js +6 -7
- package/src/html-imports/index.js +0 -1
- package/src/index.lite.js +1 -1
- package/src/namespaced-html/index.js +238 -219
- package/src/util.js +4 -4
- package/test/ii.html +0 -11
- package/test/ii2.html +0 -24
|
@@ -10,18 +10,18 @@ import { _wq, _init, _splitOuter, _fromHash, _toHash, getInternalAttrInteraction
|
|
|
10
10
|
*
|
|
11
11
|
* @param Object $config
|
|
12
12
|
*/
|
|
13
|
-
export default function init(
|
|
14
|
-
|
|
13
|
+
export default function init($config = {}) {
|
|
14
|
+
const { config, window } = _init.call(this, 'namespaced-html', $config, {
|
|
15
15
|
attr: { namespace: 'namespace', lid: 'id', },
|
|
16
16
|
api: { namespace: 'namespace', },
|
|
17
17
|
tokens: { lidrefPrefix: '~', lidrefSeparator: ':' },
|
|
18
18
|
target: { className: ':target', eventName: ':target', scrolling: true },
|
|
19
|
-
|
|
20
|
-
config.lidSelector = `[${
|
|
21
|
-
config.namespaceSelector = `[${
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
});
|
|
20
|
+
config.lidSelector = `[${window.CSS.escape(config.attr.lid)}]`;
|
|
21
|
+
config.namespaceSelector = `[${window.CSS.escape(config.attr.namespace)}]`;
|
|
22
|
+
window.webqit.DOMNamingContext = DOMNamingContext;
|
|
23
|
+
exposeAPIs.call(window, config);
|
|
24
|
+
realtime.call(window, config);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -31,17 +31,17 @@ export default function init( $config = {} ) {
|
|
|
31
31
|
*
|
|
32
32
|
* @return String
|
|
33
33
|
*/
|
|
34
|
-
function lidUtil(
|
|
34
|
+
function lidUtil(config) {
|
|
35
35
|
const { lidrefPrefix, lidrefSeparator, } = config.tokens;
|
|
36
36
|
return {
|
|
37
|
-
escape(
|
|
38
|
-
lidrefPrefix(
|
|
39
|
-
lidrefSeparator(
|
|
40
|
-
isUuid(
|
|
37
|
+
escape(str, mode = 1) { return [...str].map(x => !/\w/.test(x) ? (mode === 2 ? `\\\\${x}` : `\\${x}`) : x).join(''); },
|
|
38
|
+
lidrefPrefix(escapeMode = 0) { return escapeMode ? this.escape(lidrefPrefix, escapeMode) : lidrefPrefix; },
|
|
39
|
+
lidrefSeparator(escapeMode = 0) { return escapeMode ? this.escape(lidrefSeparator, escapeMode) : lidrefSeparator; },
|
|
40
|
+
isUuid(str, escapeMode = 0) { return str.startsWith(this.lidrefPrefix(escapeMode)) && str.includes(this.lidrefSeparator(escapeMode)); },
|
|
41
41
|
//isLidref( str, escapeMode = 0 ) { return str.startsWith( this.lidrefPrefix( escapeMode ) ) && !str.includes( this.lidrefSeparator( escapeMode ) ); },
|
|
42
|
-
toUuid(
|
|
43
|
-
uuidToId(
|
|
44
|
-
uuidToLidref(
|
|
42
|
+
toUuid(hash, lid, escapeMode = 0) { return hash.endsWith('-root') ? lid : `${this.lidrefPrefix(escapeMode)}${hash}${this.lidrefSeparator(escapeMode)}${lid}`; },
|
|
43
|
+
uuidToId(str, escapeMode = 0) { return this.isUuid(str) ? str.split(this.lidrefSeparator(escapeMode))[1] : str; },
|
|
44
|
+
uuidToLidref(str, escapeMode = 0) { return this.isUuid(str) ? `${this.lidrefPrefix(escapeMode)}${str.split(this.lidrefSeparator(escapeMode))[1]}` : str; },
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -55,59 +55,59 @@ function lidUtil( config ) {
|
|
|
55
55
|
*
|
|
56
56
|
* @return String
|
|
57
57
|
*/
|
|
58
|
-
export function rewriteSelector(
|
|
58
|
+
export function rewriteSelector(selectorText, namespaceUUID, scopeSelector = null, escapeMode = 0) {
|
|
59
59
|
const window = this, { webqit: { oohtml: { configs: { NAMESPACED_HTML: config } } } } = window;
|
|
60
|
-
const $lidUtil = lidUtil(
|
|
60
|
+
const $lidUtil = lidUtil(config);
|
|
61
61
|
// Match :scope and relative ID selector
|
|
62
|
-
const regex = new RegExp(
|
|
62
|
+
const regex = new RegExp(`${scopeSelector ? `:scope|` : ''}#(${$lidUtil.lidrefPrefix(escapeMode + 1)})?([\\w]+${$lidUtil.lidrefSeparator(escapeMode + 1)})?((?:[\\w-]|\\\\.)+)`, 'g');
|
|
63
63
|
// Parse potentially combined selectors individually and categorise into categories per whether they have :scope or not
|
|
64
|
-
const [
|
|
64
|
+
const [cat1, cat2] = _splitOuter(selectorText, ',').reduce(([cat1, cat2], selector) => {
|
|
65
65
|
// The deal: match and replace
|
|
66
66
|
let quotesMatch, hadScopeSelector;
|
|
67
|
-
selector = selector.replace(
|
|
68
|
-
if (
|
|
67
|
+
selector = selector.replace(regex, (match, lidrefPrefixMatch, lidrefSeparatorMatch, id, index) => {
|
|
68
|
+
if (!quotesMatch) { // Lazy: stuff
|
|
69
69
|
// Match strings between quotes (single or double) and use that qualify matches above
|
|
70
70
|
// The string: String.raw`She said, "Hello, John. I\"m your friend." or "you're he're" 'f\'"j\'"f'jfjf`;
|
|
71
71
|
// Should yield: `"Hello, John. I\\"m your friend."`, `"you're he're"`, `'f\\'"j\\'"f'`
|
|
72
|
-
quotesMatch = [
|
|
72
|
+
quotesMatch = [...selector.matchAll(/(["'])(?:(?=(\\?))\2.)*?\1/g)];
|
|
73
73
|
}
|
|
74
74
|
// Qualify match
|
|
75
|
-
if (
|
|
75
|
+
if (quotesMatch.some(q => index > q.index && index + match.length < q.index + q[0].length)) return match;
|
|
76
76
|
// Replace :scope
|
|
77
|
-
if (
|
|
77
|
+
if (match === ':scope') {
|
|
78
78
|
hadScopeSelector = true;
|
|
79
79
|
return scopeSelector;
|
|
80
80
|
}
|
|
81
81
|
const isLidref = lidrefPrefixMatch && !lidrefSeparatorMatch;
|
|
82
|
-
const isUuid = lidrefPrefixMatch && lidrefSeparatorMatch;
|
|
83
|
-
if (
|
|
84
|
-
return `#${
|
|
82
|
+
const isUuid = lidrefPrefixMatch && lidrefSeparatorMatch;
|
|
83
|
+
if (isUuid) {
|
|
84
|
+
return `#${$lidUtil.escape(match.replace('#', ''), 1)}`;
|
|
85
85
|
}
|
|
86
86
|
// Rewrite relative ID selector
|
|
87
|
-
if (
|
|
88
|
-
if (
|
|
89
|
-
return `#${
|
|
87
|
+
if (isLidref) {
|
|
88
|
+
if (config.attr.lid === 'id' && namespaceUUID && !namespaceUUID.endsWith('-root')) {
|
|
89
|
+
return `#${$lidUtil.toUuid(namespaceUUID, id, 1)}`;
|
|
90
90
|
}
|
|
91
91
|
// Fallback to attr-based
|
|
92
92
|
}
|
|
93
93
|
// Rewrite absolute ID selector
|
|
94
94
|
let rewrite;
|
|
95
|
-
if (
|
|
96
|
-
rewrite = `:is(#${
|
|
95
|
+
if (config.attr.lid === 'id') {
|
|
96
|
+
rewrite = `:is(#${id},[id^="${$lidUtil.lidrefPrefix(escapeMode)}"][id$="${$lidUtil.lidrefSeparator(escapeMode)}${id}"])`;
|
|
97
97
|
} else {
|
|
98
|
-
rewrite = `:is(#${
|
|
98
|
+
rewrite = `:is(#${id},[${window.CSS.escape(config.attr.lid)}="${id}"])`;
|
|
99
99
|
}
|
|
100
|
-
return isLidref ? `:is(${
|
|
101
|
-
}
|
|
100
|
+
return isLidref ? `:is(${rewrite}):not(${scopeSelector ? scopeSelector + ' ' : ''}${config.namespaceSelector} *)` : rewrite;
|
|
101
|
+
});
|
|
102
102
|
// Category 2 has :scope and category 1 does not
|
|
103
|
-
return hadScopeSelector ? [
|
|
104
|
-
}, [
|
|
103
|
+
return hadScopeSelector ? [cat1, cat2.concat(selector)] : [cat1.concat(selector), cat2];
|
|
104
|
+
}, [[], []]);
|
|
105
105
|
// Do the upgrade
|
|
106
106
|
let newSelectorText;
|
|
107
|
-
if (
|
|
108
|
-
newSelectorText = [
|
|
107
|
+
if (scopeSelector && cat1.length) {
|
|
108
|
+
newSelectorText = [cat1.length > 1 ? `${scopeSelector} :is(${cat1.join(', ')})` : `${scopeSelector} ${cat1[0]}`, cat2.join(', ')].filter(x => x).join(', ');
|
|
109
109
|
} else {
|
|
110
|
-
newSelectorText = [
|
|
110
|
+
newSelectorText = [...cat1, ...cat2].join(', ');
|
|
111
111
|
}
|
|
112
112
|
return newSelectorText;
|
|
113
113
|
}
|
|
@@ -117,17 +117,19 @@ export function rewriteSelector( selectorText, namespaceUUID, scopeSelector = nu
|
|
|
117
117
|
*
|
|
118
118
|
* @return Object
|
|
119
119
|
*/
|
|
120
|
-
export function getOwnNamespaceObject(
|
|
120
|
+
export function getOwnNamespaceObject(node) {
|
|
121
121
|
const window = this;
|
|
122
|
-
if (
|
|
123
|
-
const namespaceObj = Object.create(
|
|
124
|
-
_wq(
|
|
125
|
-
const isDocumentRoot = [
|
|
126
|
-
Object.defineProperty(
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
if (!_wq(node).has('namespace')) {
|
|
123
|
+
const namespaceObj = Object.create(null);
|
|
124
|
+
_wq(node).set('namespace', namespaceObj);
|
|
125
|
+
const isDocumentRoot = [window.Document, window.ShadowRoot].some(x => node instanceof x);
|
|
126
|
+
Object.defineProperty(namespaceObj, Symbol.toStringTag, {
|
|
127
|
+
get() {
|
|
128
|
+
return isDocumentRoot ? 'RootNamespaceRegistry' : 'NamespaceRegistry';
|
|
129
|
+
}
|
|
130
|
+
});
|
|
129
131
|
}
|
|
130
|
-
return _wq(
|
|
132
|
+
return _wq(node).get('namespace');
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
/**
|
|
@@ -136,10 +138,10 @@ export function getOwnNamespaceObject( node ) {
|
|
|
136
138
|
*
|
|
137
139
|
* @return Object
|
|
138
140
|
*/
|
|
139
|
-
export function getOwnerNamespaceObject(
|
|
141
|
+
export function getOwnerNamespaceObject(node, forID = false) {
|
|
140
142
|
const window = this, { webqit: { oohtml: { configs: { NAMESPACED_HTML: config } } } } = window;
|
|
141
|
-
const isDocumentRoot = [
|
|
142
|
-
return getOwnNamespaceObject.call(
|
|
143
|
+
const isDocumentRoot = [window.Document, window.ShadowRoot].some(x => node instanceof x);
|
|
144
|
+
return getOwnNamespaceObject.call(window, isDocumentRoot ? node : ((forID ? node.parentNode : node)?.closest/*can be documentFragment when Shadow DOM*/?.(config.namespaceSelector) || node.getRootNode()));
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
/**
|
|
@@ -147,9 +149,9 @@ export function getOwnerNamespaceObject( node, forID = false ) {
|
|
|
147
149
|
*
|
|
148
150
|
* @return String
|
|
149
151
|
*/
|
|
150
|
-
export function getNamespaceUUID(
|
|
151
|
-
const isDocumentRoot = Object.prototype.toString.call(
|
|
152
|
-
|
|
152
|
+
export function getNamespaceUUID(namespaceObj) {
|
|
153
|
+
const isDocumentRoot = Object.prototype.toString.call(namespaceObj) === '[object RootNamespaceRegistry]';
|
|
154
|
+
return (_fromHash(namespaceObj) || _toHash(namespaceObj)) + (isDocumentRoot ? '-root' : '');
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
/**
|
|
@@ -159,18 +161,20 @@ export function getNamespaceUUID( namespaceObj ) {
|
|
|
159
161
|
*
|
|
160
162
|
* @return Void
|
|
161
163
|
*/
|
|
162
|
-
function exposeAPIs(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
164
|
+
function exposeAPIs(config) {
|
|
165
|
+
const window = this, { webqit: { Observer } } = window;
|
|
166
|
+
// The Namespace API
|
|
167
|
+
[window.Document.prototype, window.Element.prototype, window.ShadowRoot.prototype].forEach(prototype => {
|
|
168
|
+
// No-conflict assertions
|
|
169
|
+
const type = prototype === window.Document.prototype ? 'Document' : (prototype === window.ShadowRoot.prototype ? 'ShadowRoot' : 'Element');
|
|
170
|
+
if (config.api.namespace in prototype) { throw new Error(`The ${type} prototype already has a "${config.api.namespace}" API!`); }
|
|
171
|
+
// Definitions
|
|
172
|
+
Object.defineProperty(prototype, config.api.namespace, {
|
|
173
|
+
get: function () {
|
|
174
|
+
return Observer.proxy(getOwnNamespaceObject.call(window, this));
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
174
178
|
}
|
|
175
179
|
|
|
176
180
|
/**
|
|
@@ -180,222 +184,237 @@ function exposeAPIs( config ) {
|
|
|
180
184
|
*
|
|
181
185
|
* @return Void
|
|
182
186
|
*/
|
|
183
|
-
function realtime(
|
|
184
|
-
|
|
185
|
-
|
|
187
|
+
function realtime(config) {
|
|
188
|
+
const window = this, { webqit: { Observer, realdom, oohtml: { configs }, DOMNamingContext } } = window;
|
|
189
|
+
|
|
186
190
|
// ------------
|
|
187
191
|
// APIS
|
|
188
|
-
|
|
192
|
+
// ------------
|
|
189
193
|
// See https://wicg.github.io/aom/aria-reflection-explainer.html & https://github.com/whatwg/html/issues/3515 for the ARIA refelction properties idea
|
|
190
194
|
// See https://www.w3.org/TR/wai-aria-1.1/#attrs_relationships for the relational ARIA attributes
|
|
191
|
-
const idRefsAttrs = [
|
|
192
|
-
const idRefAttrs = [
|
|
193
|
-
const attrList = [
|
|
194
|
-
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' };
|
|
195
|
-
const $lidUtil = lidUtil(
|
|
196
|
-
const uuidsToLidrefs = (
|
|
197
|
-
if (
|
|
198
|
-
return _wq(
|
|
195
|
+
const idRefsAttrs = ['aria-owns', 'aria-controls', 'aria-labelledby', 'aria-describedby', 'aria-flowto',];
|
|
196
|
+
const idRefAttrs = ['for', 'list', 'form', 'aria-activedescendant', 'aria-details', 'aria-errormessage', 'popovertarget'];
|
|
197
|
+
const attrList = [config.attr.lid, ...idRefsAttrs, ...idRefAttrs];
|
|
198
|
+
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', 'popovertarget': 'popoverTargetElement' };
|
|
199
|
+
const $lidUtil = lidUtil(config);
|
|
200
|
+
const uuidsToLidrefs = (node, attrName, getter) => {
|
|
201
|
+
if (!getInternalAttrInteraction(node, attrName) && _wq(node, 'attrOriginals').has(attrName)) {
|
|
202
|
+
return _wq(node, 'attrOriginals').get(attrName);
|
|
199
203
|
}
|
|
200
204
|
const value = getter();
|
|
201
|
-
if (
|
|
202
|
-
return value && value.split(
|
|
205
|
+
if (getInternalAttrInteraction(node, attrName)) return value;
|
|
206
|
+
return value && value.split(' ').map(x => (x = x.trim()) && (attrName === config.attr.lid ? $lidUtil.uuidToId : $lidUtil.uuidToLidref).call($lidUtil, x)).join(' ');
|
|
203
207
|
};
|
|
204
208
|
|
|
205
209
|
// Intercept getElementById()
|
|
206
|
-
const getElementByIdDescr = Object.getOwnPropertyDescriptor(
|
|
207
|
-
Object.defineProperty(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
+
const getElementByIdDescr = Object.getOwnPropertyDescriptor(window.Document.prototype, 'getElementById');
|
|
211
|
+
Object.defineProperty(window.Document.prototype, 'getElementById', {
|
|
212
|
+
...getElementByIdDescr, value(id) {
|
|
213
|
+
return this.querySelector(`#${id}`); // To be rewritten at querySelector()
|
|
214
|
+
}
|
|
215
|
+
});
|
|
210
216
|
// Intercept querySelector() and querySelectorAll()
|
|
211
|
-
for (
|
|
212
|
-
for (
|
|
213
|
-
const querySelectorDescr = Object.getOwnPropertyDescriptor(
|
|
214
|
-
Object.defineProperty(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
+
for (const queryApi of ['querySelector', 'querySelectorAll']) {
|
|
218
|
+
for (const nodeApi of [window.Document, window.Element]) {
|
|
219
|
+
const querySelectorDescr = Object.getOwnPropertyDescriptor(nodeApi.prototype, queryApi);
|
|
220
|
+
Object.defineProperty(nodeApi.prototype, queryApi, {
|
|
221
|
+
...querySelectorDescr, value(selector) {
|
|
222
|
+
return querySelectorDescr.value.call(this, rewriteSelector.call(window, selector, getNamespaceUUID(getOwnNamespaceObject.call(window, this))));
|
|
223
|
+
}
|
|
224
|
+
});
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
227
|
// Intercept getAttribute()
|
|
220
|
-
const getAttributeDescr = Object.getOwnPropertyDescriptor(
|
|
221
|
-
Object.defineProperty(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
const getAttributeDescr = Object.getOwnPropertyDescriptor(window.Element.prototype, 'getAttribute');
|
|
229
|
+
Object.defineProperty(window.Element.prototype, 'getAttribute', {
|
|
230
|
+
...getAttributeDescr, value(attrName) {
|
|
231
|
+
const getter = () => getAttributeDescr.value.call(this, attrName);
|
|
232
|
+
return attrList.includes(attrName) && !_wq(this, 'lock').get(attrName) ? uuidsToLidrefs(this, attrName, getter) : getter();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
225
235
|
// Hide implementation details on the Attr node too.
|
|
226
|
-
const propertyDescr = Object.getOwnPropertyDescriptor(
|
|
227
|
-
Object.defineProperty(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
const propertyDescr = Object.getOwnPropertyDescriptor(window.Attr.prototype, 'value');
|
|
237
|
+
Object.defineProperty(window.Attr.prototype, 'value', {
|
|
238
|
+
...propertyDescr, get() {
|
|
239
|
+
const getter = () => propertyDescr.get.call(this);
|
|
240
|
+
return attrList.includes(this.name) ? uuidsToLidrefs(this.ownerElement, this.name, getter) : getter();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
const propertyDescr2 = Object.getOwnPropertyDescriptor(window.Node.prototype, 'nodeValue');
|
|
244
|
+
Object.defineProperty(window.Node.prototype, 'nodeValue', {
|
|
245
|
+
...propertyDescr2, get() {
|
|
246
|
+
const getter = () => propertyDescr2.get.call(this);
|
|
247
|
+
return this instanceof window.Attr && attrList.includes(this.name) ? uuidsToLidrefs(this.ownerElement, this.name, getter) : getter();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
236
250
|
// These APIs should return LIDREFS minus the hash part
|
|
237
|
-
for (
|
|
238
|
-
if (
|
|
239
|
-
const domApis = attrName === 'for' ? [
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
251
|
+
for (const attrName of attrList) {
|
|
252
|
+
if (!(attrName in relMap)) continue;
|
|
253
|
+
const domApis = attrName === 'for' ? [window.HTMLLabelElement, window.HTMLOutputElement]
|
|
254
|
+
: (attrName === 'popovertarget' ? [window.HTMLButtonElement, window.HTMLInputElement] : [window.Element]);
|
|
255
|
+
for (const domApi of domApis) {
|
|
256
|
+
const propertyDescr = Object.getOwnPropertyDescriptor(domApi.prototype, relMap[attrName]);
|
|
257
|
+
if (!propertyDescr) continue;
|
|
258
|
+
Object.defineProperty(domApi.prototype, relMap[attrName], {
|
|
259
|
+
...propertyDescr, get() {
|
|
260
|
+
const getter = () => propertyDescr.get.call(this, attrName);
|
|
261
|
+
return uuidsToLidrefs(this, attrName, getter);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
247
264
|
}
|
|
248
265
|
}
|
|
249
|
-
if (
|
|
266
|
+
if (config.attr.lid !== 'id') {
|
|
250
267
|
// Reflect the custom [config.attr.lid] attribute
|
|
251
|
-
Object.defineProperty(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
268
|
+
Object.defineProperty(window.Element.prototype, config.attr.lid, {
|
|
269
|
+
configurable: true, enumerable: true, get() {
|
|
270
|
+
return this.getAttribute(config.attr.lid);
|
|
271
|
+
}, set(value) {
|
|
272
|
+
return this.setAttribute(config.attr.lid, value);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
256
275
|
}
|
|
257
276
|
|
|
258
277
|
// ------------
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const attrChange = (
|
|
262
|
-
return internalAttrInteraction(
|
|
263
|
-
if (
|
|
264
|
-
return callback(
|
|
265
|
-
}
|
|
278
|
+
// LOCAL IDS & IDREFS
|
|
279
|
+
// ------------
|
|
280
|
+
const attrChange = (entry, attrName, value, callback) => {
|
|
281
|
+
return internalAttrInteraction(entry, attrName, () => {
|
|
282
|
+
if (typeof value === 'function') value = value();
|
|
283
|
+
return callback(value);
|
|
284
|
+
});
|
|
266
285
|
};
|
|
267
|
-
const setupBinding = (
|
|
268
|
-
attrChange(
|
|
286
|
+
const setupBinding = (entry, attrName, value, newNamespaceObj = null) => {
|
|
287
|
+
attrChange(entry, attrName, value, value => {
|
|
269
288
|
const isLidAttr = attrName === config.attr.lid;
|
|
270
|
-
const namespaceObj = newNamespaceObj || getOwnerNamespaceObject.call(
|
|
271
|
-
const namespaceUUID = getNamespaceUUID(
|
|
272
|
-
if (
|
|
273
|
-
const id = $lidUtil.uuidToId(
|
|
274
|
-
if (
|
|
275
|
-
const uuid = $lidUtil.toUuid(
|
|
276
|
-
if (
|
|
277
|
-
Observer.set(
|
|
289
|
+
const namespaceObj = newNamespaceObj || getOwnerNamespaceObject.call(window, entry, isLidAttr);
|
|
290
|
+
const namespaceUUID = getNamespaceUUID(namespaceObj);
|
|
291
|
+
if (isLidAttr) {
|
|
292
|
+
const id = $lidUtil.uuidToId(value);
|
|
293
|
+
if (Observer.get(namespaceObj, id) !== entry) {
|
|
294
|
+
const uuid = $lidUtil.toUuid(namespaceUUID, id);
|
|
295
|
+
if (uuid !== value) { entry.setAttribute('id', uuid); }
|
|
296
|
+
Observer.set(namespaceObj, id, entry);
|
|
278
297
|
}
|
|
279
298
|
} else {
|
|
280
|
-
_wq(
|
|
281
|
-
const newAttrValue = value.split(
|
|
282
|
-
entry.setAttribute(
|
|
283
|
-
_wq(
|
|
284
|
-
_wq(
|
|
299
|
+
_wq(entry, 'attrOriginals').set(attrName, value); // Save original before rewrite
|
|
300
|
+
const newAttrValue = value.split(' ').map(idref => (idref = idref.trim()) && $lidUtil.isUuid(idref) ? idref : $lidUtil.toUuid(namespaceUUID, idref)).join(' ');
|
|
301
|
+
entry.setAttribute(attrName, newAttrValue);
|
|
302
|
+
_wq(namespaceObj).set('idrefs', _wq(namespaceObj).get('idrefs') || new Set);
|
|
303
|
+
_wq(namespaceObj).get('idrefs').add(entry);
|
|
285
304
|
}
|
|
286
|
-
}
|
|
305
|
+
});
|
|
287
306
|
};
|
|
288
|
-
const cleanupBinding = (
|
|
289
|
-
attrChange(
|
|
307
|
+
const cleanupBinding = (entry, attrName, oldValue, prevNamespaceObj = null) => {
|
|
308
|
+
attrChange(entry, attrName, oldValue, oldValue => {
|
|
290
309
|
const isLidAttr = attrName === config.attr.lid;
|
|
291
|
-
const namespaceObj = prevNamespaceObj || getOwnerNamespaceObject.call(
|
|
292
|
-
if (
|
|
293
|
-
const id = $lidUtil.uuidToId(
|
|
294
|
-
if (
|
|
295
|
-
Observer.deleteProperty(
|
|
310
|
+
const namespaceObj = prevNamespaceObj || getOwnerNamespaceObject.call(window, entry, isLidAttr);
|
|
311
|
+
if (isLidAttr) {
|
|
312
|
+
const id = $lidUtil.uuidToId(oldValue);
|
|
313
|
+
if (Observer.get(namespaceObj, id) === entry) {
|
|
314
|
+
Observer.deleteProperty(namespaceObj, id);
|
|
296
315
|
}
|
|
297
316
|
} else {
|
|
298
|
-
const newAttrValue = _wq(
|
|
299
|
-
if (
|
|
300
|
-
_wq(
|
|
317
|
+
const newAttrValue = _wq(entry, 'attrOriginals').get(attrName);// oldValue.split( ' ' ).map( lid => ( lid = lid.trim() ) && $lidUtil.uuidToLidref( lid ) ).join( ' ' );
|
|
318
|
+
if (entry.hasAttribute(attrName)) entry.setAttribute(attrName, newAttrValue);
|
|
319
|
+
_wq(namespaceObj).get('idrefs')?.delete(entry);
|
|
301
320
|
}
|
|
302
|
-
}
|
|
321
|
+
});
|
|
303
322
|
};
|
|
304
323
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const reAssociate = (
|
|
310
|
-
if (
|
|
311
|
-
const attrValue = () => entry.getAttribute(
|
|
312
|
-
cleanupBinding(
|
|
313
|
-
if (
|
|
324
|
+
// ------------
|
|
325
|
+
// NAMESPACE
|
|
326
|
+
// ------------
|
|
327
|
+
realdom.realtime(window.document).query(config.namespaceSelector, record => {
|
|
328
|
+
const reAssociate = (entry, attrName, oldNamespaceObj, newNamespaceObj) => {
|
|
329
|
+
if (!entry.hasAttribute(attrName)) return;
|
|
330
|
+
const attrValue = () => entry.getAttribute(attrName);
|
|
331
|
+
cleanupBinding(entry, attrName, attrValue/* Current resolved value as-is */, oldNamespaceObj);
|
|
332
|
+
if (entry.isConnected) { setupBinding(entry, attrName, _wq(entry, 'attrOriginals').get(attrName)/* Saved original value */ || attrValue/* Lest it's ID */, newNamespaceObj); }
|
|
314
333
|
};
|
|
315
|
-
|
|
316
|
-
if (
|
|
317
|
-
const namespaceObj = getOwnNamespaceObject.call(
|
|
334
|
+
record.exits.forEach(entry => {
|
|
335
|
+
if (entry.isConnected) {
|
|
336
|
+
const namespaceObj = getOwnNamespaceObject.call(window, entry);
|
|
318
337
|
// Detach ID and IDREF associations
|
|
319
|
-
for (
|
|
320
|
-
for (
|
|
338
|
+
for (const node of new Set([...Object.values(namespaceObj), ...(_wq(namespaceObj).get('idrefs') || [])])) {
|
|
339
|
+
for (const attrName of attrList) { reAssociate(node, attrName, namespaceObj); }
|
|
321
340
|
}
|
|
322
341
|
}
|
|
323
342
|
// Detach ID associations
|
|
324
|
-
const contextsApi = entry[
|
|
325
|
-
const ctx = contextsApi.find(
|
|
343
|
+
const contextsApi = entry[configs.CONTEXT_API.api.contexts];
|
|
344
|
+
const ctx = contextsApi.find(DOMNamingContext.kind);
|
|
326
345
|
// Detach namespace instance
|
|
327
|
-
if (
|
|
328
|
-
|
|
329
|
-
|
|
346
|
+
if (ctx) { contextsApi.detach(ctx); }
|
|
347
|
+
});
|
|
348
|
+
record.entrants.forEach(entry => {
|
|
330
349
|
// Claim ID and IDREF associations
|
|
331
350
|
let newSuperNamespaceObj;
|
|
332
|
-
const superNamespaceObj = getOwnerNamespaceObject.call(
|
|
333
|
-
for (
|
|
334
|
-
if (
|
|
335
|
-
for (
|
|
351
|
+
const superNamespaceObj = getOwnerNamespaceObject.call(window, entry, true);
|
|
352
|
+
for (const node of new Set([...Object.values(superNamespaceObj), ...(_wq(superNamespaceObj).get('idrefs') || [])])) {
|
|
353
|
+
if ((newSuperNamespaceObj = getOwnerNamespaceObject.call(window, node, true)) === superNamespaceObj) continue;
|
|
354
|
+
for (const attrName of attrList) { reAssociate(node, attrName, superNamespaceObj, newSuperNamespaceObj); }
|
|
336
355
|
}
|
|
337
356
|
// Attach namespace instance
|
|
338
|
-
const contextsApi = entry[
|
|
339
|
-
if (
|
|
340
|
-
|
|
341
|
-
|
|
357
|
+
const contextsApi = entry[configs.CONTEXT_API.api.contexts];
|
|
358
|
+
if (!contextsApi.find(DOMNamingContext.kind)) { contextsApi.attach(new DOMNamingContext); }
|
|
359
|
+
});
|
|
360
|
+
}, { id: 'namespace-html:namespace', live: true, subtree: 'cross-roots', timing: 'sync', staticSensitivity: true, eventDetails: true });
|
|
342
361
|
|
|
343
362
|
// DOM realtime
|
|
344
|
-
realdom.realtime(
|
|
363
|
+
realdom.realtime(window.document).query(`[${attrList.map(attrName => window.CSS.escape(attrName)).join('],[')}]`, record => {
|
|
345
364
|
// We do some caching to prevent redundanct lookups
|
|
346
365
|
const namespaceNodesToTest = { forID: new Map, forOther: new Map, };
|
|
347
|
-
for (
|
|
366
|
+
for (const attrName of attrList) {
|
|
348
367
|
// Point to the right cache
|
|
349
368
|
const _namespaceNodesToTest = attrName === config.attr.lid ? namespaceNodesToTest.forID : namespaceNodesToTest.forOther;
|
|
350
|
-
record.exits.forEach(
|
|
351
|
-
if (
|
|
369
|
+
record.exits.forEach(entry => {
|
|
370
|
+
if (!entry.hasAttribute(attrName)) return;
|
|
352
371
|
// Point to the right namespace node
|
|
353
|
-
let namespaceNodeToTest = _namespaceNodesToTest.get(
|
|
354
|
-
if (
|
|
355
|
-
namespaceNodeToTest = (
|
|
356
|
-
_namespaceNodesToTest.set(
|
|
372
|
+
let namespaceNodeToTest = _namespaceNodesToTest.get(entry);
|
|
373
|
+
if (typeof namespaceNodeToTest === 'undefined') {
|
|
374
|
+
namespaceNodeToTest = (attrName === config.attr.lid ? entry.parentNode : entry)?.closest/*can be documentFragment when Shadow DOM*/?.(config.namespaceSelector) || entry.getRootNode().host;
|
|
375
|
+
_namespaceNodesToTest.set(entry, namespaceNodeToTest);
|
|
357
376
|
}
|
|
358
|
-
if (
|
|
359
|
-
cleanupBinding(
|
|
360
|
-
}
|
|
361
|
-
record.entrants.forEach(
|
|
362
|
-
if (
|
|
363
|
-
setupBinding(
|
|
364
|
-
}
|
|
377
|
+
if (namespaceNodeToTest && !namespaceNodeToTest.isConnected) return;
|
|
378
|
+
cleanupBinding(entry, attrName, () => entry.getAttribute(attrName)/* Current resolved value as-is */);
|
|
379
|
+
});
|
|
380
|
+
record.entrants.forEach(entry => {
|
|
381
|
+
if (!entry.hasAttribute(attrName)) return;
|
|
382
|
+
setupBinding(entry, attrName, () => entry.getAttribute(attrName)/* Raw value (as-is) that will be saved as original */);
|
|
383
|
+
});
|
|
365
384
|
}
|
|
366
385
|
namespaceNodesToTest.forID.clear();
|
|
367
386
|
namespaceNodesToTest.forOther.clear();
|
|
368
|
-
}, { id: 'namespace-html:attrs', live: true, subtree: 'cross-roots', timing: 'sync' }
|
|
387
|
+
}, { id: 'namespace-html:attrs', live: true, subtree: 'cross-roots', timing: 'sync' });
|
|
369
388
|
// Attr realtime
|
|
370
|
-
realdom.realtime(
|
|
371
|
-
for (
|
|
372
|
-
if (
|
|
373
|
-
cleanupBinding(
|
|
389
|
+
realdom.realtime(window.document, 'attr').observe(attrList, records => {
|
|
390
|
+
for (const record of records) {
|
|
391
|
+
if (record.oldValue && record.value !== record.oldValue) {
|
|
392
|
+
cleanupBinding(record.target, record.name, record.oldValue/* Current resolved value as-is */);
|
|
374
393
|
}
|
|
375
|
-
if (
|
|
376
|
-
setupBinding(
|
|
394
|
+
if (record.value && record.value !== record.oldValue) {
|
|
395
|
+
setupBinding(record.target, record.name, record.value/* Raw value (as-is) that will be saved as original */);
|
|
377
396
|
}
|
|
378
397
|
}
|
|
379
|
-
}, { id: 'namespace-html:attr(attrs)', subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true }
|
|
398
|
+
}, { id: 'namespace-html:attr(attrs)', subtree: 'cross-roots', timing: 'sync', newValue: true, oldValue: true });
|
|
380
399
|
|
|
381
|
-
|
|
400
|
+
// ------------
|
|
382
401
|
// TARGETS
|
|
383
|
-
|
|
402
|
+
// ------------
|
|
384
403
|
let prevTarget;
|
|
385
404
|
const activateTarget = () => {
|
|
386
|
-
if (
|
|
387
|
-
const path = window.location.hash?.substring(
|
|
388
|
-
const currTarget = path.reduce(
|
|
389
|
-
if (
|
|
390
|
-
if (
|
|
391
|
-
if (
|
|
392
|
-
if (
|
|
393
|
-
if (
|
|
405
|
+
if (!window.location.hash?.startsWith(`#${$lidUtil.lidrefPrefix()}`)) return;
|
|
406
|
+
const path = window.location.hash?.substring(`#${$lidUtil.lidrefPrefix()}`.length).split('/').map(s => s.trim()).filter(s => s) || [];
|
|
407
|
+
const currTarget = path.reduce((prev, segment) => prev && prev[config.api.namespace][segment], window.document);
|
|
408
|
+
if (prevTarget && config.target.className) { prevTarget.classList.toggle(config.target.className, false); }
|
|
409
|
+
if (currTarget && currTarget !== window.document) {
|
|
410
|
+
if (config.target.className) { currTarget.classList.toggle(config.target.className, true); }
|
|
411
|
+
if (config.target.eventName) { currTarget.dispatchEvent(new window.CustomEvent(config.target.eventName)); }
|
|
412
|
+
if (config.target.scrolling && path.length > 1) { currTarget.scrollIntoView(); }
|
|
394
413
|
prevTarget = currTarget;
|
|
395
414
|
}
|
|
396
415
|
};
|
|
397
416
|
// "hash" realtime
|
|
398
|
-
window.addEventListener(
|
|
399
|
-
realdom.ready(
|
|
417
|
+
window.addEventListener('hashchange', activateTarget);
|
|
418
|
+
realdom.ready(activateTarget);
|
|
400
419
|
// ----------------
|
|
401
420
|
}
|