native-document 1.0.91 → 1.0.93

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 (68) hide show
  1. package/dist/native-document.components.min.js +1168 -138
  2. package/dist/native-document.dev.js +792 -217
  3. package/dist/native-document.dev.js.map +1 -1
  4. package/dist/native-document.devtools.min.js +1 -1
  5. package/dist/native-document.min.js +1 -1
  6. package/docs/advanced-components.md +814 -0
  7. package/docs/anchor.md +71 -11
  8. package/docs/cache.md +888 -0
  9. package/docs/conditional-rendering.md +91 -1
  10. package/docs/core-concepts.md +9 -2
  11. package/docs/elements.md +127 -2
  12. package/docs/extending-native-document-element.md +7 -1
  13. package/docs/filters.md +1216 -0
  14. package/docs/getting-started.md +12 -3
  15. package/docs/lifecycle-events.md +10 -2
  16. package/docs/list-rendering.md +453 -54
  17. package/docs/memory-management.md +9 -7
  18. package/docs/native-document-element.md +30 -9
  19. package/docs/native-fetch.md +744 -0
  20. package/docs/observables.md +135 -6
  21. package/docs/routing.md +7 -1
  22. package/docs/state-management.md +7 -1
  23. package/docs/validation.md +8 -1
  24. package/eslint.config.js +3 -3
  25. package/package.json +3 -2
  26. package/readme.md +53 -14
  27. package/src/components/$traits/HasItems.js +42 -1
  28. package/src/components/BaseComponent.js +4 -1
  29. package/src/components/accordion/Accordion.js +112 -8
  30. package/src/components/accordion/AccordionItem.js +93 -4
  31. package/src/components/alert/Alert.js +164 -4
  32. package/src/components/avatar/Avatar.js +236 -22
  33. package/src/components/menu/index.js +1 -2
  34. package/src/core/data/ObservableArray.js +120 -2
  35. package/src/core/data/ObservableChecker.js +50 -0
  36. package/src/core/data/ObservableItem.js +223 -80
  37. package/src/core/data/ObservableWhen.js +36 -6
  38. package/src/core/data/observable-helpers/array.js +12 -3
  39. package/src/core/data/observable-helpers/computed.js +17 -4
  40. package/src/core/data/observable-helpers/object.js +19 -3
  41. package/src/core/elements/control/for-each-array.js +21 -3
  42. package/src/core/elements/control/for-each.js +17 -5
  43. package/src/core/elements/control/show-if.js +31 -15
  44. package/src/core/elements/control/show-when.js +23 -0
  45. package/src/core/elements/control/switch.js +40 -10
  46. package/src/core/utils/cache.js +5 -0
  47. package/src/core/utils/memoize.js +25 -16
  48. package/src/core/utils/prototypes.js +3 -2
  49. package/src/core/wrappers/AttributesWrapper.js +1 -1
  50. package/src/core/wrappers/NDElement.js +41 -1
  51. package/src/core/wrappers/NdPrototype.js +4 -0
  52. package/src/core/wrappers/TemplateCloner.js +13 -10
  53. package/src/core/wrappers/prototypes/bind-class-extensions.js +1 -1
  54. package/src/core/wrappers/prototypes/nd-element-extensions.js +3 -0
  55. package/src/router/Route.js +9 -4
  56. package/src/router/Router.js +28 -9
  57. package/src/router/errors/RouterError.js +0 -1
  58. package/types/control-flow.d.ts +9 -6
  59. package/types/elements.d.ts +6 -3
  60. package/types/filters/index.d.ts +4 -0
  61. package/types/nd-element.d.ts +5 -238
  62. package/types/observable.d.ts +9 -3
  63. package/types/router.d.ts +5 -1
  64. package/types/template-cloner.ts +1 -0
  65. package/types/validator.ts +11 -1
  66. package/utils.d.ts +2 -1
  67. package/utils.js +4 -4
  68. package/src/core/utils/service.js +0 -6
@@ -615,10 +615,12 @@ console.log(isAdult.val()); // true
615
615
  const data = Observable("test");
616
616
 
617
617
  // Create a subscription
618
- const unsubscribe = data.subscribe(value => console.log(value));
618
+ const handler = value => console.log(value);
619
+ data.subscribe(handler);
620
+
619
621
 
620
622
  // Clean up manually if needed
621
- unsubscribe();
623
+ data.unsubscribe('test', handler);
622
624
 
623
625
  // Complete observable cleanup
624
626
  data.cleanup(); // Removes all listeners and prevents new subscriptions
