native-document 1.0.13 → 1.0.15

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.
Files changed (37) hide show
  1. package/dist/native-document.dev.js +1297 -804
  2. package/dist/native-document.min.js +1 -1
  3. package/docs/anchor.md +216 -53
  4. package/docs/conditional-rendering.md +25 -24
  5. package/docs/core-concepts.md +20 -19
  6. package/docs/elements.md +21 -20
  7. package/docs/getting-started.md +28 -27
  8. package/docs/lifecycle-events.md +2 -2
  9. package/docs/list-rendering.md +607 -0
  10. package/docs/memory-management.md +1 -1
  11. package/docs/observables.md +15 -14
  12. package/docs/routing.md +22 -22
  13. package/docs/state-management.md +8 -8
  14. package/docs/validation.md +0 -2
  15. package/index.js +6 -1
  16. package/package.json +1 -1
  17. package/readme.md +5 -4
  18. package/src/data/MemoryManager.js +8 -20
  19. package/src/data/Observable.js +2 -180
  20. package/src/data/ObservableChecker.js +26 -21
  21. package/src/data/ObservableItem.js +158 -79
  22. package/src/data/observable-helpers/array.js +74 -0
  23. package/src/data/observable-helpers/batch.js +22 -0
  24. package/src/data/observable-helpers/computed.js +28 -0
  25. package/src/data/observable-helpers/object.js +111 -0
  26. package/src/elements/anchor.js +54 -9
  27. package/src/elements/control/for-each-array.js +280 -0
  28. package/src/elements/control/for-each.js +100 -56
  29. package/src/elements/index.js +1 -0
  30. package/src/elements/list.js +4 -0
  31. package/src/utils/helpers.js +44 -21
  32. package/src/wrappers/AttributesWrapper.js +5 -18
  33. package/src/wrappers/DocumentObserver.js +58 -29
  34. package/src/wrappers/ElementCreator.js +114 -0
  35. package/src/wrappers/HtmlElementEventsWrapper.js +52 -65
  36. package/src/wrappers/HtmlElementWrapper.js +11 -167
  37. package/src/wrappers/NdPrototype.js +109 -0
package/docs/routing.md CHANGED
@@ -172,20 +172,20 @@ Add new entries to browser history. **Specify router name** when using multiple
172
172
 
173
173
  ```javascript
174
174
  const NavigationExample = Div([
175
- Button('Go to About').nd.on.click(() => {
175
+ Button('Go to About').nd.onClick(() => {
176
176
  Router.push('/about'); // Uses default router
177
177
  }),
178
178
 
179
- Button('Go to About (Main Router)').nd.on.click(() => {
179
+ Button('Go to About (Main Router)').nd.onClick(() => {
180
180
  Router.push('/about', 'main'); // Uses named router
181
181
  }),
182
182
 
183
- Button('View User 123').nd.on.click(() => {
183
+ Button('View User 123').nd.onClick(() => {
184
184
  // Navigate in specific router
185
185
  Router.push('/users/123', 'app');
186
186
  }),
187
187
 
188
- Button('Search Products').nd.on.click(() => {
188
+ Button('Search Products').nd.onClick(() => {
189
189
  Router.push('/search?term=laptop&category=electronics', 'main');
190
190
  })
191
191
  ]);
@@ -213,12 +213,12 @@ Navigate through browser history:
213
213
 
214
214
  ```javascript
215
215
  const HistoryControls = Div([
216
- Button('Go Back').nd.on.click(() => Router.back()), // Default router
217
- Button('Go Forward').nd.on.click(() => Router.forward()), // Default router
216
+ Button('Go Back').nd.onClick(() => Router.back()), // Default router
217
+ Button('Go Forward').nd.onClick(() => Router.forward()), // Default router
218
218
 
219
219
  // Navigate specific router's history
220
- Button('Back in Main').nd.on.click(() => Router.back('main')),
221
- Button('Forward in Admin').nd.on.click(() => Router.forward('admin'))
220
+ Button('Back in Main').nd.onClick(() => Router.back('main')),
221
+ Button('Forward in Admin').nd.onClick(() => Router.forward('admin'))
222
222
  ]);
