native-document 1.0.53 → 1.0.55

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.
@@ -5,13 +5,15 @@ import ObservableChecker from "./ObservableChecker";
5
5
  import PluginsManager from "../utils/plugins-manager";
6
6
  import Validator from "../utils/validator";
7
7
  import {ObservableWhen} from "./ObservableWhen";
8
+ import {deepClone} from "../utils/helpers.js";
8
9
 
9
10
  /**
10
11
  *
11
12
  * @param {*} value
13
+ * @param {{ propagation: boolean, reset: boolean} | null} configs
12
14
  * @class ObservableItem
13
15
  */
14
- export default function ObservableItem(value) {
16
+ export default function ObservableItem(value, configs = null) {
15
17
  this.$previousValue = null;
16
18
  this.$currentValue = value;
17
19
  this.$isCleanedUp = false;
@@ -20,6 +22,14 @@ export default function ObservableItem(value) {
20
22
  this.$watchers = null;
21
23
 
22
24
  this.$memoryId = null;
25
+
26
+ if(configs) {
27
+ this.configs = configs;
28
+ if(configs.reset) {
29
+ this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
30
+ }
31
+ }
32
+
23
33
  PluginsManager.emit('CreateObservable', this);
24
34
  }
25
35
 
@@ -259,4 +269,16 @@ ObservableItem.prototype.toBool = function() {
259
269
 
260
270
  ObservableItem.prototype.toggle = function() {
261
271
  this.set(!this.$currentValue);
272
+ };
273
+
274
+ ObservableItem.prototype.reset = function() {
275
+ if(!this.configs?.reset) {
276
+ return;
277
+ }
278
+ const resetValue = (Validator.isObject(this.$initialValue))
279
+ ? deepClone(this.$initialValue, (observable) => {
280
+ observable.reset();
281
+ })
282
+ : this.$initialValue;
283
+ this.set(resetValue)
262
284
  };
@@ -5,8 +5,9 @@ import ObservableArray from "../ObservableArray.js";
5
5
  /**
6
6
  *
7
7
  * @param {Array} target
8
+ * @param {{propagation: boolean, deep: boolean, reset: boolean}|null} configs
8
9
  * @returns {ObservableArray}
9
10
  */
10
- Observable.array = function(target) {
11
- return new ObservableArray(target);
11
+ Observable.array = function(target, configs = null) {
12
+ return new ObservableArray(target, configs);
12
13
  };
@@ -1,8 +1,5 @@
1
1
  import Validator from "../../utils/validator";
2
2
  import {Observable} from "../Observable";
3
- import ObservableItem from "../ObservableItem";
4
-
5
-
6
3
 
7
4
  const ObservableObjectValue = function(data) {
8
5
  const result = {};
@@ -45,40 +42,47 @@ const ObservableGet = function(target, property) {
45
42
  /**
46
43
  *
47
44
  * @param {Object} initialValue
48
- * @param {{propagation: boolean, deep: boolean}} configs
45
+ * @param {{propagation: boolean, deep: boolean, reset: boolean}|null} configs
49
46
  * @returns {Proxy}
50
47
  */
51
- Observable.init = function(initialValue, { propagation= false, deep = true } = {}) {
48
+ Observable.init = function(initialValue, configs = null) {
52
49
  const data = {};
53
50
  for(const key in initialValue) {
54
51
  const itemValue = initialValue[key];
55
52
  if(Array.isArray(itemValue)) {
56
- if(deep) {
53
+ if(configs?.deep !== false) {
57
54
  const mappedItemValue = itemValue.map(item => {
58
55
  if(Validator.isJson(item)) {
59
- return Observable.json(item, { propagation, deep });
56
+ return Observable.json(item, configs);
60
57
  }
61
58
  if(Validator.isArray(item)) {
62
- return Observable.array(item, { propagation, deep });
59
+ return Observable.array(item, configs);
63
60
  }
64
- return Observable(item);
61
+ return Observable(item, configs);
65
62
  });
66
- data[key] = Observable.array(mappedItemValue, { propagation });
63
+ data[key] = Observable.array(mappedItemValue, configs);
67
64
  continue;
68
65
  }
69
- data[key] = Observable.array(itemValue, { propagation });
66
+ data[key] = Observable.array(itemValue, configs);
70
67
  continue;
71
68
  }
72
69
  if(Validator.isObservable(itemValue) || Validator.isProxy(itemValue)) {
73
70
  data[key] = itemValue;
74
71
  continue;
75
72
  }
76
- data[key] = Observable(itemValue);
73
+ data[key] = Observable(itemValue, configs);
74
+ }
75
+
76
+ const $reset = () => {
77
+ for(const key in data) {
78
+ const item = data[key];
79
+ item.reset();
80
+ }
77
81
  }
78
82
 
79
83
  const $val = () => ObservableObjectValue(data);
80
84
 
81
- const $clone = () => Observable.init($val(), { propagation, deep });
85
+ const $clone = () => Observable.init($val(), configs);
82
86
 
83
87
  const $updateWith = (values) => {
84
88
  Observable.update(proxy, values);
@@ -88,34 +92,17 @@ Observable.init = function(initialValue, { propagation= false, deep = true } = {
88
92
 
89
93
  const proxy = new Proxy(data, {
90
94
  get(target, property) {
91
- if(property === '__isProxy__') {
92
- return true;
93
- }
94
- if(property === '$value') {
95
- return $val();
96
- }
97
- if(property === '$clone') {
98
- return $clone;
99
- }
100
- if(property === '$keys') {
101
- return Object.keys(initialValue);
102
- }
103
- if(property === '$observables') {
104
- return Object.values(target);
105
- }
106
- if(property === '$set' || property === '$updateWith') {
107
- return $updateWith;
108
- }
109
- if(property === '$get') {
110
- return $get;
111
- }
112
- if(property === '$val') {
113
- return $val;
114
- }
115
- if(target[property] !== undefined) {
116
- return target[property];
117
- }
118
- return undefined;
95
+ if(property === '__isProxy__') { return true; }
96
+ if(property === '$value') { return $val() }
97
+ if(property === 'get' || property === '$get') { return $get; }
98
+ if(property === 'val' || property === '$val') { return $val; }
99
+ if(property === 'set' || property === '$set' || property === '$updateWith') { return $updateWith; }
100
+ if(property === 'observables' || property === '$observables') { return Object.values(target); }
101
+ if(property === 'keys'|| property === '$keys') { return Object.keys(initialValue); }
102
+ if(property === 'clone' || property === '$clone') { return $clone; }
103
+ if(property === 'reset') { return $reset; }
104
+ if(property === 'configs') { return configs; }
105
+ return target[property];
119
106
  },
120
107
  set(target, prop, newValue) {
121
108
  if(target[prop] !== undefined) {
@@ -166,6 +153,7 @@ Observable.value = function(data) {
166
153
 
167
154
  Observable.update = function($target, newData) {
168
155
  const data = Validator.isProxy(newData) ? newData.$value : newData;
156
+ const configs = $target.configs;
169
157
 
170
158
  for(const key in data) {
171
159
  const targetItem = $target[key];
@@ -178,9 +166,9 @@ Observable.update = function($target, newData) {
178
166
  if(Validator.isObservable(firstElementFromOriginalValue) || Validator.isProxy(firstElementFromOriginalValue)) {
179
167
  const newValues = newValue.map(item => {
180
168
  if(Validator.isProxy(firstElementFromOriginalValue)) {
181
- return Observable.init(item);
169
+ return Observable.init(item, configs);
182
170
  }
183
- return Observable(item);
171
+ return Observable(item, configs);
184
172
  });
185
173
  targetItem.set(newValues);
186
174
  continue;
@@ -105,6 +105,8 @@ export default function Anchor(name, isUniqueChild = false) {
105
105
  parent.insertBefore(child, anchorEnd);
106
106
  };
107
107
 
108
+ element.setContent = element.replaceContent;
109
+
108
110
  element.insertBefore = function(child, anchor = null) {
109
111
  element.appendChild(child, anchor);
110
112
  };
@@ -6,7 +6,7 @@ import { ElementCreator } from "../../wrappers/ElementCreator";
6
6
  import NativeDocumentError from "../../errors/NativeDocumentError";
7
7
 
8
8
  export function ForEachArray(data, callback, key, configs = {}) {
9
- const element = new Anchor('ForEach Array');
9
+ const element = Anchor('ForEach Array');
10
10
  const blockEnd = element.endElement();
11
11
  const blockStart = element.startElement();
12
12
 
@@ -15,7 +15,7 @@ import NativeDocumentError from "../../errors/NativeDocumentError";
15
15
  * @returns {DocumentFragment}
16
16
  */
17
17
  export function ForEach(data, callback, key, { shouldKeepItemsInCache = false } = {}) {
18
- const element = new Anchor('ForEach');
18
+ const element = Anchor('ForEach');
19
19
  const blockEnd = element.endElement();
20
20
  const blockStart = element.startElement();
21
21
 
@@ -16,7 +16,7 @@ export const ShowIf = function(condition, child, { comment = null, shouldKeepInC
16
16
  if(!(Validator.isObservable(condition)) && !Validator.isObservableWhenResult(condition)) {
17
17
  return DebugManager.warn('ShowIf', "ShowIf : condition must be an Observable / "+comment, condition);
18
18
  }
19
- const element = new Anchor('Show if : '+(comment || ''));
19
+ const element = Anchor('Show if : '+(comment || ''));
20
20
 
21
21
  let childElement = null;
22
22
  const getChildElement = () => {
@@ -18,7 +18,7 @@ export const Match = function($condition, values, shouldKeepInCache = true) {
18
18
  throw new NativeDocumentError("Toggle : condition must be an Observable");
19
19
  }
20
20
 
21
- const anchor = new Anchor('Match');
21
+ const anchor = Anchor('Match');
22
22
  const cache = new Map();
23
23
 
24
24
  const getItem = function(key) {
@@ -49,9 +49,20 @@ export const Match = function($condition, values, shouldKeepInCache = true) {
49
49
  if(content) {
50
50
  anchor.appendChild(content);
51
51
  }
52
- })
52
+ });
53
53
 
54
- return anchor;
54
+ return anchor.nd.with({
55
+ add(key, view, shouldFocusOn = false) {
56
+ values[key] = view;
57
+ if(shouldFocusOn) {
58
+ $condition.set(key);
59
+ }
60
+ },
61
+ remove(key) {
62
+ shouldKeepInCache && cache.delete(key);
63
+ delete values[key];
64
+ }
65
+ });
55
66
  }
56
67
 
57
68
 
@@ -0,0 +1,83 @@
1
+ import {Anchor} from "../../elements.js";
2
+
3
+ const ComponentRegistry = (function() {
4
+ console.log('ça s excecute')
5
+
6
+ const registry = new Map();
7
+
8
+ const wrapper = function(id, factory, metadata, registryItem) {
9
+ const factoryName = factory.name;
10
+
11
+ return function(...args) {
12
+ const lastParams = args[0];
13
+ console.log({ lastParams })
14
+ if(lastParams?.__instance) {
15
+ const instance = lastParams.__instance;
16
+ const componentArgs = Array.from(instance.context.args).pop();
17
+ const newInstance = factory(...componentArgs);
18
+ instance.anchor.setContent(newInstance);
19
+ return;
20
+ }
21
+ const instance = factory(...args);
22
+ const anchor = Anchor(factoryName);
23
+ anchor.setContent(instance);
24
+ registryItem.instances.add({
25
+ anchor,
26
+ context: {
27
+ args
28
+ }
29
+ });
30
+ console.log({ instance, anchor });
31
+ return anchor;
32
+ };
33
+ }
34
+
35
+ return {
36
+ /**
37
+ * @param {string} id
38
+ * @param {Function} factory
39
+ * @param {Object} metadata
40
+ */
41
+ register(id, factory, metadata = {}) {
42
+ if (!registry.has(id)) {
43
+ registry.set(id, {
44
+ factory,
45
+ instances: new Set(),
46
+ metadata,
47
+ states: {},
48
+ version: 0
49
+ });
50
+ }
51
+ return wrapper(id, factory, metadata, registry.get(id));
52
+ },
53
+ update(id, newFactory) {
54
+ const component = registry.get(id);
55
+ if(!component) {
56
+ console.warn(`[HMR] Component ${id} not found`);
57
+ return;
58
+ }
59
+ console.log(`[HMR] Updating ${component.instances.size} instance(s) of ${id}`);
60
+ const oldFactory = component.factory;
61
+ component.factory = newFactory;
62
+ component.version++;
63
+ const instances = Array.from(component.instances);
64
+ for (const instance of instances) {
65
+ try {
66
+ this.updateInstance(instance, newFactory);
67
+ } catch (error) {
68
+ console.error('[HMR] Update failed:', error);
69
+ // Rollback
70
+ component.factory = oldFactory;
71
+ component.version--;
72
+ throw error;
73
+ }
74
+ }
75
+ },
76
+ updateInstance(instance, newFactory) {
77
+ console.log(Array.from(registry.entries()))
78
+ return newFactory({ __instance: instance });
79
+ }
80
+ };
81
+ }());
82
+
83
+ export default ComponentRegistry;
@@ -0,0 +1,52 @@
1
+
2
+ if (import.meta.hot) {
3
+ import.meta.hot.accept((newModule) => {
4
+ console.log('[HMR Browser] Update accepted for ${id}');
5
+
6
+ if (!newModule) {
7
+ console.error('[HMR Browser] newModule is undefined!');
8
+ return;
9
+ }
10
+
11
+ try {
12
+ if (typeof window === 'undefined') {
13
+ console.error('[HMR Browser] window is undefined!');
14
+ return;
15
+ }
16
+ } catch (e) {
17
+ return;
18
+ }
19
+
20
+ // const NativeDocument = window.NativeDocument;
21
+ // if (!NativeDocument) {
22
+ // console.error('[HMR Browser] NativeDocument not found on window!');
23
+ // return;
24
+ // }
25
+
26
+ if (!ComponentRegistry) {
27
+ console.error('[HMR Browser] ComponentRegistry not found!');
28
+ return;
29
+ }
30
+
31
+ console.log('[HMR Browser] Calling ComponentRegistry.update()');
32
+ try {
33
+ ComponentRegistry.update('${id}', newModule.default);
34
+ console.log('[HMR Browser] ✓ Update successful');
35
+ } catch (error) {
36
+ console.error('[HMR Browser] ✗ Update failed:', error);
37
+ }
38
+ });
39
+
40
+ import.meta.hot.on('vite:error', (payload) => {
41
+ console.error('[HMR Browser] Vite error:', payload);
42
+ });
43
+
44
+
45
+ import.meta.hot.on('nd:update', (payload) => {
46
+ console.error('[HMR Browser] ND:Update:', payload);
47
+ });
48
+
49
+ import.meta.hot.dispose(() => {
50
+ console.log('[HMR Browser] Disposing ${id}');
51
+ });
52
+ }
@@ -0,0 +1,46 @@
1
+ // vite-plugin-native-document-hmr.js
2
+ import transformComponent from "./transformComponent.js";
3
+
4
+ export default function NdViteHotReload(options) {
5
+ const {
6
+ include = /\.nd\.js$/,
7
+ preserveState = true
8
+ } = options;
9
+
10
+ return {
11
+ name: 'vite-plugin-native-document-hmr',
12
+ apply: 'serve',
13
+ enforce: 'post',
14
+
15
+ handleHotUpdate({ file, server, modules }) {
16
+ if (!include.test(file)) {
17
+ return;
18
+ }
19
+
20
+ // Notify the browser about the change
21
+ server.ws.send({
22
+ type: 'nd-hmr-file',
23
+ event: 'nd:update',
24
+ data: { file, msg: 'The content has changed' }
25
+ });
26
+
27
+ // We will manage all manually
28
+ return modules.filter(Boolean);
29
+ },
30
+ transform(code, id) {
31
+ if (!include.test(id)) return null;
32
+ if (id.includes('node_modules')) return null;
33
+
34
+ try {
35
+ return transformComponent(id, code, { preserveState });
36
+ } catch (error) {
37
+ console.error(`[NativeDocument] Transform error in ${id}:`, error);
38
+ return null;
39
+ }
40
+ },
41
+
42
+ configResolved() {
43
+ console.log('[NativeDocument] HMR Plugin loaded ✓');
44
+ }
45
+ };
46
+ }
@@ -0,0 +1,32 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ // import { parse } from '@babel/parser';
4
+ import MagicString from 'magic-string';
5
+
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ export default function transformComponent(id, code) {
12
+ // TODO: move this line outside the function
13
+ const hrmHookTemplate = fs.readFileSync(__dirname + '/hrm.hook.template.js', 'utf8');
14
+ const s = new MagicString('');
15
+
16
+ const data = {
17
+ id: id,
18
+ };
19
+ let hrmHookTemplateFormatted = hrmHookTemplate;
20
+ for(const key in data) {
21
+ hrmHookTemplateFormatted = hrmHookTemplateFormatted.replace(new RegExp("\\$\{"+key+"}", 'ig'), data[key]);
22
+ }
23
+
24
+ s.append('import ComponentRegistry from "native-document/src/hrm/ComponentRegistry";');
25
+ s.append(code);
26
+ s.append(hrmHookTemplateFormatted);
27
+
28
+ return {
29
+ code: s.toString(),
30
+ map: s.generateMap({ source: id, hires: true })
31
+ };
32
+ }
@@ -57,4 +57,36 @@ export const getKey = (item, defaultKey, key) => {
57
57
 
58
58
  export const trim = function(str, char) {
59
59
  return str.replace(new RegExp(`^[${char}]+|[${char}]+$`, 'g'), '');
60
- }
60
+ }
61
+
62
+ export const deepClone = (value, onObservableFound) => {
63
+ // Primitives
64
+ if (value === null || typeof value !== 'object') {
65
+ return value;
66
+ }
67
+
68
+ // Dates
69
+ if (value instanceof Date) {
70
+ return new Date(value.getTime());
71
+ }
72
+
73
+ // Arrays
74
+ if (Array.isArray(value)) {
75
+ return value.map(item => deepClone(item));
76
+ }
77
+
78
+ // Observables - keep the référence
79
+ if (Validator.isObservable(value)) {
80
+ onObservableFound && onObservableFound(value);
81
+ return value;
82
+ }
83
+
84
+ // Objects
85
+ const cloned = {};
86
+ for (const key in value) {
87
+ if (value.hasOwnProperty(key)) {
88
+ cloned[key] = deepClone(value[key]);
89
+ }
90
+ }
91
+ return cloned;
92
+ };
@@ -50,7 +50,7 @@ const Validator = {
50
50
  return typeof value === 'function' && value.constructor.name === 'AsyncFunction';
51
51
  },
52
52
  isObject(value) {
53
- return typeof value === 'object';
53
+ return typeof value === 'object' && value !== null;
54
54
  },
55
55
  isJson(value) {
56
56
  return typeof value === 'object' && value !== null && !Array.isArray(value) && value.constructor.name === 'Object';
@@ -65,7 +65,7 @@ export const ElementCreator = {
65
65
  $nodeCache.set(name, node);
66
66
  return node.cloneNode();
67
67
  }
68
- return new Anchor('Fragment');
68
+ return Anchor('Fragment');
69
69
  },
70
70
  /**
71
71
  *
@@ -1,6 +1,9 @@
1
1
  import DocumentObserver from "./DocumentObserver";
2
2
  import PluginsManager from "../utils/plugins-manager";
3
3
  import Validator from "../utils/validator";
4
+ import NativeDocumentError from "../errors/NativeDocumentError.js";
5
+ import DebugManager from "../utils/debug-manager.js";
6
+
4
7
  import {EVENTS} from "../utils/events";
5
8
 
6
9
  export function NDElement(element) {
@@ -19,6 +22,11 @@ NDElement.prototype.ref = function(target, name) {
19
22
  return this;
20
23
  };
21
24
 
25
+ NDElement.prototype.refSelf = function(target, name) {
26
+ target[name] = this;
27
+ return this;
28
+ };
29
+
22
30
  NDElement.prototype.unmountChildren = function() {
23
31
  let element = this.$element;
24
32
  for(let i = 0, length = element.children.length; i < length; i++) {
@@ -209,4 +217,78 @@ for(const event of EVENTS) {
209
217
  captureEventWrapper(this.$element, eventName, directHandler);
210
218
  return this;
211
219
  };
212
- }
220
+ }
221
+
222
+ NDElement.prototype.with = function(methods) {
223
+ if (!methods || typeof methods !== 'object') {
224
+ throw new NativeDocumentError('extend() requires an object of methods');
225
+ }
226
+ if(process.env.NODE_ENV === 'development') {
227
+ if (!this.$localExtensions) {
228
+ this.$localExtensions = new Map();
229
+ }
230
+ }
231
+
232
+ for (const name in methods) {
233
+ const method = methods[name];
234
+
235
+ if (typeof method !== 'function') {
236
+ console.warn(`⚠️ extends(): "${name}" is not a function, skipping`);
237
+ continue;
238
+ }
239
+ if(process.env.NODE_ENV === 'development') {
240
+ if (this[name] && !this.$localExtensions.has(name)) {
241
+ DebugManager.warn('NDElement.extend', `Method "${name}" already exists and will be overwritten`);
242
+ }
243
+ this.$localExtensions.set(name, method);
244
+ }
245
+
246
+ this[name] = method.bind(this);
247
+ }
248
+
249
+ return this;
250
+ }
251
+
252
+ NDElement.extend = function(methods) {
253
+ if (!methods || typeof methods !== 'object') {
254
+ throw new NativeDocumentError('NDElement.extend() requires an object of methods');
255
+ }
256
+
257
+ if (Array.isArray(methods)) {
258
+ throw new NativeDocumentError('NDElement.extend() requires an object, not an array');
259
+ }
260
+
261
+ const protectedMethods = new Set([
262
+ 'constructor', 'valueOf', '$element', '$observer',
263
+ 'ref', 'remove', 'cleanup', 'with', 'extend', 'attach',
264
+ 'lifecycle', 'mounted', 'unmounted', 'unmountChildren'
265
+ ]);
266
+
267
+ for (const name in methods) {
268
+ if (!methods.hasOwnProperty(name)) {
269
+ continue;
270
+ }
271
+
272
+ const method = methods[name];
273
+
274
+ if (typeof method !== 'function') {
275
+ DebugManager.warn('NDElement.extend', `"${name}" is not a function, skipping`);
276
+ continue;
277
+ }
278
+
279
+ if (protectedMethods.has(name)) {
280
+ DebugManager.error('NDElement.extend', `Cannot override protected method "${name}"`);
281
+ throw new NativeDocumentError(`Cannot override protected method "${name}"`);
282
+ }
283
+
284
+ if (NDElement.prototype[name]) {
285
+ DebugManager.warn('NDElement.extend', `Overwriting existing prototype method "${name}"`);
286
+ }
287
+
288
+ NDElement.prototype[name] = method;
289
+ }
290
+
291
+ PluginsManager.emit('NDElementExtended', methods);
292
+
293
+ return NDElement;
294
+ };
@@ -1,9 +1,19 @@
1
1
  import { NDElement } from "./NDElement";
2
2
 
3
- Object.defineProperty(HTMLElement.prototype, 'nd', {
3
+ const property = {
4
4
  configurable: true,
5
5
  get() {
6
- return new NDElement(this);
6
+ return new NDElement(this);
7
+ }
8
+ };
9
+
10
+ Object.defineProperty(HTMLElement.prototype, 'nd', property);
11
+
12
+ Object.defineProperty(DocumentFragment.prototype, 'nd', property);
13
+ Object.defineProperty(NDElement.prototype, 'nd', {
14
+ configurable: true,
15
+ get: function() {
16
+ return this;
7
17
  }
8
18
  });
9
19
 
@@ -21,7 +21,7 @@ export function SingletonView($viewCreator) {
21
21
 
22
22
  this.createSection = (name, fn) => {
23
23
  $components = $components || {};
24
- const anchor = new Anchor('Component '+name);
24
+ const anchor = Anchor('Component '+name);
25
25
 
26
26
  $components[name] = function(...args) {
27
27
  anchor.removeChildren();