@@ -626,9 +628,6 @@ data.cleanup(); // Removes all listeners and prevents new subscriptions
626
628
  // Manual trigger (useful for forcing updates)
627
629
  data.trigger(); // Notifies all subscribers without changing the value
628
630
 
629
- // Get original value (useful for reset functionality)
630
- console.log(data.originalValue()); // Returns the initial value
631
-
632
631
  // Extract values from any observable structure
633
632
  const complexData = Observable.object({
634
633
  user: "John",
@@ -637,6 +636,130 @@ const complexData = Observable.object({
637
636
  console.log(Observable.value(complexData)); // Plain object with extracted values
638
637
  ```
639
638
 
639
+ ## Utility Methods
640
+
641
+ ### `off(value, callback?)` - Remove Watchers
642
+
643
+ Remove specific value watchers created with `.on()`:
644
+ ```javascript
645
+ const status = Observable("idle");
646
+
647
+ const loadingHandler = (isActive) => console.log("Loading:", isActive);
648
+ status.on("loading", loadingHandler);
649
+
650
+ // Remove specific callback
651
+ status.off("loading", loadingHandler);
652
+
653
+ // Remove all watchers for a value
654
+ status.off("loading");
655
+ ```
656
+
657
+ ### `once(predicate, callback)` - Single-Time Listener
658
+
659
+ Execute callback only once when condition is met:
660
+ ```javascript
661
+ const count = Observable(0);
662
+
663
+ // Wait for specific value
664
+ count.once(5, (value) => {
665
+ console.log("Reached 5!"); // Only called once
666
+ });
667
+
668
+ // With predicate function
669
+ count.once(val => val > 10, (value) => {
670
+ console.log("Greater than 10!"); // Only called once
671
+ });
672
+
673
+ count.set(5); // Callback fires and unsubscribes
674
+ count.set(5); // Callback doesn't fire again
675
+ ```
676
+
677
+ ### `toggle()` - Boolean Toggle
678
+
679
+ Toggle boolean observables:
680
+ ```javascript
681
+ const isVisible = Observable(false);
682
+
683
+ isVisible.toggle(); // true
684
+ isVisible.toggle(); // false
685
+ isVisible.toggle(); // true
686
+
687
+ // Useful with buttons
688
+ Button("Toggle").nd.onClick(() => isVisible.toggle());
689
+ ```
690
+
691
+ ### `reset()` - Reset to Initial Value
692
+
693
+ Reset observable to its initial value (requires `reset: true` config):
694
+ ```javascript
695
+ const name = Observable("Alice", { reset: true });
696
+
697
+ name.set("Bob");
698
+ console.log(name.val()); // "Bob"
699
+
700
+ name.reset();
701
+ console.log(name.val()); // "Alice" (initial value)
702
+
703
+ // With objects
704
+ const user = Observable({ name: "Alice", age: 25 }, { reset: true });
705
+ user.set({ name: "Bob", age: 30 });
706
+ user.reset(); // Back to { name: "Alice", age: 25 }
707
+ ```
708
+
709
+ ### `equals(other)` - Value Comparison
710
+
711
+ Compare observable values:
712
+ ```javascript
713
+ const num1 = Observable(5);
714
+ const num2 = Observable(5);
715
+ const num3 = Observable(10);
716
+
717
+ console.log(num1.equals(num2)); // true (same value)
718
+ console.log(num1.equals(5)); // true (compare with raw value)
719
+ console.log(num1.equals(num3)); // false
720
+ ```
721
+
722
+ ### `toBool()` - Boolean Conversion
723
+
724
+ Convert observable value to boolean:
725
+ ```javascript
726
+ const text = Observable("");
727
+ console.log(text.toBool()); // false
728
+
729
+ text.set("Hello");
730
+ console.log(text.toBool()); // true
731
+
732
+ // Useful for conditions
733
+ const hasContent = text.toBool();
734
+ ```
735
+
736
+ ### `intercept(callback)` - Value Interception
737
+
738
+ Intercept and modify values before they're set:
739
+ ```javascript
740
+ const age = Observable(0);
741
+
742
+ // Intercept sets to enforce constraints
743
+ age.intercept((newValue, oldValue) => {
744
+ if (newValue < 0) return 0;
745
+ if (newValue > 120) return 120;
746
+ return newValue;
747
+ });
748
+
749
+ age.set(-5); // Actually sets 0
750
+ age.set(150); // Actually sets 120
751
+ age.set(25); // Sets 25
752
+
753
+ // Practical example: sanitize input
754
+ const username = Observable("");
755
+ username.intercept((value) => {
756
+ return value.toLowerCase().trim();
757
+ });
758
+
759
+ username.set(" JohnDoe ");
760
+ console.log(username.val()); // "johndoe"
761
+ ```
762
+
640
763
  ## Best Practices
641
764
 
642
765
  1. **Use descriptive names** for your observables
@@ -663,4 +786,10 @@ Now that you understand NativeDocument's observable, explore these advanced topi
663
786
  - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
664
787
  - **[Args Validation](validation.md)** - Function Argument Validation
665
788
  - **[Memory Management](memory-management.md)** - Memory management
666
- - **[Anchor](anchor.md)** - Anchor
789
+ - **[Anchor](anchor.md)** - Anchor
790
+
791
+ ## Utilities
792
+
793
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
794
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
795
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers
package/docs/routing.md CHANGED
@@ -817,4 +817,10 @@ Explore these related topics to build complete applications:
817
817
  - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
818
818
  - **[Args Validation](validation.md)** - Function Argument Validation
819
819
  - **[Memory Management](memory-management.md)** - Memory management
820
- - **[Anchor](anchor.md)** - Anchor
820
+ - **[Anchor](anchor.md)** - Anchor
821
+
822
+ ## Utilities
823
+
824
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
825
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
826
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers
@@ -423,4 +423,10 @@ Now that you understand state management, explore these related topics:
423
423
  - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
424
424
  - **[Args Validation](validation.md)** - Function Argument Validation
425
425
  - **[Memory Management](memory-management.md)** - Memory management
426
- - **[Anchor](anchor.md)** - Anchor
426
+ - **[Anchor](anchor.md)** - Anchor
427
+
428
+ ## Utilities
429
+
430
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
431
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
432
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers
@@ -190,4 +190,11 @@ registerUser.args(
190
190
  - **[Lifecycle Events](lifecycle-events.md)** - Validate lifecycle callback arguments
191
191
  - **[NDElement](native-document-element.md)** - Native Document Element
192
192
  - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
193
- - **[Memory Management](memory-management.md)** - Debugging memory issues with validation
193
+ - **[Advanced Components](advanced-components.md)** - Template caching and singleton views
194
+ - **[Memory Management](memory-management.md)** - Debugging memory issues with validation
195
+
196
+ ## Utilities
197
+
198
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
199
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
200
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers
package/eslint.config.js CHANGED
@@ -3,18 +3,18 @@ import globals from 'globals'
3
3
 
4
4
  export default [
5
5
  { ignores: ['dist'] },
6
+ js.configs.recommended,
6
7
  {
7
8
  files: ['**/*.{js}'],
8
9
  languageOptions: {
9
- ecmaVersion: 2020,
10
+ ecmaVersion: 'latest',
10
11
  globals: globals.browser,
11
12
  parserOptions: {
12
13
  ecmaVersion: 'latest',
13
14
  sourceType: 'module',
14
15
  },
15
16
  },
16
- plugins: {
17
- },
17
+ plugins: {},
18
18
  rules: {
19
19
  ...js.configs.recommended.rules,
20
20
  },
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.91",
3
+ "version": "1.0.93",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "rollup --config rollup.config.js --watch",
8
- "lint": "eslint ."
8
+ "lint": "eslint ./src"
9
9
  },
10
10
  "keywords": [],
11
11
  "author": "",
@@ -18,6 +18,7 @@
18
18
  "@rollup/plugin-replace": "^6.0.2",
19
19
  "@rollup/plugin-terser": "^0.4.4",
20
20
  "eslint": "^9.33.0",
21
+ "eslint-plugin-jsdoc": "^62.5.4",
21
22
  "magic-string": "^0.30.21",
22
23
  "rollup": "^4.53.3"
23
24
  },
package/readme.md CHANGED
@@ -10,6 +10,7 @@
10
10
  NativeDocument combines the familiarity of vanilla JavaScript with the power of modern reactivity. No compilation, no virtual DOM, just pure JavaScript with an intuitive API.
11
11
 
12
12
  ## Why NativeDocument?
13
+ > **Note**: NativeDocument works best with a bundler (Vite, Webpack, Rollup) for tree-shaking and optimal bundle size. The CDN version includes all features
13
14
 
14
15
  ### **Instant Start**
15
16
  ```html
@@ -18,7 +19,7 @@ NativeDocument combines the familiarity of vanilla JavaScript with the power of
18
19
 
19
20
  ### **Familiar API**
20
21
  ```javascript
21
- import { Div, Button } from 'native-document/src/elements';
22
+ import { Div, Button } from 'native-document/elements';
22
23
  import { Observable } from 'native-document';
23
24
 
24
25
  // CDN
@@ -29,7 +30,6 @@ const count = Observable(0);
29
30
 
30
31
  const App = Div({ class: 'app' }, [
31
32
  Div([ 'Count ', count]),
32
- // OR Div(`Count ${count}`),
33
33
  Button('Increment').nd.onClick(() => count.set(count.val() + 1))
34
34
  ]);
35
35
 
@@ -74,7 +74,7 @@ yarn add native-document
74
74
  ## Quick Example
75
75
 
76
76
  ```javascript
77
- import { Div, Input, Button, ShowIf, ForEach } from 'native-document/src/elements'
77
+ import { Div, Input, Button, ShowIf, ForEach } from 'native-document/elements'
78
78
  import { Observable } from 'native-document'
79
79
 
80
80
  // CDN
@@ -105,7 +105,7 @@ const TodoApp = Div({ class: 'todo-app' }, [
105
105
  Input({ type: 'checkbox', checked: todo.done }),
106
106
  `${todo.text}`,
107
107
  Button('Delete').nd.onClick(() => todos.splice(index.val(), 1))
108
- ]), /*item key (string | callback) */(item) => item),
108
+ ]), (item) => item.id ), // Key function - use unique identifier
109
109
 
