solarite 0.8.0 → 0.9.0
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/dist/Solarite-debug.js +340 -166
- package/dist/Solarite.js +339 -165
- package/dist/Solarite.min.js +2 -2
- package/dist/jsx-dev-runtime.min.js +3 -0
- package/dist/jsx-runtime.min.js +2 -0
- package/package.json +2 -2
- package/readme.md +6 -6
- package/src/Globals.js +22 -2
- package/src/NodeGroup.js +5 -5
- package/src/PathToAttribValue.js +122 -56
- package/src/PathToComponent.js +104 -90
- package/src/PathToEvent.js +16 -2
- package/src/RootNodeGroup.js +22 -5
- package/src/Shell.js +37 -6
- package/src/Solarite.d.ts +14 -12
- package/src/Util.js +13 -0
package/src/PathToComponent.js
CHANGED
|
@@ -78,106 +78,120 @@ export default class PathToComponent extends Path {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
81
|
+
// Constructing a component runs arbitrary user code -- field initializers, the
|
|
82
|
+
// constructor body, render() -- and that code can build more components, re-entering
|
|
83
|
+
// this method and overwriting the hand-off parked below. Saving the caller's value
|
|
84
|
+
// here and restoring it in the finally makes the JS call stack the stack this hand-off
|
|
85
|
+
// needs, and unlike an explicit stack it cannot leak if construction throws.
|
|
86
|
+
let prevSlotChildren = Globals.currentSlotChildren;
|
|
87
|
+
try {
|
|
88
|
+
// 2. Instantiate component on first time.
|
|
89
|
+
let isAttrib = el.getAttribute('_is');
|
|
90
|
+
if (el.tagName.endsWith('-SOLARITE-PLACEHOLDER') || isAttrib) {
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
// 2a. Instantiate component
|
|
94
|
+
let tagName = (isAttrib || el.tagName.slice(0, -21)).toLowerCase(); // Remove -SOLARITE-PLACEHOLDER
|
|
95
|
+
let Constructor = customElements.get(tagName);
|
|
96
|
+
|
|
97
|
+
// Not defined yet (e.g. the module is being lazily imported): keep the placeholder
|
|
98
|
+
// and instantiate when the definition lands, like a native custom-element upgrade.
|
|
99
|
+
// deferredExprs always holds the LATEST exprs so re-renders while undefined win.
|
|
100
|
+
if (!Constructor) {
|
|
101
|
+
this.deferredExprs = exprs;
|
|
102
|
+
if (!this.whenDefinedPending) {
|
|
103
|
+
this.whenDefinedPending = true;
|
|
104
|
+
console.warn(`Solarite: <${tagName}> is not defined yet; waiting for customElements.define().`);
|
|
105
|
+
customElements.whenDefined(tagName).then(() => {
|
|
106
|
+
this.whenDefinedPending = false;
|
|
107
|
+
let deferred = this.deferredExprs;
|
|
108
|
+
this.deferredExprs = null;
|
|
109
|
+
// Skip if a newer render already instantiated or replaced the placeholder.
|
|
110
|
+
if (deferred && this.nodeMarker === el && el.tagName.endsWith('-SOLARITE-PLACEHOLDER'))
|
|
111
|
+
this.applyAll(deferred);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
106
115
|
}
|
|
107
|
-
Globals.currentSlotChildren = null;
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
Globals.currentSlotChildren = [...el.childNodes]; // TODO: Does this need to be a stack?
|
|
112
|
-
let newEl = new Constructor(attribs);
|
|
113
116
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
// Hand the children declared inside the component's tag to the RootNodeGroup that
|
|
118
|
+
// its render() is about to create. There is no other channel: the children have
|
|
119
|
+
// to be parked before new Constructor(), because a Solarite constructor may call
|
|
120
|
+
// this.render() itself, and the element that would otherwise carry them does not
|
|
121
|
+
// exist yet.
|
|
122
|
+
Globals.currentSlotChildren = {Constructor, nodes: [...el.childNodes]};
|
|
123
|
+
let newEl = new Constructor(attribs);
|
|
124
|
+
|
|
125
|
+
// 2b. Copy attributes over.
|
|
126
|
+
if (isAttrib) {
|
|
127
|
+
newEl.setAttribute('is', isAttrib);
|
|
128
|
+
// el.removeAttribute('_is');
|
|
129
|
+
}
|
|
130
|
+
for (let attrib of el.attributes)
|
|
131
|
+
if (attrib.name !== '_is')
|
|
132
|
+
newEl.setAttribute(attrib.name, attrib.value);
|
|
133
|
+
|
|
134
|
+
// Set dynamic attributes if they are primitive types.
|
|
135
|
+
for (let name in attribs) {
|
|
136
|
+
let val = attribs[name];
|
|
137
|
+
let valType = typeof val;
|
|
138
|
+
// Only true and false can reach here, so the undefined/null halves of the
|
|
139
|
+
// falsy test this used to spell out could never have decided anything.
|
|
140
|
+
if (valType === 'boolean') {
|
|
141
|
+
if (val)
|
|
142
|
+
newEl.setAttribute(name, '');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// If type is a non-boolean primitive, set the attribute value.
|
|
146
|
+
else if (valType==='string' || valType === 'number' || valType==='bigint')
|
|
147
|
+
newEl.setAttribute(name, val);
|
|
132
148
|
}
|
|
133
149
|
|
|
134
|
-
// If type is a non-boolean primitive, set the attribute value.
|
|
135
|
-
else if (valType==='string' || valType === 'number' || valType==='bigint')
|
|
136
|
-
newEl.setAttribute(name, val);
|
|
137
|
-
}
|
|
138
150
|
|
|
151
|
+
// 2c. If an id pointed at the placeholder, update it to point to the new element.
|
|
152
|
+
let id = newEl.getAttribute('data-id') || newEl.getAttribute('id');
|
|
153
|
+
if (id)
|
|
154
|
+
delve(this.parentNg.getRootEl(), id.split(/\./g), newEl);
|
|
139
155
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
// 2d. Update paths to use replaced element.
|
|
157
|
+
let ng = this.parentNg;
|
|
158
|
+
this.nodeMarker = newEl;
|
|
159
|
+
for (let path of ng.paths) {
|
|
160
|
+
if (path.nodeMarker === el)
|
|
161
|
+
path.nodeMarker = newEl;
|
|
162
|
+
if (path.nodeBefore === el)
|
|
163
|
+
path.nodeBefore = newEl;
|
|
164
|
+
}
|
|
165
|
+
if (ng.startNode === el)
|
|
166
|
+
ng.startNode = newEl;
|
|
167
|
+
if (ng.endNode === el)
|
|
168
|
+
ng.endNode = newEl;
|
|
169
|
+
|
|
170
|
+
// 2f. Call render() if it wasn't called by the constructor.
|
|
171
|
+
// This must happen before we add it to the DOM which can trigger connectedCallback() -> renderFirstTime()
|
|
172
|
+
// Because that path renders it without the attribute expressions.
|
|
173
|
+
if (typeof newEl.render === 'function' && !Globals.rendered.has(newEl))
|
|
174
|
+
newEl.render(attribs, true);
|
|
175
|
+
|
|
176
|
+
// 2g. Update attribute paths to use the new element and re-apply them.
|
|
177
|
+
for (let i=0, attribPath; attribPath = this.attribPaths[i]; i++) {
|
|
178
|
+
attribPath.parentNg = this.parentNg;
|
|
179
|
+
attribPath.nodeMarker = newEl;
|
|
180
|
+
attribPath.applyAll(exprs[i]);
|
|
181
|
+
}
|
|
144
182
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
this.nodeMarker = newEl;
|
|
148
|
-
for (let path of ng.paths) {
|
|
149
|
-
if (path.nodeMarker === el)
|
|
150
|
-
path.nodeMarker = newEl;
|
|
151
|
-
if (path.nodeBefore === el)
|
|
152
|
-
path.nodeBefore = newEl;
|
|
153
|
-
}
|
|
154
|
-
if (ng.startNode === el)
|
|
155
|
-
ng.startNode = newEl;
|
|
156
|
-
if (ng.endNode === el)
|
|
157
|
-
ng.endNode = newEl;
|
|
158
|
-
|
|
159
|
-
// 2f. Call render() if it wasn't called by the constructor.
|
|
160
|
-
// This must happen before we add it to the DOM which can trigger connectedCallback() -> renderFirstTime()
|
|
161
|
-
// Because that path renders it without the attribute expressions.
|
|
162
|
-
if (typeof newEl.render === 'function' && !Globals.rendered.has(newEl))
|
|
163
|
-
newEl.render(attribs, true);
|
|
164
|
-
|
|
165
|
-
// 2g. Update attribute paths to use the new element and re-apply them.
|
|
166
|
-
for (let i=0, attribPath; attribPath = this.attribPaths[i]; i++) {
|
|
167
|
-
attribPath.parentNg = this.parentNg;
|
|
168
|
-
attribPath.nodeMarker = newEl;
|
|
169
|
-
attribPath.applyAll(exprs[i]);
|
|
183
|
+
// 2e. Swap it to the DOM.
|
|
184
|
+
el.replaceWith(newEl);
|
|
170
185
|
}
|
|
171
186
|
|
|
172
|
-
//
|
|
173
|
-
el.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// 2f. Render
|
|
177
|
-
else if (typeof el.render === 'function')
|
|
178
|
-
el.render(attribs, changed);
|
|
187
|
+
// 2f. Render
|
|
188
|
+
else if (typeof el.render === 'function')
|
|
189
|
+
el.render(attribs, changed);
|
|
179
190
|
|
|
180
|
-
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
Globals.currentSlotChildren = prevSlotChildren;
|
|
194
|
+
}
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
/**
|
package/src/PathToEvent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "./assert.js";
|
|
2
2
|
import PathToAttribValue, {delegatedKeyFor} from "./PathToAttribValue.js";
|
|
3
|
+
import {nativeEventPrefix} from "./Util.js";
|
|
3
4
|
|
|
4
5
|
// TODO: Merge this into PathToAttribValue?
|
|
5
6
|
export default class PathToEvent extends PathToAttribValue {
|
|
@@ -11,11 +12,24 @@ export default class PathToEvent extends PathToAttribValue {
|
|
|
11
12
|
* Undefined for non-delegatable (non-bubbling) events; bindEvent() then binds directly. */
|
|
12
13
|
delegatedKey;
|
|
13
14
|
|
|
15
|
+
/** @type {boolean} True for `native:onclick`: the handler is registered with addEventListener
|
|
16
|
+
* when the template renders, so it runs at its element's own turn in the browser's dispatch
|
|
17
|
+
* order instead of being delegated to the component root. */
|
|
18
|
+
native;
|
|
19
|
+
|
|
14
20
|
constructor(nodeBefore, nodeMarker, attribName=null, attrValue=null) {
|
|
15
21
|
super(null, nodeMarker, attribName, attrValue);
|
|
16
22
|
this.skipIfSame = true;
|
|
17
|
-
|
|
18
|
-
this.
|
|
23
|
+
let name = attribName;
|
|
24
|
+
this.native = name !== null && name.startsWith(nativeEventPrefix);
|
|
25
|
+
if (this.native)
|
|
26
|
+
name = name.slice(nativeEventPrefix.length);
|
|
27
|
+
this.eventName = name ? name.slice(2) : null;
|
|
28
|
+
|
|
29
|
+
// A native binding leaves delegatedKey undefined. That is the single switch both
|
|
30
|
+
// bindEvent() and the compiled stamp program test to choose the direct
|
|
31
|
+
// addEventListener path, so nothing else has to know about the prefix.
|
|
32
|
+
this.delegatedKey = (this.eventName !== null && !this.native) ? delegatedKeyFor(this.eventName) : undefined;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
/**
|
package/src/RootNodeGroup.js
CHANGED
|
@@ -30,13 +30,30 @@ export default class RootNodeGroup extends NodeGroup {
|
|
|
30
30
|
if (el) {
|
|
31
31
|
this.rootEl = el;
|
|
32
32
|
|
|
33
|
-
// Save slot
|
|
34
|
-
// 1.
|
|
35
|
-
//
|
|
33
|
+
// Save the children that belong in this component's <slot>, from one of two places:
|
|
34
|
+
// 1. A hand-off parked by PathToComponent.applyAll() just before it constructed
|
|
35
|
+
// us, when this component was declared inside another template. It carries
|
|
36
|
+
// the Constructor it was meant for, so an unrelated component built in the
|
|
37
|
+
// meantime -- a field initializer creating a menu, say -- leaves it alone.
|
|
38
|
+
// 2. el.childNodes, when render() is called manually for the first time.
|
|
39
|
+
// An addressed hand-off wins even when its node list is empty: a component
|
|
40
|
+
// declared as <my-tag></my-tag> is asking for an empty slot, not for whatever
|
|
41
|
+
// its own constructor happened to put in the element.
|
|
42
|
+
//
|
|
43
|
+
// The hand-off is deliberately NOT cleared on read. A component that builds
|
|
44
|
+
// another instance of its OWN class while constructing cannot be told apart
|
|
45
|
+
// from itself by any address, so both match; the inner one takes the nodes and
|
|
46
|
+
// this outer one takes them straight back, which is the only thing that makes
|
|
47
|
+
// that case work.
|
|
48
|
+
let handOff = Globals.currentSlotChildren;
|
|
49
|
+
let mySlotNodes = handOff?.Constructor === el.constructor
|
|
50
|
+
? handOff.nodes
|
|
51
|
+
: (el.childNodes.length ? [...el.childNodes] : null);
|
|
52
|
+
|
|
36
53
|
let slotChildren;
|
|
37
|
-
if (
|
|
54
|
+
if (mySlotNodes) {
|
|
38
55
|
slotChildren = Globals.doc.createDocumentFragment();
|
|
39
|
-
slotChildren.append(...
|
|
56
|
+
slotChildren.append(...mySlotNodes);
|
|
40
57
|
}
|
|
41
58
|
|
|
42
59
|
// If el should replace the root node of the fragment.
|
package/src/Shell.js
CHANGED
|
@@ -168,6 +168,9 @@ export default class Shell {
|
|
|
168
168
|
// Smaller fragments make cloning, path resolution, and insertion faster.
|
|
169
169
|
stripTableWhitespace(this.docFrag);
|
|
170
170
|
|
|
171
|
+
// 1c. Neutralize `is` so the browser can't upgrade a placeholder out from under us.
|
|
172
|
+
renameIsAttribs(this.docFrag);
|
|
173
|
+
|
|
171
174
|
// 2. Find placeholders
|
|
172
175
|
let node;
|
|
173
176
|
let toRemove = [];
|
|
@@ -181,7 +184,7 @@ export default class Shell {
|
|
|
181
184
|
|
|
182
185
|
// Replace attributes
|
|
183
186
|
if (node.nodeType === 1) {
|
|
184
|
-
const hasIs = node.hasAttribute('
|
|
187
|
+
const hasIs = node.hasAttribute('_is'); // Renamed from `is` in step 1c.
|
|
185
188
|
const isComponent = (hasIs || node.tagName.includes('-'));
|
|
186
189
|
const componentAttribPaths = [];
|
|
187
190
|
|
|
@@ -285,10 +288,6 @@ export default class Shell {
|
|
|
285
288
|
path.attribPaths = componentAttribPaths;
|
|
286
289
|
this.paths.splice(this.paths.length - componentAttribPaths.length, 0, path); // Insert before its componentAttribPaths
|
|
287
290
|
|
|
288
|
-
if (hasIs) {
|
|
289
|
-
node.setAttribute('_is', node.getAttribute('is'));
|
|
290
|
-
node.removeAttribute('is');
|
|
291
|
-
}
|
|
292
291
|
}
|
|
293
292
|
}
|
|
294
293
|
|
|
@@ -305,7 +304,7 @@ export default class Shell {
|
|
|
305
304
|
// Components and slots are excluded because they move their children
|
|
306
305
|
// during instantiation, which would orphan the expression's region.
|
|
307
306
|
if (parent.nodeType === 1 && !node.previousSibling && !node.nextSibling
|
|
308
|
-
&& !parent.tagName.includes('-') && parent.tagName !== 'SLOT' && !parent.hasAttribute('
|
|
307
|
+
&& !parent.tagName.includes('-') && parent.tagName !== 'SLOT' && !parent.hasAttribute('_is')) {
|
|
309
308
|
let path = new PathToNodes(null, parent);
|
|
310
309
|
path.wholeParent = true;
|
|
311
310
|
this.paths.push(path);
|
|
@@ -719,6 +718,38 @@ function stripTableWhitespace(el) {
|
|
|
719
718
|
}
|
|
720
719
|
}
|
|
721
720
|
|
|
721
|
+
/**
|
|
722
|
+
* Rename every `is` attribute to `_is`, rebuilding the element to do it.
|
|
723
|
+
*
|
|
724
|
+
* A component written as a dashed tag is neutralized in the shell by renaming the TAG
|
|
725
|
+
* (`<my-tag>` becomes `<my-tag-SOLARITE-PLACEHOLDER>`), so the browser never recognizes the
|
|
726
|
+
* placeholder and never upgrades it. A customized built-in cannot be neutralized that way,
|
|
727
|
+
* because its tag has to stay real: a `<tr is="my-row">` that is not a `<tr>` is thrown out
|
|
728
|
+
* by the parser's table rules. So its ATTRIBUTE is renamed instead.
|
|
729
|
+
*
|
|
730
|
+
* Renaming the attribute in place is not enough. `is` is also recorded in an internal slot on
|
|
731
|
+
* the element, which removeAttribute() cannot clear and cloneNode() copies, so a placeholder
|
|
732
|
+
* that was parsed with `is` stays a customized built-in as far as the browser is concerned.
|
|
733
|
+
* Every clone of it is upgraded the moment it enters a document with a browsing context —
|
|
734
|
+
* running the component's constructor on the placeholder, before PathToComponent has
|
|
735
|
+
* instantiated the real element or evaluated the attribute expressions meant for it. A
|
|
736
|
+
* constructor that renders then renders the placeholder, whose children are the ones the user
|
|
737
|
+
* declared, and those get handed to the real instance as if they were slot content.
|
|
738
|
+
*
|
|
739
|
+
* Building a fresh element and moving everything across is the only way to drop that slot.
|
|
740
|
+
* It happens once per unique template, because Shells are cached, and never per render.
|
|
741
|
+
*
|
|
742
|
+
* @param docFrag {DocumentFragment} */
|
|
743
|
+
function renameIsAttribs(docFrag) {
|
|
744
|
+
for (let el of docFrag.querySelectorAll('[is]')) {
|
|
745
|
+
let clean = el.ownerDocument.createElement(el.tagName);
|
|
746
|
+
for (let attrib of el.attributes)
|
|
747
|
+
clean.setAttribute(attrib.name === 'is' ? '_is' : attrib.name, attrib.value);
|
|
748
|
+
clean.append(...el.childNodes);
|
|
749
|
+
el.replaceWith(clean);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
722
753
|
// One-entry memo for Shell.get().
|
|
723
754
|
let lastHtmlStrings = null, lastSvgMode = false, lastShell = null;
|
|
724
755
|
|
package/src/Solarite.d.ts
CHANGED
|
@@ -10,17 +10,19 @@ export interface RenderOptions {
|
|
|
10
10
|
ids?: boolean;
|
|
11
11
|
render?: boolean;
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Delegate bubbling events (default true). A handler is stored on its element instead of
|
|
15
|
+
* registered with addEventListener, and when an event of that type starts, a capture-phase
|
|
16
|
+
* listener on the component root and the document attaches a real listener to each element
|
|
17
|
+
* on the event's path that has one; the browser then dispatches normally. Much faster
|
|
18
|
+
* creation and teardown of large lists, with native ordering, stopPropagation(),
|
|
19
|
+
* currentTarget, non-bubbling events and moved elements all behaving as with
|
|
20
|
+
* addEventListener. The one difference: a delegated handler is attached when the event
|
|
21
|
+
* starts, so it runs after listeners other code added to the same element. Prefix an
|
|
22
|
+
* attribute with native: (native:onclick) to bind just that handler at render time.
|
|
23
|
+
* Pass false to bind every event directly, or an array to delegate only the listed event
|
|
24
|
+
* names. Non-bubbling events always bind directly. */
|
|
25
|
+
eventDelegation?: boolean | string[];
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -183,7 +185,7 @@ export function delve(obj: object, path: string[], createVal?: any): any;
|
|
|
183
185
|
* Internal utilities and state. */
|
|
184
186
|
export const Globals: {
|
|
185
187
|
connected: WeakSet<HTMLElement>;
|
|
186
|
-
currentSlotChildren:
|
|
188
|
+
currentSlotChildren: {Constructor: Function, nodes: Node[]} | null;
|
|
187
189
|
div: HTMLDivElement;
|
|
188
190
|
doc: Document;
|
|
189
191
|
elementClasses: {[key: string]: typeof Node};
|
package/src/Util.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import Globals from "./Globals.js";
|
|
2
2
|
import delve from "./delve.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Prefix that asks for a handler to bypass event delegation: `<button native:onclick=\${...}>`
|
|
6
|
+
* is bound with addEventListener at render time, taking its normal place in the browser's own
|
|
7
|
+
* dispatch order. Shared by Util.isEvent() and PathToEvent, which strips it. */
|
|
8
|
+
export const nativeEventPrefix = 'native:';
|
|
9
|
+
|
|
4
10
|
let Util = {
|
|
5
11
|
|
|
6
12
|
/**
|
|
@@ -200,7 +206,14 @@ let Util = {
|
|
|
200
206
|
return node.value; // String
|
|
201
207
|
},
|
|
202
208
|
|
|
209
|
+
/**
|
|
210
|
+
* True for an attribute name that binds an event: `onclick`, or `native:onclick` for a
|
|
211
|
+
* handler that is registered with addEventListener when the template renders instead of
|
|
212
|
+
* being delegated. Only names an element really exposes as on* handlers count, so an
|
|
213
|
+
* attribute like `online` is never mistaken for one. */
|
|
203
214
|
isEvent(attribName) {
|
|
215
|
+
if (attribName.startsWith(nativeEventPrefix))
|
|
216
|
+
attribName = attribName.slice(nativeEventPrefix.length);
|
|
204
217
|
return attribName.startsWith('on') && attribName in Globals.div;
|
|
205
218
|
},
|
|
206
219
|
|