223
223
  ```
224
224
 
@@ -245,18 +245,18 @@ const blogUrl = router.generateUrl('blog.post', { category: 'javascript', slug:
245
245
 
246
246
  ```javascript
247
247
  const navigation = Div([
248
- Button('Home').nd.on.click(() =>
248
+ Button('Home').nd.onClick(() =>
249
249
  Router.push({ name: 'home' }) // Uses router containing this route
250
250
  ),
251
251
 
252
- Button('My Profile').nd.on.click(() =>
252
+ Button('My Profile').nd.onClick(() =>
253
253
  Router.push({
254
254
  name: 'user.profile',
255
255
  params: { id: currentUser.id }
256
256
  }, 'main') // Specify router if needed
257
257
  ),
258
258
 
259
- Button('Latest Post').nd.on.click(() =>
259
+ Button('Latest Post').nd.onClick(() =>
260
260
  Router.push({
261
261
  name: 'blog.post',
262
262
  params: { category: 'news', slug: 'latest-update' },
@@ -467,11 +467,11 @@ const mainRouter = Router.routers.main;
467
467
  const adminRouter = Router.routers.admin;
468
468
 
469
469
  // Cross-router navigation
470
- Button('Go to Admin').nd.on.click(() => {
470
+ Button('Go to Admin').nd.onClick(() => {
471
471
  Router.push('/admin/users', 'admin'); // Specify router name
472
472
  });
473
473
 
474
- Button('Back to Main').nd.on.click(() => {
474
+ Button('Back to Main').nd.onClick(() => {
475
475
  Router.push('/', 'main'); // Navigate in main router
476
476
  });
477
477
  ```
@@ -707,7 +707,7 @@ const ProductDetail = ({ params, query }) => {
707
707
  P(p.description),
708
708
 
709
709
  // Add to cart with redirect to login if needed
710
- Button('Add to Cart').nd.on.click(() => {
710
+ Button('Add to Cart').nd.onClick(() => {
711
711
  if (!isAuthenticated()) {
712
712
  Router.push({
713
713
  name: 'login',
@@ -773,22 +773,22 @@ const FormNavigation = (currentStep, formData) => {
773
773
  // Navigation buttons
774
774
  Div({ class: 'nav-buttons' }, [
775
775
  ShowIf(Observable(currentIndex > 0),
776
- Button('Previous').nd.on.click(() => {
776
+ Button('Previous').nd.onClick(() => {
777
777
  const prevStep = steps[currentIndex - 1];
778
778
  Router.push({ name: `form.${prevStep}` });
779
779
  })
780
780
  ),
781
781
 
782
782
  ShowIf(Observable(currentIndex < steps.length - 1),
783
- Button('Next').nd.on.click(() => {
783
+ Button('Next').nd.onClick(() => {
784
784
  const nextStep = steps[currentIndex + 1];
785
785
  Router.push({ name: `form.${nextStep}` });
786
786
  })
787
787
  ),
788
788
 
789
789
  ShowIf(Observable(currentIndex === steps.length - 1),
790
- Button('Submit').nd.on.click(() => {
791
- submitForm(formData.$val());
790
+ Button('Submit').nd.onClick(() => {
791
+ submitForm(formData.$value);
792
792
  })
793
793
  )
794
794
  ])
@@ -811,7 +811,7 @@ const FormNavigation = (currentStep, formData) => {
811
811
 
812
812
  Explore these related topics to build complete applications:
813
813
 
814
- - **[State Management](docs/state-management.md)** - Global state patterns
815
- - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
816
- - **[Memory Management](docs/memory-management.md)** - Memory management
817
- - **[Anchor](docs/anchor.md)** - Anchor
814
+ - **[State Management](state-management.md)** - Global state patterns
815
+ - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
816
+ - **[Memory Management](memory-management.md)** - Memory management
817
+ - **[Anchor](anchor.md)** - Anchor
@@ -95,7 +95,7 @@ const UserMenu = () => {
95
95
  return Nav([
96
96
  ShowIf(user.check(u => u.isLoggedIn), [
97
97
  Link({ to: '/profile' }, 'My Profile'),
98
- Button('Logout').nd.on.click(() => {
98
+ Button('Logout').nd.onClick(() => {
99
99
  user.set({ ...user.$value, isLoggedIn: false });
100
100
  })
101
101
  ])
@@ -126,7 +126,7 @@ Modify store state using the returned observable's methods:
126
126
  const ThemeToggle = () => {
127
127
  const theme = Store.use('theme');
128
128
 
129
- return Button('Toggle Theme').nd.on.click(() => {
129
+ return Button('Toggle Theme').nd.onClick(() => {
130
130
  const current = theme.$value;
131
131
  theme.set(current === 'light' ? 'dark' : 'light');
132
132
  });
@@ -154,7 +154,7 @@ const LoginForm = () => {
154
154
  return Form([
155
155
  Input({ type: 'email', value: email, placeholder: 'Email' }),
156
156
  Input({ type: 'password', value: password, placeholder: 'Password' }),
157
- Button('Login').nd.on.click(handleLogin)
157
+ Button('Login').nd.onClick(handleLogin)
158
158
  ]);
159
159
  };
160
160
  ```
@@ -187,9 +187,9 @@ const UserSettings = () => {
187
187
  Input({
188
188
  value: user.check(u => u.name),
189
189
  placeholder: 'Name'
190
- }).nd.on.input(e => updateName(e.target.value)),
190
+ }).nd.onInput(e => updateName(e.target.value)),
191
191
 
192
- Button('Dark Mode').nd.on.click(() =>
192
+ Button('Dark Mode').nd.onClick(() =>
193
193
  updatePreferences({ theme: 'dark' })
194
194
  )
195
195
  ]);
@@ -418,6 +418,6 @@ const createAppState = () => {
418
418
 
419
419
  Now that you understand state management, explore these related topics:
420
420
 
421
- - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
422
- - **[Memory Management](docs/memory-management.md)** - Memory management
423
- - **[Anchor](docs/anchor.md)** - Anchor
421
+ - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
422
+ - **[Memory Management](memory-management.md)** - Memory management
423
+ - **[Anchor](anchor.md)** - Anchor
@@ -187,7 +187,5 @@ registerUser.args(
187
187
 
188
188
  ## Next Steps
189
189
 
190
- - **[Advanced Features](advanced-features.md)** - Error boundaries and debugging tools
191
- - **[API Reference](api-reference.md)** - Complete ArgTypes and validation API
192
190
  - **[Memory Management](memory-management.md)** - Debugging memory issues with validation
193
191
  - **[Lifecycle Events](lifecycle-events.md)** - Validate lifecycle callback arguments
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export { default as HtmlElementWrapper, ElementCreator } from './src/wrappers/HtmlElementWrapper'
1
+ export { default as HtmlElementWrapper } from './src/wrappers/HtmlElementWrapper'
2
+ export { ElementCreator } from './src/wrappers/ElementCreator'
2
3
 
3
4
  import './src/utils/prototypes.js';
4
5
 
@@ -6,6 +7,10 @@ export * from './src/utils/plugins-manager';
6
7
  export * from './src/utils/args-types';
7
8
  export * from './src/utils/validator'
8
9
  export * from './src/data/Observable';
10
+ export * from './src/data/observable-helpers/array';
11
+ export * from './src/data/observable-helpers/batch';
12
+ export * from './src/data/observable-helpers/object';
13
+ export * from './src/data/observable-helpers/computed';
9
14
  export * from './src/data/Store';
10
15
  import * as elements from './elements';
11
16
  import * as router from './router';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -30,7 +30,7 @@ const count = Observable(0);
30
30
  const App = Div({ class: 'app' }, [
31
31
  Div([ 'Count ', count]),
32
32
  // OR Div(`Count ${count}`),
33
- Button('Increment').nd.on.click(() => count.set(count.val() + 1))
33
+ Button('Increment').nd.onClick(() => count.set(count.val() + 1))
34
34
  ]);
35
35
 
36
36
  document.body.appendChild(App);
@@ -91,7 +91,7 @@ const TodoApp = Div({ class: 'todo-app' }, [
91
91
  Input({ placeholder: 'Add new task...', value: newTodo }),
92
92
 
93
93
  // Add button
94
- Button('Add Todo').nd.on.click(() => {
94
+ Button('Add Todo').nd.onClick(() => {
95
95
  if (newTodo.val().trim()) {
96
96
  todos.push({ id: Date.now(), text: newTodo.val(), done: false })
97
97
  newTodo.set('')
@@ -103,7 +103,7 @@ const TodoApp = Div({ class: 'todo-app' }, [
103
103
  Div({ class: 'todo-item' }, [
104
104
  Input({ type: 'checkbox', checked: todo.done }),
105
105
  `${todo.text}`,
106
- Button('Delete').nd.on.click(() => todos.splice(index.val(), 1))
106
+ Button('Delete').nd.onClick(() => todos.splice(index.val(), 1))
107
107
  ]), /*item key (string | callback) */(item) => item),
108
108
 
109
109
  // Empty state
@@ -158,7 +158,7 @@ const App = function() {
158
158
  class: { 'hidden': isVisible.check(v => !v) },
159
159
  style: { opacity: isVisible.check(v => v ? 1 : 0.2) }
160
160
  }, 'Content'),
161
- Button('Toggle').nd.on.click(() => isVisible.set(v => !v)),
161
+ Button('Toggle').nd.onClick(() => isVisible.set(v => !v)),
162
162
  ]);
163
163
  };
164
164
 
@@ -191,6 +191,7 @@ When(condition)
191
191
  - **[Observables](docs/observables.md)** - Reactive state management
192
192
  - **[Elements](docs/elements.md)** - Creating and composing UI
193
193
  - **[Conditional Rendering](docs/conditional-rendering.md)** - Dynamic content
194
+ - **[List Rendering](docs/list-rendering.md)** - List Rendering
194
195
  - **[Routing](docs/routing.md)** - Navigation and URL management
195
196
  - **[State Management](docs/state-management.md)** - Global state patterns
196
197
  - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
@@ -1,40 +1,28 @@
1
1
  import DebugManager from "../utils/debug-manager";
2
+ import Validator from "../utils/validator";
2
3
 
3
4
 
4
5
  const MemoryManager = (function() {
5
6
 
6
- let $nexObserverId = 0;
7
+ let $nextObserverId = 0;
7
8
  const $observables = new Map();
8
- let $registry = null;
9
- try {
10
- $registry = new FinalizationRegistry((heldValue) => {
11
- DebugManager.log('MemoryManager', '🧹 Auto-cleanup observable:', heldValue);
12
- heldValue.listeners.splice(0);
13
- });
14
- } catch (e) {
15
- DebugManager.warn('MemoryManager', 'FinalizationRegistry not supported, observables will not be cleaned automatically');
16
- }
17
9
 
18
10
  return {
19
11
  /**
20
12
  * Register an observable and return an id.
21
13
  *
22
14
  * @param {ObservableItem} observable
23
- * @param {Function[]} listeners
15
+ * @param {Function} getListeners
24
16
  * @returns {number}
25
17
  */
26
- register(observable, listeners) {
27
- const id = ++$nexObserverId;
28
- const heldValue = {
29
- id: id,
30
- listeners
31
- };
32
- if($registry) {
33
- $registry.register(observable, heldValue);
34
- }
18
+ register(observable) {
19
+ const id = ++$nextObserverId;
35
20
  $observables.set(id, new WeakRef(observable));
36
21
  return id;
37
22
  },
23
+ unregister(id) {
24
+ $observables.delete(id);
25
+ },
38
26
  getObservableById(id) {
39
27
  return $observables.get(id)?.deref();
40
28
  },
@@ -2,7 +2,7 @@ import ObservableItem from './ObservableItem';
2
2
  import Validator from "../utils/validator";
3
3
  import MemoryManager from "./MemoryManager";
4
4
  import NativeDocumentError from "../errors/NativeDocumentError";
5
- import {throttle} from "../utils/helpers.js";
5
+ import {debounce} from "../utils/helpers.js";
6
6
 
7
7
  /**
8
8
  *
@@ -14,45 +14,6 @@ export function Observable(value) {
14
14
  return new ObservableItem(value);
15
15
  }
16
16
 
17
- /**
18
- *
19
- * @param {Function} callback
20
- * @param {Array|Function} dependencies
21
- * @returns {ObservableItem}
22
- */
23
- Observable.computed = function(callback, dependencies = []) {
24
- const initialValue = callback();
25
- const observable = new ObservableItem(initialValue);
26
- const updatedValue = () => observable.set(callback());
27
-
28
- if(Validator.isFunction(dependencies)) {
29
- if(!Validator.isObservable(dependencies.$observer)) {
30
- throw new NativeDocumentError('Observable.computed : dependencies must be valid batch function');
31
- }
32
- dependencies.$observer.subscribe(updatedValue);
33
- return observable;
34
- }
35
-
36
- dependencies.forEach(dependency => dependency.subscribe(updatedValue));
37
-
38
- return observable;
39
- };
40
-
41
- Observable.batch = function(callback) {
42
- const $observer = Observable(0);
43
- const batch = function() {
44
- if(Validator.isAsyncFunction(callback)) {
45
- return (callback(...arguments)).then(() => {
46
- $observer.trigger();
47
- }).catch(error => { throw error; });
48
- }
49
- callback(...arguments);
50
- $observer.trigger();
51
- };
52
- batch.$observer = $observer;
53
- return batch;
54
- }
55
-
56
17
  /**
57
18
  *
58
19
  * @param id
@@ -66,7 +27,6 @@ Observable.getById = function(id) {
66
27
  return item;
67
28
  };
68
29
 
69
-
70
30
  /**
71
31
  *
72
32
  * @param {ObservableItem} observable
@@ -75,144 +35,6 @@ Observable.cleanup = function(observable) {
75
35
  observable.cleanup();
76
36
  };
77
37
 
78
- /**
79
- * Get the value of an observable or an object of observables.
80
- * @param {ObservableItem|Object<ObservableItem>} object
81
- * @returns {{}|*|null}
82
- */
83
- Observable.value = function(data) {
84
- if(Validator.isObservable(data)) {
85
- return data.val();
86
- }
87
- if(Validator.isProxy(data)) {
88
- return data.$val();
89
- }
90
- if(Validator.isArray(data)) {
91
- const result = [];
92
- data.forEach(item => {
93
- result.push(Observable.value(item));
94
- });
95
- return result;
96
- }
97
- return data;
98
- }
99
-
100
- /**
101
- *
102
- * @param {Object} value
103
- * @returns {Proxy}
104
- */
105
- Observable.init = function(value) {
106
- const data = {};
107
- for(const key in value) {
108
- const itemValue = value[key];
109
- if(Validator.isJson(itemValue)) {
110
- data[key] = Observable.init(itemValue);
111
- continue;
112
- }
113
- else if(Validator.isArray(itemValue)) {
114
- data[key] = Observable.array(itemValue);
115
- continue;
116
- }
117
- data[key] = Observable(itemValue);
118
- }
119
-
120
- const $val = function() {
121
- const result = {};
122
- for(const key in data) {
123
- const dataItem = data[key];
124
- if(Validator.isObservable(dataItem)) {
125
- result[key] = dataItem.val();
126
- } else if(Validator.isProxy(dataItem)) {
127
- result[key] = dataItem.$val();
128
- } else {
129
- result[key] = dataItem;
130
- }
131
- }
132
- return result;
133
- };
134
- const $clone = function() {
135
-
136
- };
137
-
138
- return new Proxy(data, {
139
- get(target, property) {
140
- if(property === '__isProxy__') {
141
- return true;
142
- }
143
- if(property === '$val') {
144
- return $val;
145
- }
146
- if(property === '$clone') {
147
- return $clone;
148
- }
149
- if(target[property] !== undefined) {
150
- return target[property];
151
- }
152
- return undefined;
153
- },
154
- set(target, prop, newValue) {
155
- if(target[prop] !== undefined) {
156
- target[prop].set(newValue);
157
- }
158
- }
159
- })
160
- };
161
-
162
- Observable.object = Observable.init;
163
- Observable.json = Observable.init;
164
- Observable.update = function($target, data) {
165
- for(const key in data) {
166
- const targetItem = $target[key];
167
- const newValue = data[key];
168
-
169
- if(Validator.isObservable(targetItem)) {
170
- if(Validator.isArray(newValue)) {
171
- Observable.update(targetItem, newValue);
172
- continue;
173
- }
174
- targetItem.set(newValue);
175
- continue;
176
- }
177
- if(Validator.isProxy(targetItem)) {
178
- Observable.update(targetItem, newValue);
179
- continue;
180
- }
181
- $target[key] = newValue;
182
- }
183
- };
184
- /**
185
- *
186
- * @param {Array} target
187
- * @returns {ObservableItem}
188
- */
189
- Observable.array = function(target) {
190
- if(!Array.isArray(target)) {
191
- throw new NativeDocumentError('Observable.array : target must be an array');
192
- }
193
- const observer = Observable(target);
194
-
195
- const methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'];
196
-
197
- methods.forEach((method) => {
198
- observer[method] = function(...values) {
199
- const target = observer.val();
200
- const result = target[method].apply(target, arguments);
201
- observer.trigger();
202
- return result;
203
- };
204
- });
205
-
206
- const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find'];
207
- overrideMethods.forEach((method) => {
208
- observer[method] = function(callback) {
209
- return observer.val()[method](callback);
210
- };
211
- })
212
-
213
- return observer;
214
- };
215
-
216
38
  /**
217
39
  * Enable auto cleanup of observables.
218
40
  * @param {Boolean} enable
@@ -229,5 +51,5 @@ Observable.autoCleanup = function(enable = false, options = {}) {
229
51
  });
230
52
 
231
53
  setInterval(() => MemoryManager.cleanObservables(threshold), interval);
232
- }
54
+ };
233
55
 
@@ -7,28 +7,33 @@
7
7
  export default function ObservableChecker($observable, $checker) {
8
8
  this.observable = $observable;
9
9
  this.checker = $checker;
10
+ this.unSubscriptions = [];
11
+ }
10
12
 
11
- this.subscribe = function(callback) {
12
- return $observable.subscribe((value) => {
13
- callback && callback($checker(value));
14
- });
15
- };
13
+ ObservableChecker.prototype.subscribe = function(callback) {
14
+ const unSubscribe = this.observable.subscribe((value) => {
15
+ callback && callback(this.checker(value));
16
+ });
17
+ this.unSubscriptions.push(unSubscribe);
18
+ return unSubscribe;
19
+ };
16
20
 
17
- this.val = function() {
18
- return $checker && $checker($observable.val());
19
- };
20
- this.check = function(callback) {
21
- return $observable.check(() => callback(this.val()));
22
- };
21
+ ObservableChecker.prototype.check = function(callback) {
22
+ return this.observable.check(() => callback(this.val()));
23
+ }
23
24
 
24
- this.set = function(value) {
25
- return $observable.set(value);
26
- };
27
- this.trigger = function() {
28
- return $observable.trigger();
29
- }
25
+ ObservableChecker.prototype.val = function() {
26
+ return this.checker && this.checker(this.observable.val());
27
+ }
30
28
 
31
- this.cleanup = function() {
32
- return $observable.cleanup();
33
- }
34
- }
29
+ ObservableChecker.prototype.set = function(value) {
30
+ return this.observable.set(value);
31
+ };
32
+
33
+ ObservableChecker.prototype.trigger = function() {
34
+ return this.observable.trigger();
35
+ };
36
+
37
+ ObservableChecker.prototype.cleanup = function() {
38
+ return this.observable.cleanup();
39
+ };