native-document 1.0.16-8.1 → 1.0.16-8.3

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