native-document 1.0.16-8.2 → 1.0.16-8.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.168.02",
3
+ "version": "1.0.168.04",
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",
@@ -14,7 +14,28 @@ import {Observable} from '../data/Observable';
14
14
  export const bindClassAttribute = (element, data) => {
15
15
  for(const className in data) {
16
16
  const value = data[className];
17
- data[className].handleClassValue(element, className);
17
+ if(value.__$Observable) {
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
+ element.classes.toggle(className, value.val());
31
+ value.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
32
+ continue;
33
+ }
34
+ if(value.$hydrate) {
35
+ value.$hydrate(element, className);
36
+ continue;
37
+ }
38
+ element.classes.toggle(className, value);
18
39
  }
19
40
  };
20
41
 
@@ -28,11 +49,39 @@ export const bindClassAttribute = (element, data) => {
28
49
  * @param {Record<string, string|ObservableItem<string>>} data - Style map
29
50
  */
30
51
  export const bindStyleAttribute = (element, data) => {
31
- const keys = Object.keys(data);
32
- const length = keys.length;
33
- for(let i = 0; i < length; i++) {
34
- const styleName = keys[i];
35
- data[styleName].handleStyleProperty(element, styleName);
52
+ for(const styleName in data) {
53
+ const value = data[styleName];
54
+ const isCustomProperty = styleName.startsWith('--');
55
+
56
+ if(value.__$Observable) {
57
+ if(isCustomProperty) {
58
+ element.style.setProperty(styleName, value.val());
59
+ value.subscribe((newValue) => {
60
+ if(newValue === false) {
61
+ element.style.removeProperty(styleName);
62
+ return;
63
+ }
64
+ element.style.setProperty(styleName, newValue);
65
+ });
66
+ } else {
67
+ element.style[styleName] = value.val();
68
+ value.subscribe((newValue) => {
69
+ if(newValue === false) {
70
+ element.style.removeProperty(styleName);
71
+ return;
72
+ }
73
+ element.style[styleName] = newValue;
74
+ });
75
+ }
76
+ continue;
77
+ }
78
+
79
+ if(isCustomProperty) {
80
+ element.style.setProperty(styleName, value);
81
+ continue;
82
+ }
83
+
84
+ element.style[styleName] = value;
36
85
  }
37
86
  };
38
87
 
@@ -43,8 +92,30 @@ export const bindStyleAttribute = (element, data) => {
43
92
  * @param {boolean|number|Observable} value
44
93
  */
45
94
  export const bindBooleanAttribute = (element, attributeName, value) => {
46
- const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName] || attributeName;
47
- value.handleNdBooleanAttribute(element, attributeRealName);
95
+ const isObservable = value.__$isObservable;
96
+ const defaultValue = isObservable? value.val() : value;
97
+
98
+ const attributeRealName = BOOL_ATTRIBUTES_NAME[attributeName];
99
+
100
+ if(Validator.isBoolean(defaultValue)) {
101
+ element[attributeRealName] = defaultValue;
102
+ }
103
+ else {
104
+ element[attributeRealName] = defaultValue === element.value;
105
+ }
106
+ if(isObservable) {
107
+ if(attributeName === 'checked') {
108
+ if(typeof defaultValue === 'boolean') {
109
+ element.addEventListener('input', () => value.set(element[attributeRealName]));
110
+ }
111
+ else {
112
+ element.addEventListener('input', () => value.set(element.value));
113
+ }
114
+ value.subscribe((newValue) => element[attributeRealName] = newValue);
115
+ return;
116
+ }
117
+ value.subscribe((newValue) => element[attributeRealName] = (newValue === element.value));
118
+ }
48
119
  };
49
120
 
50
121
 
@@ -55,14 +126,15 @@ export const bindBooleanAttribute = (element, attributeName, value) => {
55
126
  * @param {Observable} value
56
127
  */
57
128
  export const bindAttributeWithObservable = (element, attributeName, value) => {
58
- if(attributeName !== 'value') {
59
- value.subscribe((newValue) => element.setAttribute(attributeName, newValue));
60
- element.setAttribute(attributeName, value.val());
129
+ const applyValue = attributeName === 'value' ? (newValue) => element.value = newValue : (newValue) => element.setAttribute(attributeName, newValue);
130
+ value.subscribe(applyValue);
131
+
132
+ if(attributeName === 'value') {
133
+ element.value = value.val();
134
+ element.addEventListener('input', () => value.set(element.value));
61
135
  return;
62
136
  }
63
- value.subscribe((newValue) => element.value = newValue);
64
- element.value = value.val();
65
- element.addEventListener('input', () => value.set(element.value));
137
+ element.setAttribute(attributeName, value.val());
66
138
  };
67
139
 
68
140
  /**
@@ -76,21 +148,32 @@ const AttributesWrapper = (element, attributes) => {
76
148
  Validator.validateAttributes(attributes);
77
149
  }
78
150
 
79
- if(!attributes) {
80
- return;
81
- }
82
-
83
- const keys = Object.keys(attributes);
84
- const length = keys.length;
85
-
86
- for(let i = 0; i < length; i++) {
87
- const originalAttributeName = keys[i];
151
+ for(const originalAttributeName in attributes) {
152
+ const attributeName = originalAttributeName.toLowerCase();
88
153
  const value = attributes[originalAttributeName];
89
154
  if(value == null) {
90
155
  continue;
91
156
  }
92
- value.handleNdAttribute(element, originalAttributeName.toLowerCase());
157
+ if(value.handleNdAttribute) {
158
+ value.handleNdAttribute(element, attributeName, value);
159
+ continue;
160
+ }
161
+ if(typeof value === 'object') {
162
+ if(attributeName === 'class') {
163
+ bindClassAttribute(element, value);
164
+ continue;
165
+ }
166
+ if(attributeName === 'style') {
167
+ bindStyleAttribute(element, value);
168
+ continue;
169
+ }
170
+ }
171
+ if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
172
+ bindBooleanAttribute(element, attributeName, value);
173
+ continue;
174
+ }
93
175
 
176
+ element.setAttribute(attributeName, value);
94
177
  }
95
178
  return element;
96
179
  };
@@ -93,7 +93,7 @@ export const ElementCreator = {
93
93
  return null;
94
94
  }
95
95
  do {
96
- child = child.toNdElement();
96
+ child = child.toNdElement();
97
97
  if(Validator.isElement(child)) {
98
98
  return child;
99
99
  }
@@ -38,6 +38,14 @@ NDElement.$getChild = (el) => el;
38
38
  NDElement.prototype.ghostDom = function(element) {
39
39
  if(!this.$attachements) {
40
40
  this.$attachements = document.createDocumentFragment();
41
+ this.toNdElement = () => {
42
+ const fragment = document.createDocumentFragment();
43
+ if(!this.$attachements.contains(this.$element)) {
44
+ fragment.appendChild(this.$element);
45
+ }
46
+ fragment.appendChild(this.$attachements);
47
+ return fragment;
48
+ };
41
49
  }
42
50
  this.$attachements.appendChild(NDElement.$getChild(element));
43
51
  return this;
@@ -1,179 +1,24 @@
1
1
  import {
2
- bindAttributeWithObservable, bindClassAttribute, bindStyleAttribute,
2
+ bindAttributeWithObservable,
3
+ bindBooleanAttribute,
3
4
  } from '../AttributesWrapper';
4
5
  import ObservableItem from '../../data/ObservableItem';
5
6
  import TemplateBinding from '../TemplateBinding';
6
- import { BOOLEAN_ATTRIBUTES } from '../constants';
7
- import { Observable } from '../../data/Observable';
7
+ import {BOOLEAN_ATTRIBUTES} from '../constants';
8
8
  import ObservableChecker from '../../data/ObservableChecker';
9
9
 
10
- /**
11
- * Extends a prototype with a non-enumerable, configurable, writable property.
12
- * Prevents prototype pollution issues (e.g. for...in loops) while keeping
13
- * the property overridable and deletable.
14
- *
15
- * @param {Function} Constructor - The constructor whose prototype to extend
16
- * @param {string} name - Property name
17
- * @param {Function} fn - Implementation
18
- */
19
- const extendsPrototype = (Constructor, name, fn) => {
20
- Object.defineProperty(Constructor.prototype, name, {
21
- value: fn,
22
- enumerable: false,
23
- configurable: true,
24
- writable: true,
25
- });
26
- }
27
10
 
28
- // -- handleNdAttribute --------------------------------------------------------
29
-
30
- extendsPrototype(Object, 'handleNdAttribute', function(element, attributeName) {
31
- if (attributeName === 'class') {
32
- bindClassAttribute(element, this);
33
- return;
34
- }
35
- if (attributeName === 'style') {
36
- bindStyleAttribute(element, this);
11
+ ObservableItem.prototype.handleNdAttribute = function(element, attributeName) {
12
+ if(BOOLEAN_ATTRIBUTES.has(attributeName)) {
13
+ bindBooleanAttribute(element, attributeName, this);
37
14
  return;
38
15
  }
39
- element.setAttribute(attributeName, JSON.stringify(this));
40
- });
41
16
 
42
- extendsPrototype(ObservableItem, 'handleNdAttribute', function(element, attributeName) {
43
- if (BOOLEAN_ATTRIBUTES.has(attributeName)) {
44
- this.handleNdBooleanAttribute(element, attributeName);
45
- return;
46
- }
47
17
  bindAttributeWithObservable(element, attributeName, this);
48
- });
49
-
50
- extendsPrototype(TemplateBinding, 'handleNdAttribute', function(element, attributeName) {
51
- this.$hydrate(element, attributeName);
52
- });
53
-
54
- extendsPrototype(Number, 'handleNdAttribute', function(element, attributeName) {
55
- element.setAttribute(attributeName, this.toString());
56
- });
57
-
58
- extendsPrototype(String, 'handleNdAttribute', function(element, attributeName) {
59
- element.setAttribute(attributeName, this.toString());
60
- });
61
-
62
- extendsPrototype(Array, 'handleNdAttribute', function(element, attributeName) {
63
- element.setAttribute(attributeName, this.toString());
64
- });
65
-
66
- extendsPrototype(Boolean, 'handleNdAttribute', function(element, attributeName) {
67
- element[attributeName] = this;
68
- });
69
-
70
- // -- handleNdBooleanAttribute -------------------------------------------------
71
-
72
- extendsPrototype(Boolean, 'handleNdBooleanAttribute', function(element, attributeName) {
73
- element[attributeName] = this;
74
- });
75
-
76
- extendsPrototype(String, 'handleNdBooleanAttribute', function(element, attributeName) {
77
- element[attributeName] = this === element.value;
78
- });
79
-
80
- extendsPrototype(ObservableItem, 'handleNdBooleanAttribute', function(element, attributeName) {
81
- const defaultValue = this.val();
82
- defaultValue.handleNdBooleanAttribute(element, attributeName);
83
-
84
- if (attributeName === 'checked') {
85
- if (typeof defaultValue === 'boolean') {
86
- element.addEventListener('input', () => this.set(element[attributeName]));
87
- } else {
88
- element.addEventListener('input', () => this.set(element.value));
89
- }
90
- this.subscribe((newValue) => element[attributeName] = newValue);
91
- return;
92
- }
93
-
94
- this.subscribe((newValue) => element[attributeName] = (newValue === element.value));
95
- });
18
+ };
96
19
 
97
- // -- handleStyleProperty ------------------------------------------------------
20
+ ObservableChecker.prototype.handleNdAttribute = ObservableItem.prototype.handleNdAttribute;
98
21
 
99
- extendsPrototype(String, 'handleStyleProperty', function(element, styleName) {
100
- const isCustomProperty = styleName.startsWith('--');
101
- if (isCustomProperty) {
102
- element.style.setProperty(styleName, this);
103
- return;
104
- }
105
- element.style[styleName] = this;
106
- });
107
-
108
- extendsPrototype(Number, 'handleStyleProperty', String.prototype.handleStyleProperty);
109
-
110
- extendsPrototype(ObservableItem, 'handleStyleProperty', function(element, styleName) {
111
- const isCustomProperty = styleName.startsWith('--');
112
-
113
- if (isCustomProperty) {
114
- element.style.setProperty(styleName, this.val());
115
- this.subscribe((newValue) => {
116
- if (newValue === false) {
117
- element.style.removeProperty(styleName);
118
- return;
119
- }
120
- element.style.setProperty(styleName, newValue);
121
- });
122
- return;
123
- }
124
-
125
- element.style[styleName] = this.val();
126
- this.subscribe((newValue) => {
127
- if (newValue === false) {
128
- element.style.removeProperty(styleName);
129
- return;
130
- }
131
- element.style[styleName] = newValue;
132
- });
133
- });
134
-
135
- extendsPrototype(Array, 'handleStyleProperty', function(element, styleName) {
136
- const dependencies = this.filter((item) => item.__$Observable);
137
- if (dependencies.length) {
138
- const style = Observable.computed(
139
- () => this.map((item) => item.valueOf()).join(', '),
140
- dependencies,
141
- );
142
- style.handleStyleProperty(element, styleName);
143
- return;
144
- }
145
- element.style[styleName] = this.join(', ');
146
- });
147
-
148
- // -- handleClassValue ------------------------------------------------------
149
- extendsPrototype(ObservableItem, 'handleClassValue', function(element, className) {
150
- element.classes.toggle(className, this.val());
151
- this.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
152
- });
153
-
154
- extendsPrototype(ObservableChecker, 'handleClassValue', function(element, className) {
155
- let lastClass = this.val();
156
- if(typeof lastClass === 'string') {
157
- element.classes.toggle(lastClass, true);
158
- this.subscribe((currentValue) => {
159
- element.classes.remove(lastClass);
160
- element.classes.toggle(currentValue, true);
161
- lastClass = currentValue;
162
- });
163
- return;
164
- }
165
- element.classes.toggle(className, this.val());
166
- this.subscribe((shouldAdd) => element.classes.toggle(className, shouldAdd));
167
- });
168
-
169
- extendsPrototype(TemplateBinding, 'handleClassValue', function(element, className) {
170
- this.$hydrate(element, className);
171
- });
172
-
173
- extendsPrototype(String, 'handleClassValue', function(element, className) {
174
- element.classes.toggle(className, this);
175
- });
176
-
177
- extendsPrototype(Boolean, 'handleClassValue', function(element, className) {
178
- element.classes.toggle(className);
179
- });
22
+ TemplateBinding.prototype.handleNdAttribute = function(element, attributeName) {
23
+ this.$hydrate(element, attributeName);
24
+ };
@@ -1,157 +1,131 @@
1
1
  import ObservableItem from '../../data/ObservableItem';
2
- import { NDElement } from '../NDElement';
2
+ import {NDElement} from '../NDElement';
3
3
  import TemplateBinding from '../TemplateBinding';
4
- import { ElementCreator } from '../ElementCreator';
4
+ import {ElementCreator} from '../ElementCreator';
5
5
  import PluginsManager from '../../utils/plugins-manager';
6
6
  import ObservableChecker from '../../data/ObservableChecker';
7
7
 
8
- /**
9
- * Extends a prototype with a non-enumerable, configurable, writable property.
10
- *
11
- * @param {Function} Constructor - The constructor whose prototype to extend
12
- * @param {string} name - Property name
13
- * @param {Function} fn - Implementation
14
- */
15
- function extendsPrototype(Constructor, name, fn) {
16
- Object.defineProperty(Constructor.prototype, name, {
17
- value: fn,
18
- enumerable: false,
19
- configurable: true,
20
- writable: true,
21
- });
22
- }
23
-
24
- // -- Static assignment (not a prototype method) -------------------------------
25
8
 
26
9
  NDElement.$getChild = ElementCreator.getChild;
27
10
 
28
- // -- toNdElement --------------------------------------------------------------
29
-
30
- /**
31
- * Converts a string to a static text node.
32
- * @returns {Text}
33
- */
34
- extendsPrototype(Object, 'toNdElement', function() {
35
- return ElementCreator.createStaticTextNode(null, JSON.stringify(this));
36
- });
37
-
38
11
  /**
39
- * Converts a string to a static text node.
40
- * @returns {Text}
12
+ * Converts a string to a reactive text node.
13
+ *
14
+ * @returns {Text} Static text node containing the string value
41
15
  */
42
- extendsPrototype(String, 'toNdElement', function() {
16
+ String.prototype.toNdElement = function () {
43
17
  return ElementCreator.createStaticTextNode(null, this);
44
- });
18
+ };
45
19
 
46
20
  /**
47
21
  * Converts a number to a static text node.
48
- * @returns {Text}
22
+ *
23
+ * @returns {Text} Static text node containing the number as a string
49
24
  */
50
- extendsPrototype(Number, 'toNdElement', function() {
25
+ Number.prototype.toNdElement = function () {
51
26
  return ElementCreator.createStaticTextNode(null, this.toString());
52
- });
27
+ };
53
28
 
54
29
  /**
55
30
  * Returns the element itself (identity for DOM compatibility).
56
- * @returns {Element}
31
+ *
32
+ * @returns {Element} this
57
33
  */
58
- extendsPrototype(Element, 'toNdElement', function() {
34
+ Element.prototype.toNdElement = function () {
59
35
  return this;
60
- });
36
+ };
61
37
 
62
38
  /**
63
39
  * Returns the text node itself (identity for DOM compatibility).
64
- * @returns {Text}
40
+ *
41
+ * @returns {Text} this
65
42
  */
66
- extendsPrototype(Text, 'toNdElement', function() {
43
+ Text.prototype.toNdElement = function () {
67
44
  return this;
68
- });
45
+ };
69
46
 
70
47
  /**
71
48
  * Returns the comment node itself (identity for DOM compatibility).
72
- * @returns {Comment}
49
+ *
50
+ * @returns {Comment} this
73
51
  */
74
- extendsPrototype(Comment, 'toNdElement', function() {
52
+ Comment.prototype.toNdElement = function () {
75
53
  return this;
76
- });
54
+ };
77
55
 
78
56
  /**
79
57
  * Returns the document itself (identity for DOM compatibility).
80
- * @returns {Document}
58
+ *
59
+ * @returns {Document} this
81
60
  */
82
- extendsPrototype(Document, 'toNdElement', function() {
61
+ Document.prototype.toNdElement = function () {
83
62
  return this;
84
- });
63
+ };
85
64
 
86
65
  /**
87
66
  * Returns the document fragment itself (identity for DOM compatibility).
88
- * @returns {DocumentFragment}
67
+ *
68
+ * @returns {DocumentFragment} this
89
69
  */
90
- extendsPrototype(DocumentFragment, 'toNdElement', function() {
70
+ DocumentFragment.prototype.toNdElement = function () {
91
71
  return this;
92
- });
72
+ };
93
73
 
94
74
  /**
95
75
  * Converts the ObservableItem to a reactive text node that updates automatically when the value changes.
96
- * @returns {Text}
76
+ *
77
+ * @returns {Text} Reactive text node bound to this observable
97
78
  */
98
- extendsPrototype(ObservableItem, 'toNdElement', function() {
79
+ ObservableItem.prototype.toNdElement = function () {
99
80
  return ElementCreator.createObservableNode(null, this);
100
- });
81
+ };
101
82
 
102
- /**
103
- * ObservableChecker shares the same reactive text node behaviour as ObservableItem.
104
- * @returns {Text}
105
- */
106
- extendsPrototype(ObservableChecker, 'toNdElement', ObservableItem.prototype.toNdElement);
83
+ ObservableChecker.prototype.toNdElement = ObservableItem.prototype.toNdElement;
107
84
 
108
85
  /**
109
86
  * Converts the NDElement to its underlying HTMLElement (or ghost DOM fragment if ghostDom was used).
110
- * @returns {HTMLElement|DocumentFragment}
87
+ *
88
+ * @returns {HTMLElement|DocumentFragment} The underlying DOM node
111
89
  */
112
- extendsPrototype(NDElement, 'toNdElement', function() {
113
- const element = this.$element ?? this.$build?.() ?? this.build?.() ?? null;
114
- if (this.$attachements) {
115
- if (!this.$attachements.contains(this.$element)) {
116
- this.$attachements.append(this.$element);
117
- }
118
- return this.$attachements;
119
- }
120
- return element;
121
- });
90
+ NDElement.prototype.toNdElement = function () {
91
+ return this.$element;
92
+ };
122
93
 
123
94
  /**
124
- * Converts the array to a DocumentFragment containing all child elements.
95
+ * Converts the array to a DocumentFragment containing all elements.
125
96
  * Each item is processed through ElementCreator.getChild().
126
- * @returns {DocumentFragment}
97
+ *
98
+ * @returns {DocumentFragment} Fragment containing all array children
127
99
  */
128
- extendsPrototype(Array, 'toNdElement', function() {
100
+ Array.prototype.toNdElement = function () {
129
101
  const fragment = document.createDocumentFragment();
130
- for (let i = 0, length = this.length; i < length; i++) {
102
+ for(let i = 0, length = this.length; i < length; i++) {
131
103
  const child = ElementCreator.getChild(this[i]);
132
- if (child === null) continue;
104
+ if(child === null) continue;
133
105
  fragment.appendChild(child);
134
106
  }
135
107
  return fragment;
136
- });
108
+ };
137
109
 
138
110
  /**
139
111
  * Calls the function and converts its return value to a DOM node.
140
112
  * Used internally by ElementCreator to process function-based children.
141
- * @returns {Node}
113
+ *
114
+ * @returns {Node} The DOM node returned by the function
142
115
  */
143
- extendsPrototype(Function, 'toNdElement', function() {
116
+ Function.prototype.toNdElement = function () {
144
117
  const child = this;
145
- if (process.env.NODE_ENV === 'development') {
118
+ if(process.env.NODE_ENV === 'development') {
146
119
  PluginsManager.emit('BeforeProcessComponent', child);
147
120
  }
148
121
  return ElementCreator.getChild(child());
149
- });
122
+ };
150
123
 
151
124
  /**
152
125
  * Converts the TemplateBinding to a hydratable DOM node for use in TemplateCloner.
153
- * @returns {Node}
126
+ *
127
+ * @returns {Node} Hydratable node
154
128
  */
155
- extendsPrototype(TemplateBinding, 'toNdElement', function() {
129
+ TemplateBinding.prototype.toNdElement = function () {
156
130
  return ElementCreator.createHydratableNode(null, this);
157
- });
131
+ };