native-document 1.0.17-1.6 → 1.0.17-1.8
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 +9 -1
- package/src/core/elements/content-formatter.js +19 -0
- package/src/core/elements/control/for-each-array.js +10 -19
- package/src/core/wrappers/AttributesWrapper.js +31 -26
- package/src/core/wrappers/ElementCreator.js +17 -0
- package/src/core/wrappers/HtmlElementWrapper.js +1 -1
- package/src/core/wrappers/NDElement.js +11 -1
- package/src/core/wrappers/NdPrototype.js +27 -7
- package/src/core/wrappers/prototypes/nd-element-extensions.js +37 -0
- 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.8",
|
|
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;
|
|
@@ -553,7 +556,12 @@ ObservableItem.prototype.reset = function() {
|
|
|
553
556
|
* @returns {string} String representation of the current value
|
|
554
557
|
*/
|
|
555
558
|
ObservableItem.prototype.toString = function() {
|
|
556
|
-
|
|
559
|
+
if(this.$id) {
|
|
560
|
+
return this.$id.toString();
|
|
561
|
+
}
|
|
562
|
+
const id = MemoryManager.register(this);
|
|
563
|
+
this.$id = id;
|
|
564
|
+
return '{{obs:'+id+'}}';
|
|
557
565
|
};
|
|
558
566
|
|
|
559
567
|
/**
|
|
@@ -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}
|
|
@@ -151,14 +151,14 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
151
151
|
if(Array.isArray(data)) {
|
|
152
152
|
set = () => {
|
|
153
153
|
clear(data);
|
|
154
|
-
|
|
154
|
+
Actions.appendToElement(data);
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
157
|
else {
|
|
158
158
|
set = () => {
|
|
159
159
|
const items = data.val();
|
|
160
160
|
clear(items);
|
|
161
|
-
|
|
161
|
+
Actions.appendToElement(items);
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -167,45 +167,36 @@ export function ForEachArray(data, callback, configs = {}) {
|
|
|
167
167
|
const fragment = document.createDocumentFragment();
|
|
168
168
|
for(let i = 0, length = items.length; i < length; i++) {
|
|
169
169
|
fragment.appendChild(buildItem(items[i], lastNumberOfItems));
|
|
170
|
-
lastNumberOfItems++;
|
|
171
170
|
}
|
|
172
171
|
return fragment;
|
|
173
172
|
},
|
|
173
|
+
appendToElement: (items) => {
|
|
174
|
+
for(let i = 0, length = items.length; i < length; i++) {
|
|
175
|
+
element.appendChild(buildItem(items[i], i));
|
|
176
|
+
}
|
|
177
|
+
},
|
|
174
178
|
add: (items) => {
|
|
175
|
-
|
|
179
|
+
Actions.appendToElement(items);
|
|
176
180
|
},
|
|
177
181
|
replace: (items) => {
|
|
178
182
|
clear(items);
|
|
179
|
-
|
|
183
|
+
Actions.appendToElement(items);
|
|
180
184
|
},
|
|
181
185
|
set,
|
|
182
186
|
reOrder: (items) => {
|
|
183
187
|
let child = null;
|
|
184
|
-
const fragment = document.createDocumentFragment();
|
|
185
188
|
for(const item of items) {
|
|
186
189
|
child = getItemChild(item);
|
|
187
190
|
if(child) {
|
|
188
|
-
|
|
191
|
+
element.appendChild(child);
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
child = null;
|
|
192
|
-
element.appendChildRaw(fragment);
|
|
193
195
|
},
|
|
194
196
|
removeOne: (element, index) => {
|
|
195
197
|
removeCacheItem(element, true);
|
|
196
198
|
},
|
|
197
199
|
clear,
|
|
198
|
-
populate: ([target, iteration, callback]) => {
|
|
199
|
-
const fragment = document.createDocumentFragment();
|
|
200
|
-
for (let i = 0; i < iteration; i++) {
|
|
201
|
-
const data = callback(i);
|
|
202
|
-
target.push(data);
|
|
203
|
-
fragment.append(buildItem(data, i));
|
|
204
|
-
lastNumberOfItems++;
|
|
205
|
-
}
|
|
206
|
-
element.appendChildRaw(fragment);
|
|
207
|
-
fragment.replaceChildren();
|
|
208
|
-
},
|
|
209
200
|
unshift: (values) => {
|
|
210
201
|
element.insertAtStartRaw(Actions.toFragment(values));
|
|
211
202
|
},
|
|
@@ -3,6 +3,36 @@ import NativeDocumentError from '../errors/NativeDocumentError';
|
|
|
3
3
|
import {BOOL_ATTRIBUTES_NAME, BOOLEAN_ATTRIBUTES} from './constants.js';
|
|
4
4
|
import {Observable} from '../data/Observable';
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export const handleClassAttributeItem = (element, className, value) => {
|
|
9
|
+
if (value.__$isObservableChecker) {
|
|
10
|
+
let lastClass = value.val();
|
|
11
|
+
if (typeof lastClass === 'string') {
|
|
12
|
+
element.classes.toggle(lastClass, true);
|
|
13
|
+
value.subscribe((currentValue) => {
|
|
14
|
+
element.classes.remove(lastClass);
|
|
15
|
+
element.classes.toggle(currentValue, true);
|
|
16
|
+
lastClass = currentValue;
|
|
17
|
+
});
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (value.__$Observable) {
|
|
23
|
+
element.classes.toggle(className, value.val());
|
|
24
|
+
value.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (value.$hydrate) {
|
|
29
|
+
value.$hydrate(element, className);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
element.classes.toggle(className, value);
|
|
34
|
+
};
|
|
35
|
+
|
|
6
36
|
/**
|
|
7
37
|
* Applies a reactive class map to an HTMLElement.
|
|
8
38
|
* Each key is a CSS class name; each value is a boolean or ObservableItem<boolean>.
|
|
@@ -14,32 +44,7 @@ import {Observable} from '../data/Observable';
|
|
|
14
44
|
export const bindClassAttribute = (element, data) => {
|
|
15
45
|
for(const className in data) {
|
|
16
46
|
const value = data[className];
|
|
17
|
-
|
|
18
|
-
if (value.__$isObservableChecker) {
|
|
19
|
-
let lastClass = value.val();
|
|
20
|
-
if (typeof lastClass === 'string') {
|
|
21
|
-
element.classes.toggle(lastClass, true);
|
|
22
|
-
value.subscribe((currentValue) => {
|
|
23
|
-
element.classes.remove(lastClass);
|
|
24
|
-
element.classes.toggle(currentValue, true);
|
|
25
|
-
lastClass = currentValue;
|
|
26
|
-
});
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (value.__$Observable) {
|
|
32
|
-
element.classes.toggle(className, value.val());
|
|
33
|
-
value.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (value.$hydrate) {
|
|
38
|
-
value.$hydrate(element, className);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
element.classes.toggle(className, value);
|
|
47
|
+
handleClassAttributeItem(element, className, value);
|
|
43
48
|
}
|
|
44
49
|
};
|
|
45
50
|
|
|
@@ -88,6 +88,23 @@ export const ElementCreator = {
|
|
|
88
88
|
if(process.env.NODE_ENV === 'development') {
|
|
89
89
|
PluginsManager.emit('BeforeProcessChildren', parent);
|
|
90
90
|
}
|
|
91
|
+
if(Array.isArray(children)) {
|
|
92
|
+
for(let i = 0, length = children.length; i < length; i++) {
|
|
93
|
+
let child = children[i];
|
|
94
|
+
if(child == null) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if(!child.__$isNativeNode) {
|
|
98
|
+
child = ElementCreator.getChild(children[i]);
|
|
99
|
+
if(child) {
|
|
100
|
+
parent.appendChild(child);
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
parent.appendChild(child);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
91
108
|
const child = ElementCreator.getChild(children);
|
|
92
109
|
parent.appendChild(child);
|
|
93
110
|
if(process.env.NODE_ENV === 'development') {
|
|
@@ -54,7 +54,7 @@ const OBJECT_PROTOTYPE = Object.prototype;
|
|
|
54
54
|
*/
|
|
55
55
|
export const createHtmlElement = (element, _attributes, _children = null) => {
|
|
56
56
|
let attributes = _attributes, children = _children;
|
|
57
|
-
if(_attributes
|
|
57
|
+
if(_attributes?.__$isValidNdChild) { // If attributes is a ValidChild
|
|
58
58
|
attributes = _children;
|
|
59
59
|
children = _attributes;
|
|
60
60
|
}
|
|
@@ -5,7 +5,7 @@ import DebugManager from '../utils/debug-manager.js';
|
|
|
5
5
|
import attributesWrapper, {
|
|
6
6
|
bindAttributeWithObservable,
|
|
7
7
|
bindClassAttribute,
|
|
8
|
-
bindStyleAttribute,
|
|
8
|
+
bindStyleAttribute, handleClassAttributeItem,
|
|
9
9
|
} from './AttributesWrapper';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -434,6 +434,16 @@ NDElement.prototype.style = function(style) {
|
|
|
434
434
|
return this;
|
|
435
435
|
};
|
|
436
436
|
|
|
437
|
+
NDElement.prototype.className = function(classes) {
|
|
438
|
+
this.$element.classes.add(classes);
|
|
439
|
+
return this;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
NDElement.prototype.class = function(className, value) {
|
|
443
|
+
handleClassAttributeItem(this.$element, className, value);
|
|
444
|
+
return this;
|
|
445
|
+
};
|
|
446
|
+
|
|
437
447
|
|
|
438
448
|
/**
|
|
439
449
|
* Extends the NDElement prototype with new methods available to all NDElement instances.
|
|
@@ -20,25 +20,29 @@ 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
|
-
|
|
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
|
+
} else {
|
|
33
|
+
this.$element.addEventListener(eventName, callback, options);
|
|
34
|
+
}
|
|
31
35
|
return this;
|
|
32
36
|
};
|
|
33
37
|
});
|
|
34
38
|
|
|
35
39
|
EVENTS_WITH_STOP.forEach(eventSourceName => {
|
|
36
40
|
const eventName = eventSourceName.toLowerCase();
|
|
37
|
-
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options =
|
|
41
|
+
NDElement.prototype['onStop'+eventSourceName] = function(callback = null, options = null) {
|
|
38
42
|
_stop(this.$element, eventName, callback, options);
|
|
39
43
|
return this;
|
|
40
44
|
};
|
|
41
|
-
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options =
|
|
45
|
+
NDElement.prototype['onPreventStop'+eventSourceName] = function(callback = null, options = null) {
|
|
42
46
|
_preventStop(this.$element, eventName, callback, options);
|
|
43
47
|
return this;
|
|
44
48
|
};
|
|
@@ -46,7 +50,7 @@ EVENTS_WITH_STOP.forEach(eventSourceName => {
|
|
|
46
50
|
|
|
47
51
|
EVENTS_WITH_PREVENT.forEach(eventSourceName => {
|
|
48
52
|
const eventName = eventSourceName.toLowerCase();
|
|
49
|
-
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options =
|
|
53
|
+
NDElement.prototype['onPrevent'+eventSourceName] = function(callback = null, options = null) {
|
|
50
54
|
_prevent(this.$element, eventName, callback, options);
|
|
51
55
|
return this;
|
|
52
56
|
};
|
|
@@ -122,8 +126,13 @@ const _prevent = function(element, eventName, callback, options) {
|
|
|
122
126
|
event.preventDefault();
|
|
123
127
|
callback && callback.call(element, event);
|
|
124
128
|
};
|
|
129
|
+
|
|
130
|
+
const inlineHandler = 'on' + eventName;
|
|
131
|
+
if(!element[inlineHandler] && !options) {
|
|
132
|
+
element[inlineHandler] = handler;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
125
135
|
element.addEventListener(eventName, handler, options);
|
|
126
|
-
return this;
|
|
127
136
|
};
|
|
128
137
|
|
|
129
138
|
const _stop = function(element, eventName, callback, options) {
|
|
@@ -131,6 +140,13 @@ const _stop = function(element, eventName, callback, options) {
|
|
|
131
140
|
event.stopPropagation();
|
|
132
141
|
callback && callback.call(element, event);
|
|
133
142
|
};
|
|
143
|
+
|
|
144
|
+
const inlineHandler = 'on' + eventName;
|
|
145
|
+
if(!element[inlineHandler] && !options) {
|
|
146
|
+
element[inlineHandler] = handler;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
134
150
|
element.addEventListener(eventName, handler, options);
|
|
135
151
|
return this;
|
|
136
152
|
};
|
|
@@ -141,6 +157,10 @@ const _preventStop = function(element, eventName, callback, options) {
|
|
|
141
157
|
event.preventDefault();
|
|
142
158
|
callback && callback.call(element, event);
|
|
143
159
|
};
|
|
160
|
+
const inlineHandler = 'on' + eventName;
|
|
161
|
+
if(!element[inlineHandler] && !options) {
|
|
162
|
+
element[inlineHandler] = handler;
|
|
163
|
+
}
|
|
144
164
|
element.addEventListener(eventName, handler, options);
|
|
145
165
|
return this;
|
|
146
166
|
};
|
|
@@ -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.
|
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>;
|