native-document 1.0.17-1.1 → 1.0.17-1.10
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/package.json +1 -1
- package/src/core/data/MemoryManager.js +6 -1
- package/src/core/data/ObservableItem.js +28 -10
- package/src/core/elements/anchor/anchor.js +4 -3
- package/src/core/elements/anchor/one-child-anchor-overwriting.js +4 -4
- package/src/core/elements/content-formatter.js +19 -0
- package/src/core/elements/control/for-each-array-cache.js +59 -0
- package/src/core/elements/control/for-each-array.js +15 -23
- package/src/core/wrappers/AttributesWrapper.js +21 -65
- package/src/core/wrappers/ElementCreator.js +25 -25
- package/src/core/wrappers/HtmlElementWrapper.js +9 -4
- package/src/core/wrappers/NDElement.js +13 -17
- package/src/core/wrappers/NdPrototype.js +31 -39
- package/src/core/wrappers/constants.js +12 -12
- package/src/core/wrappers/prototypes/nd-element-extensions.js +93 -17
- package/src/ui/components/accordion/AccordionItemRender.js +6 -6
- package/types/elements.d.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-document",
|
|
3
|
-
"version": "1.0.171.
|
|
3
|
+
"version": "1.0.171.10",
|
|
4
4
|
"description": "A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features",
|
|
5
5
|
"author": "AfroCodeur <https://github.com/afrocodeur>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,7 +23,12 @@ const MemoryManager = (function() {
|
|
|
23
23
|
$observables.delete(id);
|
|
24
24
|
},
|
|
25
25
|
getObservableById(id) {
|
|
26
|
-
|
|
26
|
+
const observable = $observables.get(id);
|
|
27
|
+
if(!observable) {
|
|
28
|
+
$observables.delete(id);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return observable.deref();
|
|
27
32
|
},
|
|
28
33
|
cleanup() {
|
|
29
34
|
for (const [_, weakObservableRef] of $observables) {
|
|
@@ -124,6 +124,9 @@ ObservableItem.prototype.triggerListeners = function(operations) {
|
|
|
124
124
|
* @param {{ action?: string, args?: any[], result?: any }} [operations] - Mutation metadata
|
|
125
125
|
*/
|
|
126
126
|
ObservableItem.prototype.triggerWatchers = function(operations) {
|
|
127
|
+
if(!this.$watchers) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
127
130
|
const $watchers = this.$watchers;
|
|
128
131
|
const $previousValue = this.$previousValue;
|
|
129
132
|
const $currentValue = this.$currentValue;
|
|
@@ -160,6 +163,7 @@ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations)
|
|
|
160
163
|
this.triggerFirstListener(operations);
|
|
161
164
|
};
|
|
162
165
|
|
|
166
|
+
|
|
163
167
|
/**
|
|
164
168
|
* Selects and assigns the optimal trigger strategy based on the current
|
|
165
169
|
* combination of listeners and watchers (internal).
|
|
@@ -167,31 +171,40 @@ ObservableItem.prototype.triggerWatchersAndFirstListener = function(operations)
|
|
|
167
171
|
*
|
|
168
172
|
* @internal
|
|
169
173
|
*/
|
|
170
|
-
ObservableItem.prototype
|
|
174
|
+
ObservableItem.prototype.$runAssocTrigger = function() {
|
|
171
175
|
this.$firstListener = null;
|
|
172
176
|
if(this.$watchers?.size && this.$listeners?.length) {
|
|
173
|
-
this.$
|
|
174
|
-
this.trigger = this.$firstListener.length === 0 ? this.$firstListener : this.triggerFirstListener;
|
|
175
|
-
this.trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
|
|
177
|
+
this.$trigger = (this.$listeners.length === 1) ? this.triggerWatchersAndFirstListener : this.triggerAll;
|
|
176
178
|
return;
|
|
177
179
|
}
|
|
178
180
|
if(this.$listeners?.length) {
|
|
179
181
|
if(this.$listeners.length === 1) {
|
|
180
182
|
this.$firstListener = this.$listeners[0];
|
|
181
|
-
this
|
|
183
|
+
this.$trigger = this.$firstListener.length === 0 ? this.$firstListener : this.triggerFirstListener;
|
|
182
184
|
}
|
|
183
185
|
else {
|
|
184
|
-
this
|
|
186
|
+
this.$trigger = this.triggerListeners;
|
|
185
187
|
}
|
|
186
188
|
return;
|
|
187
189
|
}
|
|
188
190
|
if(this.$watchers?.size) {
|
|
189
|
-
this
|
|
191
|
+
this.$trigger = this.triggerWatchers;
|
|
190
192
|
return;
|
|
191
193
|
}
|
|
192
|
-
this
|
|
194
|
+
this.$trigger = noneTrigger;
|
|
193
195
|
};
|
|
194
|
-
ObservableItem.prototype.
|
|
196
|
+
ObservableItem.prototype.assocTrigger = function() {
|
|
197
|
+
this.$trigger = null;
|
|
198
|
+
};
|
|
199
|
+
Object.defineProperty(ObservableItem.prototype, 'trigger', {
|
|
200
|
+
value: function (operations) {
|
|
201
|
+
if(!this.$trigger) {
|
|
202
|
+
this.$runAssocTrigger();
|
|
203
|
+
}
|
|
204
|
+
return this.$trigger(operations);
|
|
205
|
+
},
|
|
206
|
+
configurable: false,
|
|
207
|
+
});
|
|
195
208
|
|
|
196
209
|
|
|
197
210
|
const $setOperation = { action: 'set' };
|
|
@@ -543,7 +556,12 @@ ObservableItem.prototype.reset = function() {
|
|
|
543
556
|
* @returns {string} String representation of the current value
|
|
544
557
|
*/
|
|
545
558
|
ObservableItem.prototype.toString = function() {
|
|
546
|
-
|
|
559
|
+
if(this.$id) {
|
|
560
|
+
return this.$id.toString();
|
|
561
|
+
}
|
|
562
|
+
const id = MemoryManager.register(this);
|
|
563
|
+
this.$id = id;
|
|
564
|
+
return '{{obs:'+id+'}}';
|
|
547
565
|
};
|
|
548
566
|
|
|
549
567
|
/**
|
|
@@ -2,6 +2,7 @@ import Validator from '../../utils/validator';
|
|
|
2
2
|
import {ElementCreator} from '../../wrappers/ElementCreator';
|
|
3
3
|
import AnchorWithSentinel from './anchor-with-sentinel';
|
|
4
4
|
import oneChildAnchorOverwriting from './one-child-anchor-overwriting';
|
|
5
|
+
import DebugManager from '../../utils/debug-manager';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Creates an anchor fragment — a managed DocumentFragment delimited by comment sentinels.
|
|
@@ -34,7 +35,7 @@ export default function Anchor(name, isUniqueChild = false) {
|
|
|
34
35
|
? () => true: (parent) => (parent.firstChild === anchorStart && parent.lastChild === anchorEnd);
|
|
35
36
|
|
|
36
37
|
const insertBefore = (parent, child, target) => {
|
|
37
|
-
const childElement =
|
|
38
|
+
const childElement = child.__$isNativeNode ? child : ElementCreator.getChild(child);
|
|
38
39
|
insertBeforeRaw(parent, childElement, target);
|
|
39
40
|
};
|
|
40
41
|
|
|
@@ -84,7 +85,7 @@ export default function Anchor(name, isUniqueChild = false) {
|
|
|
84
85
|
anchorFragment.appendRaw = anchorFragment.appendChildRaw;
|
|
85
86
|
|
|
86
87
|
anchorFragment.insertAtStart = function(child) {
|
|
87
|
-
child =
|
|
88
|
+
child = child.__$isNativeNode? child : ElementCreator.getChild(child);
|
|
88
89
|
anchorFragment.insertAtStartRaw(child);
|
|
89
90
|
};
|
|
90
91
|
|
|
@@ -141,7 +142,7 @@ export default function Anchor(name, isUniqueChild = false) {
|
|
|
141
142
|
anchorFragment.delete = anchorFragment.removeWithAnchors;
|
|
142
143
|
|
|
143
144
|
anchorFragment.replaceContent = function(child) {
|
|
144
|
-
const childElement =
|
|
145
|
+
const childElement = child.__$isNativeNode ? child : ElementCreator.getChild(child);
|
|
145
146
|
anchorFragment.replaceContentRaw(childElement);
|
|
146
147
|
};
|
|
147
148
|
|
|
@@ -10,7 +10,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
|
|
|
10
10
|
anchor.getParent = () => parent;
|
|
11
11
|
|
|
12
12
|
anchor.appendChild = (child) => {
|
|
13
|
-
child =
|
|
13
|
+
child = child.__$isNativeNode ? child : ElementCreator.getChild(child);
|
|
14
14
|
parent.appendChild(child);
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -19,7 +19,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
|
|
|
19
19
|
anchor.appendRaw = anchor.appendChildRaw;
|
|
20
20
|
|
|
21
21
|
anchor.insertAtStart = (child) => {
|
|
22
|
-
child =
|
|
22
|
+
child = child.__$isNativeNode ? child : ElementCreator.getChild(child);
|
|
23
23
|
parent.firstChild ? parent.insertBefore(child, parent.firstChild) : parent.appendChild(child);
|
|
24
24
|
};
|
|
25
25
|
anchor.insertAtStartRaw = (child) => {
|
|
@@ -33,7 +33,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
anchor.replaceContent = function(content) {
|
|
36
|
-
const child =
|
|
36
|
+
const child = (content && content.__$isNativeNode) ? content : ElementCreator.getChild(content);
|
|
37
37
|
parent.replaceChildren(child);
|
|
38
38
|
};
|
|
39
39
|
|
|
@@ -43,7 +43,7 @@ export default function oneChildAnchorOverwriting(anchor, parent) {
|
|
|
43
43
|
anchor.setContent = anchor.replaceContent;
|
|
44
44
|
|
|
45
45
|
anchor.insertBefore = (child, anchor) => {
|
|
46
|
-
child =
|
|
46
|
+
child = child.__$isNativeNode ? child : ElementCreator.getChild(child);
|
|
47
47
|
parent.insertBefore(child, anchor);
|
|
48
48
|
};
|
|
49
49
|
anchor.insertBeforeRaw = (child, anchor) => {
|
|
@@ -24,6 +24,25 @@ export const Label = HtmlElementWrapper('label');
|
|
|
24
24
|
*/
|
|
25
25
|
export const P = HtmlElementWrapper('p');
|
|
26
26
|
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parses a string containing Observable placeholders and returns an array
|
|
30
|
+
* of strings and Observable instances ready to be used as NativeDocument children.
|
|
31
|
+
*
|
|
32
|
+
* Observable placeholders are generated automatically when an ObservableItem
|
|
33
|
+
* is interpolated in a template string via its toString() method.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} string - Template string with optional Observable placeholders
|
|
36
|
+
* @returns {Array<string|ObservableItem>} Mixed array of static strings and resolved Observables
|
|
37
|
+
* @example
|
|
38
|
+
* const name = Observable('John');
|
|
39
|
+
* const count = Observable(5);
|
|
40
|
+
*
|
|
41
|
+
* Text(`Hello ${name}, count: ${count}`)
|
|
42
|
+
* // → ['Hello ', ObservableItem(John), ', count: ', ObservableItem(5)]
|
|
43
|
+
*/
|
|
44
|
+
export const Text = (string) => string.toNdChildren();
|
|
45
|
+
|
|
27
46
|
/**
|
|
28
47
|
* Alias for {@link P}.
|
|
29
48
|
* @type {function(GlobalAttributes=, NdChild|NdChild[]=): HTMLParagraphElement}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export default function ForEachArrayCache(isIndexesRequired) {
|
|
4
|
+
this.$nodes = new Map();
|
|
5
|
+
this.$indexes = isIndexesRequired ? new Map() : null;
|
|
6
|
+
|
|
7
|
+
this.has = this.$nodes.has.bind(this.$nodes);
|
|
8
|
+
|
|
9
|
+
this.keys = function() {
|
|
10
|
+
return Array.from(this.$nodes.keys());
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
if(isIndexesRequired) {
|
|
14
|
+
this.delete = function(item) {
|
|
15
|
+
this.$nodes.get(item)?.nd.destroy();
|
|
16
|
+
this.$nodes.delete(item);
|
|
17
|
+
this.$indexes.delete(item);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
this.set = function(item, child, index) {
|
|
21
|
+
this.$nodes.set(item, child);
|
|
22
|
+
this.$indexes.set(item, index);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
this.clear = function() {
|
|
26
|
+
for(const [_, node] in this.$nodes.entries()) {
|
|
27
|
+
node.nd.destroy();
|
|
28
|
+
}
|
|
29
|
+
this.$nodes.clear();
|
|
30
|
+
this.$indexes.clear();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
this.get = function(item) {
|
|
34
|
+
return {
|
|
35
|
+
child: this.$nodes.get(item),
|
|
36
|
+
indexObserver: this.$indexes.get(item),
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
} else {
|
|
40
|
+
|
|
41
|
+
this.delete = (item) => {
|
|
42
|
+
this.$nodes.get(item)?.nd.destroy();
|
|
43
|
+
this.$nodes.delete(item);
|
|
44
|
+
};
|
|
45
|
+
this.set = this.$nodes.set.bind(this.$nodes);
|
|
46
|
+
this.clear = function() {
|
|
47
|
+
for(const [_, node] in this.$nodes.entries()) {
|
|
48
|
+
node.nd.destroy();
|
|
49
|
+
}
|
|
50
|
+
this.$nodes.clear();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
this.get = function(item) {
|
|
54
|
+
return { child: this.$nodes.get(item), indexObserver: null };
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Anchor from '../anchor/anchor';
|
|
2
2
|
import { ElementCreator } from '../../wrappers/ElementCreator';
|
|
3
3
|
import NativeDocumentError from '../../errors/NativeDocumentError';
|
|
4
|
+
import ForEachArrayCache from './for-each-array-cache';
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
const CREATE_AND_CACHE_ACTIONS = new Set(['clear', 'push', 'unshift', 'replace']);
|
|
@@ -30,9 +31,9 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
30
31
|
const element = Anchor('ForEach Array', configs.isParentUniqueChild);
|
|
31
32
|
const blockEnd = element.endElement();
|
|
32
33
|
|
|
33
|
-
const cache = new Map();
|
|
34
34
|
let lastNumberOfItems = 0;
|
|
35
35
|
const isIndexRequired = callback.length >= 2;
|
|
36
|
+
const cache = new ForEachArrayCache(isIndexRequired);
|
|
36
37
|
|
|
37
38
|
const clear = (items) => {
|
|
38
39
|
element.removeChildren();
|
|
@@ -64,7 +65,7 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
64
65
|
throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
cache.set(item,
|
|
68
|
+
cache.set(item, child);
|
|
68
69
|
return child;
|
|
69
70
|
};
|
|
70
71
|
|
|
@@ -76,7 +77,7 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
76
77
|
throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
cache.set(item,
|
|
80
|
+
cache.set(item, child, indexObserver);
|
|
80
81
|
return child;
|
|
81
82
|
};
|
|
82
83
|
if(!data.__$Observable) {
|
|
@@ -87,7 +88,7 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
87
88
|
throw new NativeDocumentError('ForEachArray child can\'t be null or undefined!');
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
cache.set(item,
|
|
91
|
+
cache.set(item, child);
|
|
91
92
|
return child;
|
|
92
93
|
};
|
|
93
94
|
}
|
|
@@ -150,14 +151,14 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
150
151
|
if(Array.isArray(data)) {
|
|
151
152
|
set = () => {
|
|
152
153
|
clear(data);
|
|
153
|
-
|
|
154
|
+
Actions.appendToElement(data);
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
else {
|
|
157
158
|
set = () => {
|
|
158
159
|
const items = data.val();
|
|
159
160
|
clear(items);
|
|
160
|
-
|
|
161
|
+
Actions.appendToElement(items);
|
|
161
162
|
};
|
|
162
163
|
}
|
|
163
164
|
|
|
@@ -166,45 +167,36 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
166
167
|
const fragment = document.createDocumentFragment();
|
|
167
168
|
for(let i = 0, length = items.length; i < length; i++) {
|
|
168
169
|
fragment.appendChild(buildItem(items[i], lastNumberOfItems));
|
|
169
|
-
lastNumberOfItems++;
|
|
170
170
|
}
|
|
171
171
|
return fragment;
|
|
172
172
|
},
|
|
173
|
+
appendToElement: (items) => {
|
|
174
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
175
|
+
element.appendChild(buildItem(items[i], i));
|
|
176
|
+
}
|
|
177
|
+
},
|
|
173
178
|
add: (items) => {
|
|
174
|
-
|
|
179
|
+
Actions.appendToElement(items);
|
|
175
180
|
},
|
|
176
181
|
replace: (items) => {
|
|
177
182
|
clear(items);
|
|
178
|
-
|
|
183
|
+
Actions.appendToElement(items);
|
|
179
184
|
},
|
|
180
185
|
set,
|
|
181
186
|
reOrder: (items) => {
|
|
182
187
|
let child = null;
|
|
183
|
-
const fragment = document.createDocumentFragment();
|
|
184
188
|
for(const item of items) {
|
|
185
189
|
child = getItemChild(item);
|
|
186
190
|
if(child) {
|
|
187
|
-
|
|
191
|
+
element.appendChild(child);
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
child = null;
|
|
191
|
-
element.appendChildRaw(fragment);
|
|
192
195
|
},
|
|
193
196
|
removeOne: (element, index) => {
|
|
194
197
|
removeCacheItem(element, true);
|
|
195
198
|
},
|
|
196
199
|
clear,
|
|
197
|
-
populate: ([target, iteration, callback]) => {
|
|
198
|
-
const fragment = document.createDocumentFragment();
|
|
199
|
-
for (let i = 0; i < iteration; i++) {
|
|
200
|
-
const data = callback(i);
|
|
201
|
-
target.push(data);
|
|
202
|
-
fragment.append(buildItem(data, i));
|
|
203
|
-
lastNumberOfItems++;
|
|
204
|
-
}
|
|
205
|
-
element.appendChildRaw(fragment);
|
|
206
|
-
fragment.replaceChildren();
|
|
207
|
-
},
|
|
208
200
|
unshift: (values) => {
|
|
209
201
|
element.insertAtStartRaw(Actions.toFragment(values));
|
|
210
202
|
},
|
|
@@ -1,35 +1,8 @@
|
|
|
1
1
|
import Validator from '../utils/validator';
|
|
2
|
-
import NativeDocumentError from '../errors/NativeDocumentError';
|
|
3
2
|
import {BOOL_ATTRIBUTES_NAME, BOOLEAN_ATTRIBUTES} from './constants.js';
|
|
4
3
|
import {Observable} from '../data/Observable';
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
const addNdDestroy = (element, observable, callback) => {
|
|
8
|
-
if(element.ndDestroy) {
|
|
9
|
-
element.ndAddDestroy(observable, callback);
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
let destroyCallbacks = [];
|
|
13
|
-
|
|
14
|
-
element.ndAddDestroy = (observable, callback) => {
|
|
15
|
-
destroyCallbacks.push(observable);
|
|
16
|
-
destroyCallbacks.push(callback);
|
|
17
|
-
};
|
|
18
|
-
element.ndDestroy = () => {
|
|
19
|
-
|
|
20
|
-
for(let i = 0, len = destroyCallbacks.length; i < len; i+= 2) {
|
|
21
|
-
destroyCallbacks[i].unsubscribe(destroyCallbacks[i+1]);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
destroyCallbacks.length = 0;
|
|
25
|
-
destroyCallbacks = null;
|
|
26
|
-
element.ndAddDestroy = null;
|
|
27
|
-
element.ndDestroy = null;
|
|
28
|
-
element = null;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
element.ndAddDestroy(observable, callback);
|
|
32
|
-
};
|
|
33
6
|
|
|
34
7
|
/**
|
|
35
8
|
* Applies a reactive class map to an HTMLElement.
|
|
@@ -42,33 +15,28 @@ const addNdDestroy = (element, observable, callback) => {
|
|
|
42
15
|
export const bindClassAttribute = (element, data) => {
|
|
43
16
|
for(const className in data) {
|
|
44
17
|
const value = data[className];
|
|
45
|
-
|
|
46
18
|
if (value.__$isObservableChecker) {
|
|
47
19
|
let lastClass = value.val();
|
|
48
20
|
if (typeof lastClass === 'string') {
|
|
49
21
|
element.classes.toggle(lastClass, true);
|
|
50
|
-
|
|
22
|
+
value.subscribe((currentValue) => {
|
|
51
23
|
element.classes.remove(lastClass);
|
|
52
24
|
element.classes.toggle(currentValue, true);
|
|
53
25
|
lastClass = currentValue;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
addNdDestroy(element, value, callback);
|
|
57
|
-
continue;
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
58
28
|
}
|
|
59
29
|
}
|
|
60
30
|
|
|
61
31
|
if (value.__$Observable) {
|
|
62
32
|
element.classes.toggle(className, value.val());
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
addNdDestroy(element, value, callback);
|
|
66
|
-
continue;
|
|
33
|
+
value.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
|
|
34
|
+
return;
|
|
67
35
|
}
|
|
68
36
|
|
|
69
37
|
if (value.$hydrate) {
|
|
70
38
|
value.$hydrate(element, className);
|
|
71
|
-
|
|
39
|
+
return;
|
|
72
40
|
}
|
|
73
41
|
|
|
74
42
|
element.classes.toggle(className, value);
|
|
@@ -92,26 +60,22 @@ export const bindStyleAttribute = (element, data) => {
|
|
|
92
60
|
if(value.__$Observable) {
|
|
93
61
|
if(isCustomProperty) {
|
|
94
62
|
element.style.setProperty(styleName, value.val());
|
|
95
|
-
|
|
63
|
+
value.subscribe((newValue) => {
|
|
96
64
|
if(newValue === false) {
|
|
97
65
|
element.style.removeProperty(styleName);
|
|
98
66
|
return;
|
|
99
67
|
}
|
|
100
68
|
element.style.setProperty(styleName, newValue);
|
|
101
|
-
};
|
|
102
|
-
value.subscribe(callback);
|
|
103
|
-
addNdDestroy(element, value, callback);
|
|
69
|
+
});
|
|
104
70
|
} else {
|
|
105
71
|
element.style[styleName] = value.val();
|
|
106
|
-
|
|
72
|
+
value.subscribe((newValue) => {
|
|
107
73
|
if(newValue === false) {
|
|
108
74
|
element.style.removeProperty(styleName);
|
|
109
75
|
return;
|
|
110
76
|
}
|
|
111
77
|
element.style[styleName] = newValue;
|
|
112
|
-
};
|
|
113
|
-
value.subscribe(callback);
|
|
114
|
-
addNdDestroy(element, value, callback);
|
|
78
|
+
});
|
|
115
79
|
}
|
|
116
80
|
continue;
|
|
117
81
|
}
|
|
@@ -135,7 +99,7 @@ export const bindBooleanAttribute = (element, attributeName, value) => {
|
|
|
135
99
|
const isObservable = value.__$Observable;
|
|
136
100
|
const defaultValue = isObservable? value.val() : value;
|
|
137
101
|
|
|
138
|
-
const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName];
|
|
102
|
+
const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName] || attributeName;
|
|
139
103
|
|
|
140
104
|
if(typeof defaultValue === 'boolean') {
|
|
141
105
|
element[attributeRealName] = defaultValue;
|
|
@@ -151,14 +115,10 @@ export const bindBooleanAttribute = (element, attributeName, value) => {
|
|
|
151
115
|
else {
|
|
152
116
|
element.addEventListener('input', () => value.set(element.value));
|
|
153
117
|
}
|
|
154
|
-
|
|
155
|
-
value.subscribe(callback);
|
|
156
|
-
addNdDestroy(element, value, callback);
|
|
118
|
+
value.subscribe((newValue) => element[attributeRealName] = newValue);
|
|
157
119
|
return;
|
|
158
120
|
}
|
|
159
|
-
|
|
160
|
-
value.subscribe(callback);
|
|
161
|
-
addNdDestroy(element, value, callback);
|
|
121
|
+
value.subscribe((newValue) => element[attributeRealName] = (newValue === element.value));
|
|
162
122
|
}
|
|
163
123
|
};
|
|
164
124
|
|
|
@@ -173,15 +133,11 @@ export const bindAttributeWithObservable = (element, attributeName, value) => {
|
|
|
173
133
|
if(attributeName === 'value') {
|
|
174
134
|
element.value = value.val();
|
|
175
135
|
element.addEventListener('input', () => value.set(element.value));
|
|
176
|
-
|
|
177
|
-
value.subscribe(callback);
|
|
178
|
-
addNdDestroy(element, value, callback);
|
|
136
|
+
value.subscribe((newValue) => element.value = newValue);
|
|
179
137
|
return;
|
|
180
138
|
}
|
|
181
139
|
element.setAttribute(attributeName, value.val());
|
|
182
|
-
|
|
183
|
-
value.subscribe(callback);
|
|
184
|
-
addNdDestroy(element, value, callback);
|
|
140
|
+
value.subscribe((newValue) => element.setAttribute(attributeName, newValue));
|
|
185
141
|
};
|
|
186
142
|
|
|
187
143
|
/**
|
|
@@ -189,29 +145,29 @@ export const bindAttributeWithObservable = (element, attributeName, value) => {
|
|
|
189
145
|
* @param {HTMLElement} element
|
|
190
146
|
* @param {Object} attributes
|
|
191
147
|
*/
|
|
192
|
-
const AttributesWrapper = (element, attributes) => {
|
|
148
|
+
const AttributesWrapper = (element, attributes = {}) => {
|
|
193
149
|
|
|
194
150
|
if(process.env.NODE_ENV === 'development') {
|
|
195
151
|
Validator.validateAttributes(attributes);
|
|
196
152
|
}
|
|
197
153
|
|
|
198
|
-
for(const
|
|
199
|
-
const value = attributes[
|
|
154
|
+
for(const attributeName in attributes) {
|
|
155
|
+
const value = attributes[attributeName];
|
|
200
156
|
if(value == null) {
|
|
201
157
|
continue;
|
|
202
158
|
}
|
|
203
159
|
const type = typeof value;
|
|
204
160
|
if(type === 'string' || type === 'number') {
|
|
205
|
-
element.setAttribute(
|
|
161
|
+
element.setAttribute(attributeName, value);
|
|
206
162
|
continue;
|
|
207
163
|
}
|
|
208
|
-
const attributeName = originalAttributeName.toLowerCase();
|
|
164
|
+
// const attributeName = originalAttributeName.toLowerCase();
|
|
209
165
|
if(value.__$Observable) {
|
|
210
166
|
if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
|
|
211
167
|
bindBooleanAttribute(element, attributeName, value);
|
|
212
168
|
continue;
|
|
213
169
|
}
|
|
214
|
-
bindAttributeWithObservable(element,
|
|
170
|
+
bindAttributeWithObservable(element, attributeName, value);
|
|
215
171
|
continue;
|
|
216
172
|
}
|
|
217
173
|
if(type === 'object') {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import Validator from '../utils/validator';
|
|
2
1
|
import AttributesWrapper, { bindClassAttribute, bindStyleAttribute } from './AttributesWrapper';
|
|
3
2
|
import PluginsManager from '../utils/plugins-manager';
|
|
4
|
-
import {call} from '@babel/traverse/lib/path/context';
|
|
5
3
|
|
|
6
4
|
let $textNodeCache = null;
|
|
7
5
|
|
|
@@ -9,7 +7,7 @@ export const ElementCreator = {
|
|
|
9
7
|
createTextNode() {
|
|
10
8
|
if(!$textNodeCache) {
|
|
11
9
|
$textNodeCache = document.createTextNode('');
|
|
12
|
-
ElementCreator.createTextNode =
|
|
10
|
+
ElementCreator.createTextNode = $textNodeCache.cloneNode.bind($textNodeCache);
|
|
13
11
|
}
|
|
14
12
|
return $textNodeCache.cloneNode();
|
|
15
13
|
},
|
|
@@ -21,9 +19,7 @@ export const ElementCreator = {
|
|
|
21
19
|
*/
|
|
22
20
|
createObservableNode: (parent, observable) => {
|
|
23
21
|
const text = ElementCreator.createObservableNodeWithoutParent(observable);
|
|
24
|
-
|
|
25
|
-
parent.appendChild(text);
|
|
26
|
-
}
|
|
22
|
+
parent && parent.appendChild(text);
|
|
27
23
|
return text;
|
|
28
24
|
},
|
|
29
25
|
/**
|
|
@@ -33,19 +29,8 @@ export const ElementCreator = {
|
|
|
33
29
|
*/
|
|
34
30
|
createObservableNodeWithoutParent: (observable) => {
|
|
35
31
|
const text = ElementCreator.createTextNode();
|
|
36
|
-
|
|
37
|
-
observable.subscribe(callback);
|
|
32
|
+
observable.subscribe(value => text.nodeValue = value);
|
|
38
33
|
text.nodeValue = observable.val();
|
|
39
|
-
let unsubscribe = () => {
|
|
40
|
-
observable.unsubscribe(callback);
|
|
41
|
-
observable = null;
|
|
42
|
-
};
|
|
43
|
-
text.ndDestroy = () => {
|
|
44
|
-
text.nodeValue = null;
|
|
45
|
-
unsubscribe();
|
|
46
|
-
unsubscribe = null;
|
|
47
|
-
text.ndDestroy = null;
|
|
48
|
-
};
|
|
49
34
|
return text;
|
|
50
35
|
},
|
|
51
36
|
/**
|
|
@@ -72,6 +57,11 @@ export const ElementCreator = {
|
|
|
72
57
|
parent && parent.appendChild(text);
|
|
73
58
|
return text;
|
|
74
59
|
},
|
|
60
|
+
createStaticTextNodeWithoutParent: (value) => {
|
|
61
|
+
const text = ElementCreator.createTextNode();
|
|
62
|
+
text.nodeValue = value;
|
|
63
|
+
return text;
|
|
64
|
+
},
|
|
75
65
|
/**
|
|
76
66
|
*
|
|
77
67
|
* @param {string} name
|
|
@@ -95,27 +85,37 @@ export const ElementCreator = {
|
|
|
95
85
|
* @param {HTMLElement|DocumentFragment} parent
|
|
96
86
|
*/
|
|
97
87
|
processChildren: (children, parent) => {
|
|
98
|
-
if(children === null) return;
|
|
99
88
|
if(process.env.NODE_ENV === 'development') {
|
|
100
89
|
PluginsManager.emit('BeforeProcessChildren', parent);
|
|
101
90
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
if(Array.isArray(children)) {
|
|
92
|
+
for(let i = 0, length = children.length; i < length; i++) {
|
|
93
|
+
const child = ElementCreator.getChild(children[i]);
|
|
94
|
+
if(child === null) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
parent.appendChild(child);
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
105
100
|
}
|
|
101
|
+
const child = ElementCreator.getChild(children);
|
|
102
|
+
parent.appendChild(child);
|
|
106
103
|
if(process.env.NODE_ENV === 'development') {
|
|
107
104
|
PluginsManager.emit('AfterProcessChildren', parent);
|
|
108
105
|
}
|
|
109
106
|
},
|
|
110
107
|
async safeRemove(element) {
|
|
111
108
|
await element.remove();
|
|
112
|
-
|
|
113
109
|
},
|
|
114
110
|
getChild: (child) => {
|
|
115
|
-
if (child == null)
|
|
111
|
+
if (child == null) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
116
114
|
|
|
117
115
|
child = child.toNdElement();
|
|
118
|
-
if (child instanceof Node)
|
|
116
|
+
if (child && (child instanceof Node)) {
|
|
117
|
+
return child;
|
|
118
|
+
}
|
|
119
119
|
|
|
120
120
|
while (child != null && !(child instanceof Node)) {
|
|
121
121
|
child = child.toNdElement?.();
|
|
@@ -36,10 +36,11 @@ export const createTextNode = (value) => {
|
|
|
36
36
|
* @returns {HTMLElement} The configured element
|
|
37
37
|
*/
|
|
38
38
|
export const createVoidHtmlElement = (element, attributes) => {
|
|
39
|
-
ElementCreator.processAttributes(element, attributes);
|
|
39
|
+
attributes && ElementCreator.processAttributes(element, attributes);
|
|
40
40
|
return element;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
|
|
43
44
|
const OBJECT_PROTOTYPE = Object.prototype;
|
|
44
45
|
/**
|
|
45
46
|
* Applies attributes and children to an existing HTMLElement.
|
|
@@ -53,13 +54,17 @@ const OBJECT_PROTOTYPE = Object.prototype;
|
|
|
53
54
|
*/
|
|
54
55
|
export const createHtmlElement = (element, _attributes, _children = null) => {
|
|
55
56
|
let attributes = _attributes, children = _children;
|
|
56
|
-
if(
|
|
57
|
+
if(_attributes?.__$isValidNdChild) { // If attributes is a ValidChild
|
|
57
58
|
attributes = _children;
|
|
58
59
|
children = _attributes;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
if(attributes) {
|
|
63
|
+
ElementCreator.processAttributes(element, attributes);
|
|
64
|
+
}
|
|
65
|
+
if(children) {
|
|
66
|
+
ElementCreator.processChildren(children, element);
|
|
67
|
+
}
|
|
63
68
|
return element;
|
|
64
69
|
};
|
|
65
70
|
|
|
@@ -195,27 +195,24 @@ NDElement.prototype.destroy = function() {
|
|
|
195
195
|
observer.disconnect();
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
const
|
|
198
|
+
const treewalk = document.createTreeWalker(this.$element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
199
|
+
const nodes = [];
|
|
200
|
+
while(treewalk.nextNode()) {
|
|
201
|
+
nodes.push(treewalk.currentNode);
|
|
202
|
+
}
|
|
199
203
|
|
|
200
|
-
for(let i = 0, length =
|
|
201
|
-
const child =
|
|
204
|
+
for(let i = 0, length = nodes.length; i < length; i++) {
|
|
205
|
+
const child = nodes[i];
|
|
202
206
|
child.remove();
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
const childObserver = $lifeCycleObservers.get(child);
|
|
208
|
+
if(childObserver) {
|
|
209
|
+
childObserver.disconnect();
|
|
210
|
+
}
|
|
205
211
|
$lifeCycleObservers.delete(child);
|
|
206
212
|
}
|
|
207
213
|
|
|
208
|
-
const treeWalker = document.createTreeWalker(this.$element, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
|
|
209
|
-
console.log(treeWalker);
|
|
210
|
-
while(treeWalker.nextNode()) {
|
|
211
|
-
const node = treeWalker.currentNode;
|
|
212
|
-
node.ndDestroy?.();
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
this.$element.ndDestroy?.();
|
|
216
|
-
this.$element.__$controller?.abort();
|
|
217
|
-
this.$element.__$controller = null;
|
|
218
214
|
$lifeCycleObservers.delete(this.$element);
|
|
215
|
+
this.$element.remove();
|
|
219
216
|
this.$element = null;
|
|
220
217
|
};
|
|
221
218
|
|
|
@@ -437,7 +434,6 @@ NDElement.prototype.style = function(style) {
|
|
|
437
434
|
return this;
|
|
438
435
|
};
|
|
439
436
|
|
|
440
|
-
|
|
441
437
|
/**
|
|
442
438
|
* Extends the NDElement prototype with new methods available to all NDElement instances.
|
|
443
439
|
* Use this to add global methods to all NDElements.
|
|
@@ -637,7 +633,7 @@ NDElement.prototype.contentEditable = function($obs, {format = 'html'} = {}) {
|
|
|
637
633
|
|
|
638
634
|
this.$element.addEventListener('input', () => {
|
|
639
635
|
$obs?.set(getValue());
|
|
640
|
-
}
|
|
636
|
+
});
|
|
641
637
|
|
|
642
638
|
return this;
|
|
643
639
|
};
|
|
@@ -20,65 +20,42 @@ Object.defineProperty(NDElement.prototype, 'nd', {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
// ----------------------------------------------------------------
|
|
25
24
|
// Events helpers
|
|
26
25
|
// ----------------------------------------------------------------
|
|
27
26
|
EVENTS.forEach(eventSourceName => {
|
|
28
27
|
const eventName = eventSourceName.toLowerCase();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const inlineHandler = 'on'+eventName;
|
|
29
|
+
NDElement.prototype['on'+eventSourceName] = function(callback = null, options = null) {
|
|
30
|
+
if(!this.$element[inlineHandler] && !options) {
|
|
31
|
+
this.$element[inlineHandler] = callback;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
this.$element.addEventListener(eventName, callback, options);
|
|
34
35
|
return this;
|
|
35
36
|
};
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
EVENTS_WITH_STOP.forEach(eventSourceName => {
|
|
39
40
|
const eventName = eventSourceName.toLowerCase();
|
|
40
|
-
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options =
|
|
41
|
-
_stop(this.$element, eventName, callback,
|
|
42
|
-
signal: this.$getSignal(),
|
|
43
|
-
...options,
|
|
44
|
-
});
|
|
41
|
+
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options = null) {
|
|
42
|
+
_stop(this.$element, eventName, callback, options);
|
|
45
43
|
return this;
|
|
46
44
|
};
|
|
47
|
-
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options =
|
|
48
|
-
_preventStop(this.$element, eventName, callback,
|
|
49
|
-
signal: this.$getSignal(),
|
|
50
|
-
...options,
|
|
51
|
-
});
|
|
45
|
+
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options = null) {
|
|
46
|
+
_preventStop(this.$element, eventName, callback, options);
|
|
52
47
|
return this;
|
|
53
48
|
};
|
|
54
49
|
});
|
|
55
50
|
|
|
56
51
|
EVENTS_WITH_PREVENT.forEach(eventSourceName => {
|
|
57
52
|
const eventName = eventSourceName.toLowerCase();
|
|
58
|
-
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options =
|
|
59
|
-
_prevent(this.$element, eventName, callback,
|
|
60
|
-
signal: this.$getSignal(),
|
|
61
|
-
...options,
|
|
62
|
-
});
|
|
53
|
+
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options = null) {
|
|
54
|
+
_prevent(this.$element, eventName, callback, options);
|
|
63
55
|
return this;
|
|
64
56
|
};
|
|
65
57
|
});
|
|
66
58
|
|
|
67
|
-
/**
|
|
68
|
-
* Retrieves or creates an AbortController signal tied to this element's lifecycle.
|
|
69
|
-
* The signal is automatically aborted when the element is removed via .nd.remove().
|
|
70
|
-
* Used internally by all event listeners (onClick, onInput, etc.) to auto-cleanup on unmount.
|
|
71
|
-
*
|
|
72
|
-
* @internal
|
|
73
|
-
* @returns {AbortSignal} The signal for this element's AbortController
|
|
74
|
-
*/
|
|
75
|
-
NDElement.prototype.$getSignal = function() {
|
|
76
|
-
if(!this.$element.__$controller) {
|
|
77
|
-
this.$element.__$controller = new AbortController();
|
|
78
|
-
}
|
|
79
|
-
return this.$element.__$controller.signal;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
59
|
|
|
83
60
|
/**
|
|
84
61
|
* Adds a native event listener to the underlying HTMLElement.
|
|
@@ -91,7 +68,6 @@ NDElement.prototype.$getSignal = function() {
|
|
|
91
68
|
*/
|
|
92
69
|
NDElement.prototype.on = function(name, callback, options) {
|
|
93
70
|
this.$element.addEventListener(name.toLowerCase(), callback, {
|
|
94
|
-
signal: this.$getSignal(),
|
|
95
71
|
...options,
|
|
96
72
|
});
|
|
97
73
|
return this;
|
|
@@ -119,7 +95,6 @@ NDElement.prototype.off = function(name, callback) {
|
|
|
119
95
|
*/
|
|
120
96
|
NDElement.prototype.once = function(name, callback) {
|
|
121
97
|
this.$element.addEventListener(name.toLowerCase(), callback, {
|
|
122
|
-
signal: this.$getSignal(),
|
|
123
98
|
once: true,
|
|
124
99
|
});
|
|
125
100
|
return this;
|
|
@@ -151,8 +126,13 @@ const _prevent = function(element, eventName, callback, options) {
|
|
|
151
126
|
event.preventDefault();
|
|
152
127
|
callback && callback.call(element, event);
|
|
153
128
|
};
|
|
129
|
+
|
|
130
|
+
const inlineHandler = 'on' + eventName;
|
|
131
|
+
if(!element[inlineHandler] && !options) {
|
|
132
|
+
element[inlineHandler] = handler;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
154
135
|
element.addEventListener(eventName, handler, options);
|
|
155
|
-
return this;
|
|
156
136
|
};
|
|
157
137
|
|
|
158
138
|
const _stop = function(element, eventName, callback, options) {
|
|
@@ -160,6 +140,13 @@ const _stop = function(element, eventName, callback, options) {
|
|
|
160
140
|
event.stopPropagation();
|
|
161
141
|
callback && callback.call(element, event);
|
|
162
142
|
};
|
|
143
|
+
|
|
144
|
+
const inlineHandler = 'on' + eventName;
|
|
145
|
+
if(!element[inlineHandler] && !options) {
|
|
146
|
+
element[inlineHandler] = handler;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
163
150
|
element.addEventListener(eventName, handler, options);
|
|
164
151
|
return this;
|
|
165
152
|
};
|
|
@@ -170,6 +157,11 @@ const _preventStop = function(element, eventName, callback, options) {
|
|
|
170
157
|
event.preventDefault();
|
|
171
158
|
callback && callback.call(element, event);
|
|
172
159
|
};
|
|
160
|
+
const inlineHandler = 'on' + eventName;
|
|
161
|
+
if(!element[inlineHandler] && !options) {
|
|
162
|
+
element[inlineHandler] = handler;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
173
165
|
element.addEventListener(eventName, handler, options);
|
|
174
166
|
return this;
|
|
175
167
|
};
|
|
@@ -3,19 +3,19 @@ export const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
3
3
|
'checked',
|
|
4
4
|
'selected',
|
|
5
5
|
'disabled',
|
|
6
|
-
'readonly',
|
|
6
|
+
'readonly', 'readOnly',
|
|
7
7
|
'required',
|
|
8
|
-
'autofocus',
|
|
8
|
+
'autofocus', 'autoFocus',
|
|
9
9
|
'multiple',
|
|
10
|
-
'autocomplete',
|
|
10
|
+
'autocomplete', 'autoComplete',
|
|
11
11
|
'hidden',
|
|
12
|
-
'contenteditable',
|
|
13
|
-
'spellcheck',
|
|
12
|
+
'contenteditable', 'contentEditable',
|
|
13
|
+
'spellcheck', 'spellCheck',
|
|
14
14
|
'translate',
|
|
15
15
|
'draggable',
|
|
16
16
|
'async',
|
|
17
17
|
'defer',
|
|
18
|
-
'autoplay',
|
|
18
|
+
'autoplay', 'autoPlay',
|
|
19
19
|
'controls',
|
|
20
20
|
'loop',
|
|
21
21
|
'muted',
|
|
@@ -23,13 +23,13 @@ export const BOOLEAN_ATTRIBUTES = new Set([
|
|
|
23
23
|
'reversed',
|
|
24
24
|
'open',
|
|
25
25
|
'default',
|
|
26
|
-
'formnovalidate',
|
|
27
|
-
'novalidate',
|
|
26
|
+
'formnovalidate', 'formNoValidate',
|
|
27
|
+
'novalidate', 'noValidate',
|
|
28
28
|
'scoped',
|
|
29
|
-
'itemscope',
|
|
30
|
-
'allowfullscreen',
|
|
31
|
-
'allowpaymentrequest',
|
|
32
|
-
'playsinline',
|
|
29
|
+
'itemscope', 'itemScope',
|
|
30
|
+
'allowfullscreen', 'allowFullscreen',
|
|
31
|
+
'allowpaymentrequest', 'allowPaymentRequest',
|
|
32
|
+
'playsinline', 'playsInline',
|
|
33
33
|
]);
|
|
34
34
|
|
|
35
35
|
export const BOOL_ATTRIBUTES_NAME = {
|
|
@@ -4,6 +4,43 @@ import TemplateBinding from '../TemplateBinding';
|
|
|
4
4
|
import { ElementCreator } from '../ElementCreator';
|
|
5
5
|
import PluginsManager from '../../utils/plugins-manager';
|
|
6
6
|
import ObservableChecker from '../../data/ObservableChecker';
|
|
7
|
+
import MemoryManager from '../../data/MemoryManager';
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const OBS_PATTERN = /({{obs:\d+}})/;
|
|
12
|
+
const OBS_ID_PATTERN = /\d+/;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parses a string containing Observable placeholders and returns an array
|
|
17
|
+
* of strings and Observable instances.
|
|
18
|
+
*
|
|
19
|
+
* Placeholders are generated automatically when an ObservableItem is interpolated
|
|
20
|
+
* in a template string via its toString() method.
|
|
21
|
+
*
|
|
22
|
+
* @returns {Array<string|ObservableItem>} Mixed array of static strings and resolved Observables
|
|
23
|
+
* @example
|
|
24
|
+
* const name = Observable('John');
|
|
25
|
+
* const count = Observable(5);
|
|
26
|
+
*
|
|
27
|
+
* `Hello ${name}, count: ${count}`.toNdChildren()
|
|
28
|
+
* // → ['Hello ', ObservableItem(John), ', count: ', ObservableItem(5)]
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(String.prototype, 'toNdChildren', {
|
|
31
|
+
value: function() {
|
|
32
|
+
return this.split(OBS_PATTERN).filter(Boolean).map((item) => {
|
|
33
|
+
if(OBS_PATTERN.test(item)) {
|
|
34
|
+
const id = item.match(OBS_ID_PATTERN)[0];
|
|
35
|
+
return MemoryManager.getObservableById(Number(id));
|
|
36
|
+
}
|
|
37
|
+
return item;
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
writable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
enumerable: false,
|
|
43
|
+
});
|
|
7
44
|
|
|
8
45
|
/**
|
|
9
46
|
* Registers a non-enumerable toNdElement method on a prototype.
|
|
@@ -20,9 +57,62 @@ const defineToNdElement = (target, fn) => {
|
|
|
20
57
|
});
|
|
21
58
|
};
|
|
22
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Marks a prototype as a valid NativeDocument child node.
|
|
62
|
+
*
|
|
63
|
+
* @param {object} target - The prototype to mark
|
|
64
|
+
*/
|
|
65
|
+
const defineValidNdChild = (target) => {
|
|
66
|
+
Object.defineProperty(target, '__$isValidNdChild', {
|
|
67
|
+
value: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
enumerable: false,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Marks a prototype as a native DOM node.
|
|
76
|
+
* Faster alternative to instanceof Node for hot paths.
|
|
77
|
+
*
|
|
78
|
+
* @param {object} target - The prototype to mark
|
|
79
|
+
*/
|
|
80
|
+
const defineNativeNode = (target) => {
|
|
81
|
+
Object.defineProperty(target, '__$isNativeNode', {
|
|
82
|
+
value: true,
|
|
83
|
+
writable: true,
|
|
84
|
+
configurable: true,
|
|
85
|
+
enumerable: false,
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
|
|
23
89
|
NDElement.$getChild = ElementCreator.getChild;
|
|
24
90
|
|
|
25
|
-
// --
|
|
91
|
+
// -- __$isNativeNode ----------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
defineNativeNode(Element.prototype);
|
|
94
|
+
defineNativeNode(Text.prototype);
|
|
95
|
+
defineNativeNode(Comment.prototype);
|
|
96
|
+
defineNativeNode(Document.prototype);
|
|
97
|
+
defineNativeNode(DocumentFragment.prototype);
|
|
98
|
+
|
|
99
|
+
// -- __$isValidNdChild --------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
defineValidNdChild(String.prototype);
|
|
102
|
+
defineValidNdChild(Number.prototype);
|
|
103
|
+
defineValidNdChild(Boolean.prototype);
|
|
104
|
+
defineValidNdChild(Array.prototype);
|
|
105
|
+
defineValidNdChild(Element.prototype);
|
|
106
|
+
defineValidNdChild(Text.prototype);
|
|
107
|
+
defineValidNdChild(Comment.prototype);
|
|
108
|
+
defineValidNdChild(DocumentFragment.prototype);
|
|
109
|
+
defineValidNdChild(NDElement.prototype);
|
|
110
|
+
defineValidNdChild(ObservableItem.prototype);
|
|
111
|
+
defineValidNdChild(ObservableChecker.prototype);
|
|
112
|
+
defineValidNdChild(TemplateBinding.prototype);
|
|
113
|
+
defineValidNdChild(Function.prototype);
|
|
114
|
+
|
|
115
|
+
// -- toNdElement --------------------------------------------------------------
|
|
26
116
|
|
|
27
117
|
/**
|
|
28
118
|
* Converts a string to a static text node.
|
|
@@ -30,22 +120,18 @@ NDElement.$getChild = ElementCreator.getChild;
|
|
|
30
120
|
* @returns {Text}
|
|
31
121
|
*/
|
|
32
122
|
defineToNdElement(String.prototype, function () {
|
|
33
|
-
return ElementCreator.
|
|
123
|
+
return ElementCreator.createStaticTextNodeWithoutParent(this);
|
|
34
124
|
});
|
|
35
125
|
|
|
36
|
-
// -- Number -------------------------------------------------------------------
|
|
37
|
-
|
|
38
126
|
/**
|
|
39
127
|
* Converts a number to a static text node.
|
|
40
128
|
*
|
|
41
129
|
* @returns {Text}
|
|
42
130
|
*/
|
|
43
131
|
defineToNdElement(Number.prototype, function () {
|
|
44
|
-
return ElementCreator.
|
|
132
|
+
return ElementCreator.createStaticTextNodeWithoutParent(this.toString());
|
|
45
133
|
});
|
|
46
134
|
|
|
47
|
-
// -- DOM nodes (identity) -----------------------------------------------------
|
|
48
|
-
|
|
49
135
|
/**
|
|
50
136
|
* Returns the DOM node itself (identity).
|
|
51
137
|
*
|
|
@@ -57,8 +143,6 @@ defineToNdElement(Comment.prototype, function () { return this; });
|
|
|
57
143
|
defineToNdElement(Document.prototype, function () { return this; });
|
|
58
144
|
defineToNdElement(DocumentFragment.prototype, function () { return this; });
|
|
59
145
|
|
|
60
|
-
// -- ObservableItem -----------------------------------------------------------
|
|
61
|
-
|
|
62
146
|
/**
|
|
63
147
|
* Converts an ObservableItem to a reactive text node that updates when the value changes.
|
|
64
148
|
*
|
|
@@ -70,8 +154,6 @@ defineToNdElement(ObservableItem.prototype, function () {
|
|
|
70
154
|
|
|
71
155
|
ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
|
|
72
156
|
|
|
73
|
-
// -- NDElement ----------------------------------------------------------------
|
|
74
|
-
|
|
75
157
|
/**
|
|
76
158
|
* Returns the underlying HTMLElement of this NDElement.
|
|
77
159
|
*
|
|
@@ -81,8 +163,6 @@ defineToNdElement(NDElement.prototype, function () {
|
|
|
81
163
|
return this.$element;
|
|
82
164
|
});
|
|
83
165
|
|
|
84
|
-
// -- Array --------------------------------------------------------------------
|
|
85
|
-
|
|
86
166
|
/**
|
|
87
167
|
* Converts the array to a DocumentFragment containing all child elements.
|
|
88
168
|
*
|
|
@@ -98,8 +178,6 @@ defineToNdElement(Array.prototype, function () {
|
|
|
98
178
|
return fragment;
|
|
99
179
|
});
|
|
100
180
|
|
|
101
|
-
// -- Function -----------------------------------------------------------------
|
|
102
|
-
|
|
103
181
|
/**
|
|
104
182
|
* Calls the function and converts its return value to a DOM node.
|
|
105
183
|
*
|
|
@@ -113,8 +191,6 @@ defineToNdElement(Function.prototype, function () {
|
|
|
113
191
|
return ElementCreator.getChild(child());
|
|
114
192
|
});
|
|
115
193
|
|
|
116
|
-
// -- TemplateBinding ----------------------------------------------------------
|
|
117
|
-
|
|
118
194
|
/**
|
|
119
195
|
* Converts the TemplateBinding to a hydratable DOM node.
|
|
120
196
|
*
|
|
@@ -36,8 +36,8 @@ const buildHeader = ($desc, instance, headerId, panelId) => {
|
|
|
36
36
|
];
|
|
37
37
|
|
|
38
38
|
const header = Div({
|
|
39
|
-
class:
|
|
40
|
-
id:
|
|
39
|
+
class: 'accordion-header',
|
|
40
|
+
id: headerId,
|
|
41
41
|
// [a11y] aria-expanded reactive, aria-controls points to panel
|
|
42
42
|
'aria-expanded': $desc.expanded.transform((v) => String(v)),
|
|
43
43
|
'aria-controls': panelId,
|
|
@@ -64,12 +64,12 @@ const buildIndicator = ($desc) => {
|
|
|
64
64
|
}, '▾');
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
const buildContent = ($desc, instance) => {
|
|
67
|
+
const buildContent = ($desc, instance, headerId, panelId) => {
|
|
68
68
|
// [a11y] role=region + aria-labelledby on panel
|
|
69
69
|
const panelProps = {
|
|
70
|
-
class:
|
|
71
|
-
id:
|
|
72
|
-
role:
|
|
70
|
+
class: 'accordion-content',
|
|
71
|
+
id: panelId,
|
|
72
|
+
role: 'region',
|
|
73
73
|
'aria-labelledby': headerId,
|
|
74
74
|
};
|
|
75
75
|
|
package/types/elements.d.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { ObservableItem } from './observable';
|
|
3
3
|
import { BindingHydrator } from './template-cloner';
|
|
4
4
|
import { NDElement } from './nd-element';
|
|
5
|
+
import {AnchorAttributes,
|
|
6
|
+
AudioAttributes, CanvasAttributes, DetailsAttributes, DialogAttributes, GlobalAttributes, OlAttributes,
|
|
7
|
+
SourceAttributes, SvgAttributes, TrackAttributes, VideoAttributes, ThAttributes, ModAttributes, TdAttributes, TimeAttributes } from './globals';
|
|
5
8
|
|
|
6
9
|
// - Re-export all attribute types and utilities from globals
|
|
7
10
|
export type {
|
|
@@ -75,6 +78,8 @@ export type NdHTMLElement<T extends HTMLElement = HTMLElement> = T & { nd: NDEle
|
|
|
75
78
|
// Text & structural elements
|
|
76
79
|
// ---------------------------------------------------------
|
|
77
80
|
|
|
81
|
+
|
|
82
|
+
export declare function Text(string: string): string | Array<string | ObservableItem>;
|
|
78
83
|
export declare const Div: ElementFunction<GlobalAttributes, HTMLDivElement>;
|
|
79
84
|
export declare const Span: ElementFunction<GlobalAttributes, HTMLSpanElement>;
|
|
80
85
|
export declare const P: ElementFunction<GlobalAttributes, HTMLParagraphElement>;
|