110
110
  // Empty state
111
111
  ShowIf(
@@ -122,7 +122,7 @@ document.body.appendChild(TodoApp)
122
122
  ### Observables
123
123
  Reactive data that automatically updates the DOM:
124
124
  ```javascript
125
- import { Div } from 'native-document/src/elements'
125
+ import { Div } from 'native-document/elements'
126
126
  import { Observable } from 'native-document'
127
127
 
128
128
  // CDN
@@ -135,16 +135,19 @@ const greeting = Observable.computed(() => `Hello ${user.$value.name}!`, [user])
135
135
 
136
136
  document.body.appendChild(Div(greeting));
137
137
 
138
- // user.name = 'Fausty'; // will not work
139
- // user.$value = { ...user.$value, name: ' Hermes!' }; // will work
140
- // user.set(data => ({ ...data, name: 'Hermes!' })); // will work
138
+ // Direct mutation won't trigger updates
139
+ user.name = 'Fausty';
140
+
141
+ // These will trigger updates:
142
+ user.$value = { ...user.$value, name: ' Hermes!' };
143
+ user.set(data => ({ ...data, name: 'Hermes!' }));
141
144
  user.set({ ...user.val(), name: 'Hermes!' });
142
145
  ```
143
146
 
144
147
  ### Elements
145
148
  Familiar HTML element creation with reactive bindings:
146
149
  ```javascript
147
- import { Div, Button } from 'native-document/src/elements'
150
+ import { Div, Button } from 'native-document/elements'
148
151
  import { Observable } from 'native-document'
149
152
 
150
153
  // CDN
@@ -185,6 +188,30 @@ When(condition)
185
188
  .otherwise(onFalse)
186
189
  ```
187
190
 
191
+ ### List Rendering
192
+ Efficient rendering of lists with automatic updates:
193
+ ```javascript
194
+ import { ForEach, Div } from 'native-document/elements'
195
+ import { Observable } from 'native-document'
196
+
197
+ const items = Observable.array(['Apple', 'Banana', 'Cherry'])
198
+
199
+ ForEach(items, (item, index) =>
200
+ Div([index, '. ', item])
201
+ )
202
+
203
+ // With object arrays - use key function
204
+ const users = Observable.array([
205
+ { id: 1, name: 'Alice' },
206
+ { id: 2, name: 'Bob' }
207
+ ])
208
+
209
+ ForEach(users, (user) =>
210
+ Div(user.name),
211
+ (user) => user.id // Key for efficient updates
212
+ )
213
+ ```
214
+
188
215
  ## Documentation
189
216
 
190
217
  - **[Getting Started](docs/getting-started.md)** - Installation and first steps
@@ -198,10 +225,17 @@ When(condition)
198
225
  - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
199
226
  - **[NDElement](docs/native-document-element.md)** - Native Document Element
200
227
  - **[Extending NDElement](docs/extending-native-document-element.md)** - Custom Methods Guide
228
+ - **[Advanced Components](docs/advanced-components.md)** - Template caching and singleton views
201
229
  - **[Args Validation](docs/validation.md)** - Function Argument Validation
202
230
  - **[Memory Management](docs/memory-management.md)** - Memory management
203
231
  - **[Anchor](docs/anchor.md)** - Anchor
204
232
 
233
+ ### Utilities
234
+
235
+ - **[Cache](docs/utils/cache.md)** - Lazy initialization and singleton patterns
236
+ - **[NativeFetch](docs/utils/native-fetch.md)** - HTTP client with interceptors
237
+ - **[Filters](docs/utils/filters.md)** - Data filtering helpers
238
+
205
239
 
206
240
  ## Key Features Deep Dive
207
241
 
@@ -213,6 +247,8 @@ When(condition)
213
247
 
214
248
  ### Developer Experience
215
249
  ```javascript
250
+ import { ArgTypes } from 'native-document'
251
+
216
252
  // Built-in debugging
217
253
  Observable.debug.enable()
218
254
 
@@ -221,12 +257,15 @@ const createUser = (function (name, age) {
221
257
  // Auto-validates argument types
222
258
  }).args(ArgTypes.string('name'), ArgTypes.number('age'))
223
259
 
224
- // Error boundaries
225
- const AppWithBoundayError = App.errorBoundary(() => {
226
- return Div('Error in the Create User component');
227
- })
228
260
 
229
- document.body.appendChild(AppWithBoundayError());
261
+ const SafeApp = App.errorBoundary((error, { caller, args }) => {
262
+ return Div({ class: 'error' }, [
263
+ 'An error occurred: ',
264
+ error.message
265
+ ])
266
+ });
267
+
268
+ document.body.appendChild(SafeApp());
230
269
  ```
231
270
 
232
271
  ## Contributing
@@ -1,22 +1,45 @@
1
1
  import {$} from "../../../index";
2
2
 
3
+ /**
4
+ * Mixin for managing a collection of items with manipulation methods
5
+ * @class
6
+ */
3
7
  export default function HasItems() {}
4
8
 
9
+ /**
10
+ * Sets a dynamic observable array to store items
11
+ * @param {ObservableArray?} [observableArray=null] - Observable array to use, or creates a new one if null
12
+ * @returns {HasItems}
13
+ */
5
14
  HasItems.prototype.dynamic = function(observableArray = null) {
6
15
  this.$description.items = observableArray || $.array([]);
7
16
  return this;
8
17
  };
9
18
 
19
+ /**
20
+ * Replaces all existing items with a new array of items
21
+ * @param {Array} items - Array of new items
22
+ * @returns {HasItems}
23
+ */
10
24
  HasItems.prototype.items = function(items) {
11
25
  this.$description.items.splice(0, this.$description.items.length, ...items);
12
26
  return this;
13
27
  };
14
28
 
29
+ /**
30
+ * Adds an item to the collection
31
+ * @param {*} item - The item to add
32
+ * @returns {HasItems}
33
+ */
15
34
  HasItems.prototype.item = function(item) {
16
35
  this.$description.items.push(item);
17
36
  return this;
18
37
  };
19
38
 
39
+ /**
40
+ * Clears all items from the collection
41
+ * @returns {HasItems}
42
+ */
20
43
  HasItems.prototype.clear = function() {
21
44
  const items = this.$description.items;
22
45
  if(Array.isArray(items)) {
@@ -27,11 +50,29 @@ HasItems.prototype.clear = function() {
27
50
  return this;
28
51
  };
29
52
 
53
+ /**
54
+ * Removes a specific item from the collection
55
+ * @param {*} item - The item to remove
56
+ * @returns {HasItems}
57
+ */
30
58
  HasItems.prototype.removeItem = function(item) {
31
- // TODO: implement this method
59
+ const items = this.$description.items;
60
+ if(Array.isArray(items)) {
61
+ const index = items.indexOf(item);
62
+ if(index > -1) {
63
+ items.splice(index, 1);
64
+ return this;
65
+ }
66
+ }
67
+ items.removeItem(item);
32
68
  return this;
33
69
  };
34
70
 
71
+ /**
72
+ * Sets the render function for items
73
+ * @param {(element: *) => ValidChildren} renderFn - Render function to apply
74
+ * @returns {HasItems}
75
+ */
35
76
  HasItems.prototype.render = function(renderFn) {
36
77
  this.$description.render = renderFn;
37
78
  return this;
@@ -1,4 +1,7 @@
1
-
1
+ /**
2
+ *
3
+ * @class
4
+ */
2
5
  export default function BaseComponent() {
3
6
 
4
7
  }