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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.171.6",
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
- return $observables.get(id)?.deref();
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
- return String(this.$currentValue);
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
- element.appendChildRaw(Actions.toFragment(data));
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
- element.appendChildRaw(Actions.toFragment(items));
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
- element.appendChildRaw(Actions.toFragment(items));
179
+ Actions.appendToElement(items);
176
180
  },
177
181
  replace: (items) => {
178
182
  clear(items);
179
- element.appendChildRaw(Actions.toFragment(items));
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
- fragment.appendChild(child);
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.__$isValidNdChild) { // If attributes is a ValidChild
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
- NDElement.prototype['on'+eventSourceName] = function(callback = null, options = {}) {
30
- this.$element.addEventListener(eventName, callback, options);
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.
@@ -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>;