native-document 1.0.19 → 1.0.21

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/docs/anchor.md CHANGED
@@ -372,4 +372,7 @@ anchor.replaceContent(content2);
372
372
  - **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
373
373
  - **[Routing](routing.md)** - Navigation and URL management
374
374
  - **[State Management](state-management.md)** - Global state patterns
375
+ - **[NDElement](native-document-element.md)** - Native Document Element
376
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
377
+ - **[Args Validation](validation.md)** - Function Argument Validation
375
378
  - **[Memory Management](memory-management.md)** - Memory management
@@ -625,5 +625,8 @@ Now that you understand conditional rendering, explore these related topics:
625
625
  - **[Routing](routing.md)** - Navigation and URL management
626
626
  - **[State Management](state-management.md)** - Global state patterns
627
627
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
628
+ - **[NDElement](native-document-element.md)** - Native Document Element
629
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
630
+ - **[Args Validation](validation.md)** - Function Argument Validation
628
631
  - **[Memory Management](memory-management.md)** - Memory management
629
632
  - **[Anchor](anchor.md)** - Anchor
@@ -510,5 +510,8 @@ Now that you understand NativeDocument's core concepts, explore these advanced t
510
510
  - **[Routing](routing.md)** - Navigation and URL management
511
511
  - **[State Management](state-management.md)** - Global state patterns
512
512
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
513
+ - **[NDElement](native-document-element.md)** - Native Document Element
514
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
515
+ - **[Args Validation](validation.md)** - Function Argument Validation
513
516
  - **[Memory Management](memory-management.md)** - Memory management
514
517
  - **[Anchor](anchor.md)** - Anchor
package/docs/elements.md CHANGED
@@ -380,5 +380,8 @@ Now that you understand NativeDocument's elements, explore these advanced topics
380
380
  - **[Routing](routing.md)** - Navigation and URL management
381
381
  - **[State Management](state-management.md)** - Global state patterns
382
382
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
383
+ - **[NDElement](native-document-element.md)** - Native Document Element
384
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
385
+ - **[Args Validation](validation.md)** - Function Argument Validation
383
386
  - **[Memory Management](memory-management.md)** - Memory management
384
387
  - **[Anchor](anchor.md)** - Anchor
@@ -0,0 +1,268 @@
1
+ # Extending NDElement - Custom Methods Guide
2
+
3
+ NDElement is designed to be extensible, allowing developers to add custom methods that make their code more readable and maintainable. This guide shows how to create custom NDElement extensions for common patterns.
4
+
5
+ ## Why Extend NDElement?
6
+
7
+ Extending NDElement allows you to:
8
+ - **Encapsulate common patterns** into reusable methods
9
+ - **Improve code readability** with domain-specific method names
10
+ - **Reduce boilerplate** by abstracting complex event handling
11
+ - **Create a consistent API** across your application
12
+
13
+ ## Basic Extension Pattern
14
+
15
+ The simplest way to extend NDElement is by adding methods to its prototype:
16
+
17
+ ```javascript
18
+ // Basic extension
19
+ NDElement.prototype.customMethod = function(/* parameters */) {
20
+ // Your logic here
21
+ return this; // Return 'this' for method chaining
22
+ };
23
+ ```
24
+
25
+ ## Common Extension Examples
26
+
27
+ ### 1. Keyboard Event Shortcuts
28
+
29
+ Instead of writing complex keyboard event handlers, create semantic shortcuts:
30
+
31
+ ```javascript
32
+ // Enter key handler
33
+ NDElement.prototype.onEnter = function(callback) {
34
+ this.$element.addEventListener('keyup', e => {
35
+ if (e.key === 'Enter') {
36
+ callback(e);
37
+ }
38
+ });
39
+ return this;
40
+ };
41
+
42
+ // Escape key handler
43
+ NDElement.prototype.onEscape = function(callback) {
44
+ this.$element.addEventListener('keyup', e => {
45
+ if (e.key === 'Escape') {
46
+ callback(e);
47
+ }
48
+ });
49
+ return this;
50
+ };
51
+
52
+ // Arrow keys handler
53
+ NDElement.prototype.onArrowKey = function(callback) {
54
+ this.$element.addEventListener('keydown', e => {
55
+ if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
56
+ callback(e, e.key);
57
+ }
58
+ });
59
+ return this;
60
+ };
61
+
62
+ // Usage
63
+ Input({ type: 'text' })
64
+ .nd.onEnter(e => console.log('Form submitted'))
65
+ .onEscape(e => e.target.blur())
66
+ .onArrowKey((e, direction) => console.log('Arrow pressed:', direction));
67
+ ```
68
+
69
+ ### 2. Form Validation Extensions
70
+
71
+ Create semantic validation methods:
72
+
73
+ ```javascript
74
+ // Required field validation
75
+ NDElement.prototype.required = function(message = 'This field is required') {
76
+ this.$element.addEventListener('blur', e => {
77
+ const value = e.target.value.trim();
78
+ if (!value) {
79
+ this.showError(message);
80
+ } else {
81
+ this.clearError();
82
+ }
83
+ });
84
+ return this;
85
+ };
86
+
87
+ // Email validation
88
+ NDElement.prototype.email = function(message = 'Please enter a valid email') {
89
+ this.$element.addEventListener('blur', e => {
90
+ const email = e.target.value.trim();
91
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
92
+ if (email && !emailRegex.test(email)) {
93
+ this.showError(message);
94
+ } else {
95
+ this.clearError();
96
+ }
97
+ });
98
+ return this;
99
+ };
100
+
101
+ // Min length validation
102
+ NDElement.prototype.minLength = function(length, message) {
103
+ message = message || `Minimum ${length} characters required`;
104
+ this.$element.addEventListener('input', e => {
105
+ if (e.target.value.length < length && e.target.value.length > 0) {
106
+ this.showError(message);
107
+ } else {
108
+ this.clearError();
109
+ }
110
+ });
111
+ return this;
112
+ };
113
+
114
+ // Error display helpers
115
+ NDElement.prototype.showError = function(message) {
116
+ this.clearError();
117
+ const errorElement = Span({
118
+ class: 'error-message',
119
+ style: 'color: red; font-size: 0.8rem'
120
+ }, message);
121
+
122
+ this.$element.parentNode.appendChild(errorElement);
123
+ this.$element.classList.add('error');
124
+ return this;
125
+ };
126
+
127
+ NDElement.prototype.clearError = function() {
128
+ const parent = this.$element.parentNode;
129
+ const existingError = parent.querySelector('.error-message');
130
+ if (existingError) {
131
+ existingError.remove();
132
+ }
133
+ this.$element.classList.remove('error');
134
+ return this;
135
+ };
136
+
137
+ // Usage
138
+ Input({ type: 'email', placeholder: 'Email' })
139
+ .nd.required()
140
+ .email();
141
+
142
+ Input({ type: 'password', placeholder: 'Password' })
143
+ .nd.required()
144
+ .minLength(8, 'Password must be at least 8 characters');
145
+ ```
146
+
147
+ ### 3. Animation Extensions
148
+
149
+ Create smooth animation helpers:
150
+
151
+ ```javascript
152
+ // Fade in animation
153
+ NDElement.prototype.fadeIn = function(duration = 300) {
154
+ this.$element.style.opacity = '0';
155
+ this.$element.style.transition = `opacity ${duration}ms ease-in-out`;
156
+
157
+ requestAnimationFrame(() => {
158
+ this.$element.style.opacity = '1';
159
+ });
160
+
161
+ return this;
162
+ };
163
+
164
+ // Fade out animation
165
+ NDElement.prototype.fadeOut = function(duration = 300, callback) {
166
+ this.$element.style.transition = `opacity ${duration}ms ease-in-out`;
167
+ this.$element.style.opacity = '0';
168
+
169
+ setTimeout(() => {
170
+ if (callback) callback();
171
+ }, duration);
172
+
173
+ return this;
174
+ };
175
+
176
+ // Slide down animation
177
+ NDElement.prototype.slideDown = function(duration = 300) {
178
+ const element = this.$element;
179
+ element.style.maxHeight = '0';
180
+ element.style.overflow = 'hidden';
181
+ element.style.transition = `max-height ${duration}ms ease-in-out`;
182
+
183
+ requestAnimationFrame(() => {
184
+ element.style.maxHeight = element.scrollHeight + 'px';
185
+ });
186
+
187
+ return this;
188
+ };
189
+
190
+ // Usage
191
+ Div("Animated content")
192
+ .nd.onClick(function() {
193
+ this.nd.fadeOut(300, () => this.remove());
194
+ });
195
+ ```
196
+
197
+ ## Best Practices
198
+
199
+ ### 1. Always Return `this`
200
+ Enable method chaining by returning the NDElement instance:
201
+
202
+ ```javascript
203
+ NDElement.prototype.myMethod = function() {
204
+ // Your logic here
205
+ return this; // Enable chaining
206
+ };
207
+ ```
208
+
209
+ ### 2. Use Descriptive Method Names
210
+ Choose names that clearly describe what the method does:
211
+
212
+ ```javascript
213
+ // Good
214
+ NDElement.prototype.onEnter = function(callback) { /* ... */ };
215
+ NDElement.prototype.fadeIn = function(duration) { /* ... */ };
216
+
217
+ // Avoid
218
+ NDElement.prototype.ke = function(callback) { /* ... */ }; // Unclear
219
+ NDElement.prototype.doStuff = function() { /* ... */ }; // Too vague
220
+ ```
221
+
222
+ ### 3. Handle Edge Cases
223
+ Always consider edge cases and provide sensible defaults:
224
+
225
+ ```javascript
226
+ NDElement.prototype.fadeIn = function(duration = 300) {
227
+ // Ensure duration is valid
228
+ duration = Math.max(0, parseInt(duration) || 300);
229
+
230
+ // Check if element exists
231
+ if (!this.$element) return this;
232
+
233
+ // Your animation logic
234
+ return this;
235
+ };
236
+ ```
237
+
238
+ ### 4. Document Your Extensions
239
+
240
+ Always document your custom methods:
241
+
242
+ ```javascript
243
+ /**
244
+ * Handles Enter key press events
245
+ * @param {Function} callback - Function to call when Enter is pressed
246
+ * @returns {NDElement} Returns this for method chaining
247
+ * @example
248
+ * Input().nd.onEnter(e => console.log('Enter pressed'));
249
+ */
250
+ NDElement.prototype.onEnter = function(callback) {
251
+ this.$element.addEventListener('keyup', e => {
252
+ if (e.key === 'Enter') {
253
+ callback(e);
254
+ }
255
+ });
256
+ return this;
257
+ };
258
+ ```
259
+
260
+ By extending NDElement thoughtfully, you can create a powerful, domain-specific API that makes your code more readable, maintainable, and enjoyable to work with.
261
+
262
+ ## Next Steps
263
+
264
+ Explore these related topics to build complete applications:
265
+
266
+ - **[Args Validation](validation.md)** - Function Argument Validation
267
+ - **[Memory Management](memory-management.md)** - Memory management
268
+ - **[Anchor](anchor.md)** - Anchor
@@ -358,6 +358,9 @@ Now that you've built your first NativeDocument applications, explore these topi
358
358
  - **[Routing](routing.md)** - Navigation and URL management
359
359
  - **[State Management](state-management.md)** - Global state patterns
360
360
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
361
+ - **[NDElement](native-document-element.md)** - Native Document Element
362
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
363
+ - **[Args Validation](validation.md)** - Function Argument Validation
361
364
  - **[Memory Management](memory-management.md)** - Memory management
362
365
  - **[Anchor](anchor.md)** - Anchor
363
366
 
@@ -98,6 +98,9 @@ document.body.appendChild(reusableComponent);
98
98
 
99
99
  Now that you understand lifecycle events, explore these related topics:
100
100
 
101
+ - **[NDElement](native-document-element.md)** - Native Document Element
102
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
103
+ - **[Args Validation](validation.md)** - Function Argument Validation
101
104
  - **[Memory Management](memory-management.md)** - Memory management
102
105
  - **[Anchor](anchor.md)** - Anchor
103
106
 
@@ -603,5 +603,11 @@ const DebugList = ForEachArray(items, (item, index) => {
603
603
  Now that you understand list rendering, explore these related topics:
604
604
 
605
605
  - **[Conditional Rendering](conditional-rendering.md)** - Show/hide content dynamically
606
+ - **[Routing](routing.md)** - Navigation and URL management
607
+ - **[State Management](state-management.md)** - Global state patterns
608
+ - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
609
+ - **[NDElement](native-document-element.md)** - Native Document Element
610
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
611
+ - **[Args Validation](validation.md)** - Function Argument Validation
606
612
  - **[State Management](state-management.md)** - Managing application state
607
613
  - **[Memory Management](memory-management.md)** - Understanding cleanup and memory
@@ -0,0 +1,279 @@
1
+ # NDElement
2
+
3
+ `NDElement` is a wrapper class that enhances native HTML elements with utility methods and simplified event handlers. It enables fluent DOM manipulation while preserving access to the underlying HTML element.
4
+
5
+ ## Accessing NDElement
6
+
7
+ Every HTML element created with NativeDocument automatically has an `nd` property that returns an `NDElement` instance:
8
+
9
+ ```javascript
10
+ const element = Div("Hello World");
11
+ const ndElement = element.nd; // NDElement instance
12
+
13
+ // Or directly with method chaining
14
+ Div("Hello").nd.onClick(() => console.log("Clicked!"));
15
+ ```
16
+
17
+ ## Constructor
18
+
19
+ ```javascript
20
+ new NDElement(element)
21
+ ```
22
+
23
+ **Parameters:**
24
+ - `element`: The HTML element to wrap
25
+
26
+ ## Properties
27
+
28
+ ### `$element`
29
+ The encapsulated native HTML element.
30
+
31
+ ```javascript
32
+ const div = Div("Content");
33
+ const htmlElement = div.nd.$element; // Native HTMLDivElement
34
+ ```
35
+
36
+ ### `$observer`
37
+ Lifecycle observer (used internally for DOM monitoring).
38
+
39
+ ## Event Handling Methods
40
+
41
+ NDElement automatically generates methods for all standard DOM events with multiple variants:
42
+
43
+ ### Basic Events
44
+
45
+ ```javascript
46
+ // Standard event
47
+ element.nd.onClick(callback)
48
+ element.nd.onMouseOver(callback)
49
+ element.nd.onKeyDown(callback)
50
+
51
+ // Examples
52
+ Button("Click me").nd.onClick(e => console.log("Button clicked!"));
53
+ Input().nd.onInput(e => console.log("Input changed:", e.target.value));
54
+ ```
55
+
56
+ ### Prevention Variants
57
+
58
+ ```javascript
59
+ // Prevents default behavior
60
+ element.nd.onPreventClick(callback) // preventDefault()
61
+ element.nd.onPreventSubmit(callback)
62
+
63
+ // Example
64
+ Link({ href: "/page" }).nd.onPreventClick(e => {
65
+ // Link won't navigate, custom behavior
66
+ router.push("/page");
67
+ });
68
+ ```
69
+
70
+ ### Propagation Stop Variants
71
+
72
+ ```javascript
73
+ // Stops event propagation
74
+ element.nd.onStopClick(callback) // stopPropagation()
75
+ element.nd.onStopKeyDown(callback)
76
+
77
+ // Example
78
+ Div([
79
+ Button("Child").nd.onStopClick(e => {
80
+ console.log("Child clicked - won't bubble up");
81
+ })
82
+ ]).nd.onClick(() => console.log("This won't be called"));
83
+ ```
84
+
85
+ ### Combined Variants
86
+
87
+ ```javascript
88
+ // Combines preventDefault() and stopPropagation()
89
+ element.nd.onPreventStopSubmit(callback)
90
+ element.nd.onPreventStopClick(callback)
91
+
92
+ // Example
93
+ Form().nd.onPreventStopSubmit(e => {
94
+ // Prevents submission AND stops propagation
95
+ handleFormSubmit(e);
96
+ });
97
+ ```
98
+
99
+ ### Supported Events List
100
+
101
+ All standard DOM events are supported with the 4 variants:
102
+
103
+ **Mouse:** Click, DblClick, MouseDown, MouseEnter, MouseLeave, MouseMove, MouseOut, MouseOver, MouseUp, Wheel
104
+
105
+ **Keyboard:** KeyDown, KeyPress, KeyUp
106
+
107
+ **Form:** Blur, Change, Focus, Input, Invalid, Reset, Search, Select, Submit
108
+
109
+ **Drag & Drop:** Drag, DragEnd, DragEnter, DragLeave, DragOver, DragStart, Drop
110
+
111
+ **Media:** Abort, CanPlay, CanPlayThrough, DurationChange, Emptied, Ended, LoadedData, LoadedMetadata, LoadStart, Pause, Play, Playing, Progress, RateChange, Seeked, Seeking, Stalled, Suspend, TimeUpdate, VolumeChange, Waiting
112
+
113
+ **Window:** AfterPrint, BeforePrint, BeforeUnload, Error, HashChange, Load, Offline, Online, PageHide, PageShow, Resize, Scroll, Unload
114
+
115
+ ## Utility Methods
116
+
117
+ ### `ref(target, name)`
118
+ Assigns the HTML element to a property of a target object.
119
+
120
+ ```javascript
121
+ const refs = {};
122
+ Div("Content").nd.ref(refs, 'contentDiv');
123
+ console.log(refs.contentDiv); // HTMLDivElement
124
+ ```
125
+
126
+ ### `htmlElement()` / `node()`
127
+ Returns the native HTML element (alias for `$element`).
128
+
129
+ ```javascript
130
+ const div = Div("Hello");
131
+ const htmlElement = div.nd.htmlElement(); // HTMLDivElement
132
+ const node = div.nd.node(); // Same thing, alias
133
+ ```
134
+
135
+ ### `remove()`
136
+ Removes the element and cleans up its internal references.
137
+
138
+ ```javascript
139
+ const element = Div("To be removed");
140
+ element.nd.remove(); // Element removed and cleaned
141
+ ```
142
+
143
+ ### `unmountChildren()`
144
+ Unmounts all child elements and cleans up their references.
145
+
146
+ ```javascript
147
+ const container = Div([
148
+ Div("Child 1"),
149
+ Div("Child 2")
150
+ ]);
151
+ container.nd.unmountChildren(); // Children cleaned up
152
+ ```
153
+
154
+ ## Lifecycle Management
155
+
156
+ ### `lifecycle(states)`
157
+ Configures lifecycle callbacks.
158
+
159
+ ```javascript
160
+ element.nd.lifecycle({
161
+ mounted: (element) => console.log("Element added to DOM"),
162
+ unmounted: (element) => console.log("Element removed from DOM")
163
+ });
164
+ ```
165
+
166
+ ### `mounted(callback)`
167
+ Shortcut to define only the mount callback.
168
+
169
+ ```javascript
170
+ Div("Content").nd.mounted(element => {
171
+ console.log("Element is now in the DOM!");
172
+ });
173
+ ```
174
+
175
+ ## Practical Examples
176
+
177
+ ### Custom Event Handler
178
+
179
+ ```javascript
180
+ // Extending NDElement with a custom handler
181
+ NDElement.prototype.onEnter = function(callback) {
182
+ this.$element.addEventListener('keyup', e => {
183
+ if (e.key === 'Enter') {
184
+ callback(e);
185
+ }
186
+ });
187
+ return this;
188
+ };
189
+
190
+ // Usage
191
+ Input({ type: 'text' })
192
+ .nd.onEnter(e => console.log("Enter pressed!"));
193
+ ```
194
+
195
+ ### Fluent Chaining
196
+
197
+ ```javascript
198
+ const interactiveButton = Button("Interactive")
199
+ .nd.onClick(e => console.log("Clicked"))
200
+ .nd.onMouseEnter(e => e.target.style.background = "blue")
201
+ .nd.onMouseLeave(e => e.target.style.background = "")
202
+ .nd.mounted(el => console.log("Button mounted"));
203
+
204
+ // OR
205
+
206
+ const interactiveButton = Button("Interactive")
207
+ .nd.onClick(e => console.log("Clicked"))
208
+ .onMouseEnter(e => e.target.style.background = "blue")
209
+ .onMouseLeave(e => e.target.style.background = "")
210
+ .mounted(el => console.log("Button mounted"));
211
+
212
+ ```
213
+
214
+ ### Form with Event Handling
215
+
216
+ ```javascript
217
+ const todoForm = Form([
218
+ Input({ type: 'text', value: newTodo })
219
+ .nd.onEnter(addTodo),
220
+
221
+ Button('Add', { type: 'submit' })
222
+ ]).nd.onPreventSubmit(addTodo);
223
+ ```
224
+
225
+ ### Reference Management
226
+
227
+ ```javascript
228
+ const components = {};
229
+
230
+ const app = Div([
231
+ Input().nd.ref(components, 'input'),
232
+ Button('Focus Input').nd.onClick(() => {
233
+ components.input.focus();
234
+ })
235
+ ]);
236
+ ```
237
+
238
+ ## Integration with Observables
239
+
240
+ NDElement works seamlessly with NativeDocument's Observable system:
241
+
242
+ ```javascript
243
+ const isVisible = Observable(false);
244
+ const message = Observable("Hello");
245
+
246
+ Div([
247
+ Button("Toggle").nd.onClick(() => isVisible.set(!isVisible.val())),
248
+ ShowIf(isVisible, () =>
249
+ P(message).nd.onClick(() =>
250
+ message.set("Clicked!")
251
+ )
252
+ )
253
+ ]);
254
+ ```
255
+
256
+ ## Best Practices
257
+
258
+ 1. **Fluent chaining**: Use method chaining for concise syntax
259
+ 2. **Cleanup**: Call `remove()` to clean up dynamic elements
260
+ 3. **Extensions**: Add your own methods to the prototype for specific needs
261
+ 4. **Lifecycle**: Use `mounted`/`unmounted` for initialization/cleanup
262
+ 5. **References**: Use `ref()` for direct element access when needed
263
+
264
+ ## Limitations
265
+
266
+ - Event handlers are not automatically removed (manual management required if needed)
267
+ - Access to native HTML element is still necessary for advanced APIs
268
+
269
+ NDElement thus provides a practical abstraction layer while preserving the power and performance of native DOM.
270
+
271
+
272
+ ## Next Steps
273
+
274
+ Explore these related topics to build complete applications:
275
+
276
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
277
+ - **[Args Validation](validation.md)** - Function Argument Validation
278
+ - **[Memory Management](memory-management.md)** - Memory management
279
+ - **[Anchor](anchor.md)** - Anchor
@@ -542,5 +542,8 @@ Now that you understand NativeDocument's observable, explore these advanced topi
542
542
  - **[Routing](routing.md)** - Navigation and URL management
543
543
  - **[State Management](state-management.md)** - Global state patterns
544
544
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
545
+ - **[NDElement](native-document-element.md)** - Native Document Element
546
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
547
+ - **[Args Validation](validation.md)** - Function Argument Validation
545
548
  - **[Memory Management](memory-management.md)** - Memory management
546
549
  - **[Anchor](anchor.md)** - Anchor
package/docs/routing.md CHANGED
@@ -813,5 +813,8 @@ Explore these related topics to build complete applications:
813
813
 
814
814
  - **[State Management](state-management.md)** - Global state patterns
815
815
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
816
+ - **[NDElement](native-document-element.md)** - Native Document Element
817
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
818
+ - **[Args Validation](validation.md)** - Function Argument Validation
816
819
  - **[Memory Management](memory-management.md)** - Memory management
817
820
  - **[Anchor](anchor.md)** - Anchor
@@ -419,5 +419,8 @@ const createAppState = () => {
419
419
  Now that you understand state management, explore these related topics:
420
420
 
421
421
  - **[Lifecycle Events](lifecycle-events.md)** - Lifecycle events
422
+ - **[NDElement](native-document-element.md)** - Native Document Element
423
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
424
+ - **[Args Validation](validation.md)** - Function Argument Validation
422
425
  - **[Memory Management](memory-management.md)** - Memory management
423
426
  - **[Anchor](anchor.md)** - Anchor
@@ -187,5 +187,7 @@ registerUser.args(
187
187
 
188
188
  ## Next Steps
189
189
 
190
- - **[Memory Management](memory-management.md)** - Debugging memory issues with validation
191
- - **[Lifecycle Events](lifecycle-events.md)** - Validate lifecycle callback arguments
190
+ - **[Lifecycle Events](lifecycle-events.md)** - Validate lifecycle callback arguments
191
+ - **[NDElement](native-document-element.md)** - Native Document Element
192
+ - **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
193
+ - **[Memory Management](memory-management.md)** - Debugging memory issues with validation
@@ -0,0 +1,22 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+
4
+ export default [
5
+ { ignores: ['dist'] },
6
+ {
7
+ files: ['**/*.{js}'],
8
+ languageOptions: {
9
+ ecmaVersion: 2020,
10
+ globals: globals.browser,
11
+ parserOptions: {
12
+ ecmaVersion: 'latest',
13
+ sourceType: 'module',
14
+ },
15
+ },
16
+ plugins: {
17
+ },
18
+ rules: {
19
+ ...js.configs.recommended.rules,
20
+ },
21
+ },
22
+ ]
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
- "build": "rollup --config rollup.config.js --watch"
7
+ "build": "rollup --config rollup.config.js --watch",
8
+ "lint": "eslint ."
8
9
  },
9
10
  "keywords": [],
10
11
  "author": "",
@@ -12,6 +13,7 @@
12
13
  "description": "",
13
14
  "devDependencies": {
14
15
  "@rollup/plugin-replace": "^6.0.2",
15
- "@rollup/plugin-terser": "^0.4.4"
16
+ "@rollup/plugin-terser": "^0.4.4",
17
+ "eslint": "^9.33.0"
16
18
  }
17
19
  }
package/readme.md CHANGED
@@ -60,7 +60,7 @@ document.body.appendChild(App);
60
60
  npx degit afrocodeur/native-document-vite my-app
61
61
  cd my-app
62
62
  npm install
63
- npm run dev
63
+ npm run start
64
64
  ```
65
65
 
66
66
  ### Option 3: NPM/Yarn
@@ -195,29 +195,12 @@ When(condition)
195
195
  - **[Routing](docs/routing.md)** - Navigation and URL management
196
196
  - **[State Management](docs/state-management.md)** - Global state patterns
197
197
  - **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
198
+ - **[NDElement](docs/native-document-element.md)** - Native Document Element
199
+ - **[Extending NDElement](docs/extending-native-document-element.md)** - Custom Methods Guide
200
+ - **[Args Validation](docs/validation.md)** - Function Argument Validation
198
201
  - **[Memory Management](docs/memory-management.md)** - Memory management
199
202
  - **[Anchor](docs/anchor.md)** - Anchor
200
203
 
201
- ## Examples
202
-
203
- ### Todo App
204
- ```bash
205
- # Complete todo application with local storage
206
- git clone https://github.com/afrocodeur/native-document-examples
207
- cd examples/todo-app
208
- ```
209
-
210
- ### SPA Router
211
- ```bash
212
- # Single Page Application with routing
213
- cd examples/routing-spa
214
- ```
215
-
216
- ### Reusable Components
217
- ```bash
218
- # Component library patterns
219
- cd examples/components
220
- ```
221
204
 
222
205
  ## Key Features Deep Dive
223
206
 
@@ -5,12 +5,13 @@
5
5
  * @class ObservableChecker
6
6
  */
7
7
  export default function ObservableChecker($observable, $checker) {
8
- this.__$isObservableChecker = true;
9
8
  this.observable = $observable;
10
9
  this.checker = $checker;
11
10
  this.unSubscriptions = [];
12
11
  }
13
12
 
13
+ ObservableChecker.prototype.__$isObservableChecker = true;
14
+
14
15
  ObservableChecker.prototype.subscribe = function(callback) {
15
16
  const unSubscribe = this.observable.subscribe((value) => {
16
17
  callback && callback(this.checker(value));
@@ -9,14 +9,6 @@ import ObservableChecker from "./ObservableChecker";
9
9
  * @class ObservableItem
10
10
  */
11
11
  export default function ObservableItem(value) {
12
- if (value === undefined) {
13
- throw new NativeDocumentError('ObservableItem requires an initial value');
14
- }
15
- if(value instanceof ObservableItem) {
16
- throw new NativeDocumentError('ObservableItem cannot be an Observable');
17
- }
18
-
19
- this.__$isObservable = true;
20
12
  this.$previousValue = value;
21
13
  this.$currentValue = value;
22
14
  this.$isCleanedUp = false;
@@ -24,7 +16,7 @@ export default function ObservableItem(value) {
24
16
  this.$listeners = null;
25
17
  this.$watchers = null;
26
18
 
27
- this.$memoryId = MemoryManager.register(this);
19
+ this.$memoryId = null;
28
20
  }
29
21
 
30
22
  Object.defineProperty(ObservableItem.prototype, '$value', {
@@ -37,6 +29,9 @@ Object.defineProperty(ObservableItem.prototype, '$value', {
37
29
  configurable: true,
38
30
  });
39
31
 
32
+ ObservableItem.prototype.__$isObservable = true;
33
+
34
+ const noneTrigger = function() {};
40
35
  ObservableItem.prototype.triggerListeners = function(operations) {
41
36
  const $listeners = this.$listeners;
42
37
  const $previousValue = this.$previousValue;
@@ -60,31 +55,38 @@ ObservableItem.prototype.triggerWatchers = function() {
60
55
  const $currentValue = this.$currentValue;
61
56
 
62
57
  if($watchers.has($currentValue)) {
63
- const watchValueList = $watchers.get($currentValue);
64
- watchValueList.forEach(itemValue => {
65
- if(itemValue.ifTrue.called) {
66
- return;
67
- }
68
- itemValue.ifTrue.callback();
69
- itemValue.else.called = false;
70
- })
58
+ $watchers.get($currentValue).forEach(callback => {
59
+ callback(true);
60
+ });
71
61
  }
72
62
  if($watchers.has($previousValue)) {
73
- const watchValueList = $watchers.get($previousValue);
74
- watchValueList.forEach(itemValue => {
75
- if(itemValue.else.called) {
76
- return;
77
- }
78
- itemValue.else.callback();
79
- itemValue.ifTrue.called = false;
63
+ $watchers.get($previousValue).forEach(callback => {
64
+ callback(false);
80
65
  });
81
66
  }
82
67
  };
83
68
 
84
- ObservableItem.prototype.trigger = function(operations) {
69
+ ObservableItem.prototype.triggerAll = function(operations) {
85
70
  this.triggerListeners(operations);
86
71
  this.triggerWatchers();
72
+ };
73
+
74
+ ObservableItem.prototype.assocTrigger = function() {
75
+ if(this.$watchers?.size && this.$listeners?.length) {
76
+ this.trigger = this.triggerAll;
77
+ return;
78
+ }
79
+ if(this.$listeners?.length) {
80
+ this.trigger = this.triggerListeners;
81
+ return;
82
+ }
83
+ if(this.$watchers?.size) {
84
+ this.trigger = this.triggerWatchers;
85
+ return;
86
+ }
87
+ this.trigger = noneTrigger;
87
88
  }
89
+ ObservableItem.prototype.trigger = noneTrigger;
88
90
 
89
91
  /**
90
92
  * @param {*} data
@@ -109,16 +111,13 @@ ObservableItem.prototype.disconnectAll = function() {
109
111
  this.$currentValue = null;
110
112
  if(this.$watchers) {
111
113
  for (const [_, watchValueList] of this.$watchers) {
112
- for (const itemValue of watchValueList) {
113
- itemValue.ifTrue.callback = null;
114
- itemValue.else.callback = null;
115
- }
116
- watchValueList.clear();
114
+ watchValueList.splice(0);
117
115
  }
118
116
  }
119
117
  this.$watchers?.clear();
120
118
  this.$listeners = null;
121
119
  this.$watchers = null;
120
+ this.trigger = noneTrigger;
122
121
  }
123
122
  ObservableItem.prototype.cleanup = function() {
124
123
  MemoryManager.unregister(this.$memoryId);
@@ -143,30 +142,32 @@ ObservableItem.prototype.subscribe = function(callback) {
143
142
  }
144
143
 
145
144
  this.$listeners.push(callback);
146
- return () => this.unsubscribe(callback);
145
+ this.assocTrigger();
146
+ return () => {
147
+ this.unsubscribe(callback);
148
+ this.assocTrigger();
149
+ };
147
150
  };
148
151
 
149
- ObservableItem.prototype.on = function(value, callback, elseCallback) {
152
+ ObservableItem.prototype.on = function(value, callback) {
150
153
  this.$watchers = this.$watchers ?? new Map();
151
154
 
152
155
  let watchValueList = this.$watchers.get(value);
153
156
  if(!watchValueList) {
154
- watchValueList = new Set();
157
+ watchValueList = [];
155
158
  this.$watchers.set(value, watchValueList);
156
159
  }
157
160
 
158
- let itemValue = {
159
- ifTrue: { callback, called: false },
160
- else: { callback: elseCallback, called: false }
161
- };
162
- watchValueList.add(itemValue);
161
+ watchValueList.push(callback);
162
+ this.assocTrigger();
163
163
  return () => {
164
- watchValueList?.delete(itemValue);
164
+ const index = watchValueList.indexOf(callback);
165
+ watchValueList?.splice(index, 1);
165
166
  if(watchValueList.size === 0) {
166
167
  this.$watchers?.delete(value);
168
+ watchValueList = null;
167
169
  }
168
- watchValueList = null;
169
- itemValue = null;
170
+ this.assocTrigger();
170
171
  };
171
172
  };
172
173
 
@@ -179,6 +180,7 @@ ObservableItem.prototype.unsubscribe = function(callback) {
179
180
  if (index > -1) {
180
181
  this.$listeners.splice(index, 1);
181
182
  }
183
+ this.assocTrigger();
182
184
  };
183
185
 
184
186
  /**
@@ -191,6 +193,9 @@ ObservableItem.prototype.check = function(callback) {
191
193
  };
192
194
  ObservableItem.prototype.get = ObservableItem.prototype.check;
193
195
 
194
- ObservableItem.prototype.toString = function() {
196
+ ObservableItem.prototype.toString = function() {
197
+ if(!this.$memoryId) {
198
+ MemoryManager.register(this);
199
+ }
195
200
  return '{{#ObItem::(' +this.$memoryId+ ')}}';
196
201
  }
@@ -24,20 +24,20 @@ Observable.array = function(target) {
24
24
  });
25
25
 
26
26
  observer.clear = function() {
27
- observer.$value.length = 0;
27
+ observer.val().length = 0;
28
28
  observer.trigger({ action: 'clear' });
29
29
  return true;
30
30
  };
31
31
 
32
32
  observer.merge = function(values) {
33
- observer.$value = [...observer.$value, ...values];
33
+ observer.set([...observer.val(), ...values]);
34
34
  };
35
35
 
36
36
  observer.populateAndRender = function(iteration, callback) {
37
- observer.trigger({ action: 'populate', args: [observer.$value, iteration, callback] });
37
+ observer.trigger({ action: 'populate', args: [observer.val(), iteration, callback] });
38
38
  };
39
39
  observer.remove = function(index) {
40
- const deleted = observer.$value.splice(index, 1);
40
+ const deleted = observer.val().splice(index, 1);
41
41
  if(deleted.length === 0) {
42
42
  return [];
43
43
  }
@@ -46,7 +46,7 @@ Observable.array = function(target) {
46
46
  };
47
47
 
48
48
  observer.swap = function(indexA, indexB) {
49
- const value = observer.$value;
49
+ const value = observer.val();
50
50
  const length = value.length;
51
51
  if(length < indexA || length < indexB) {
52
52
  return false;
@@ -66,7 +66,7 @@ Observable.array = function(target) {
66
66
  };
67
67
 
68
68
  observer.length = function() {
69
- return observer.$value.length;
69
+ return observer.val().length;
70
70
  }
71
71
 
72
72
  const overrideMethods = ['map', 'filter', 'reduce', 'some', 'every', 'find', 'findIndex', 'concat'];
@@ -11,7 +11,6 @@ export function ForEachArray(data, callback, key, configs = {}) {
11
11
  const blockStart = element.startElement();
12
12
 
13
13
  let cache = new Map();
14
- let nodeCacheByElement = new WeakMap();
15
14
  let lastNumberOfItems = 0;
16
15
 
17
16
  const keysCache = new WeakMap();
@@ -26,7 +25,10 @@ export function ForEachArray(data, callback, key, configs = {}) {
26
25
  return keysCache.get(item);
27
26
  }
28
27
  return getKey(item, indexKey, key);
29
- }
28
+ };
29
+ const getItemChild = (item) => {
30
+ return getChildByKey(getItemKey(item));
31
+ };
30
32
 
31
33
  const updateIndexObservers = (items, startFrom = 0) => {
32
34
  if(callback.length < 2) {
@@ -47,13 +49,10 @@ export function ForEachArray(data, callback, key, configs = {}) {
47
49
  if(!cacheItem) {
48
50
  return;
49
51
  }
50
- const child = cacheItem.child?.deref();
52
+ const child = cacheItem.child;
51
53
  cacheItem.indexObserver?.deref()?.cleanup();
52
54
  cacheItem.child = null;
53
55
  cacheItem.indexObserver = null;
54
- nodeCacheByElement.delete(cacheItem.item);
55
- keysCache.delete(cacheItem.item);
56
- cacheItem.item = null;
57
56
  if(removeChild) {
58
57
  child?.remove();
59
58
  cache.delete(cacheItem.keyId);
@@ -77,8 +76,7 @@ export function ForEachArray(data, callback, key, configs = {}) {
77
76
  if(cache.has(keyId)) {
78
77
  const cacheItem = cache.get(keyId);
79
78
  cacheItem.indexObserver?.deref()?.set(indexKey);
80
- cacheItem.isNew = false;
81
- const child = cacheItem.child?.deref();
79
+ const child = cacheItem.child;
82
80
  if(child) {
83
81
  return child;
84
82
  }
@@ -90,27 +88,22 @@ export function ForEachArray(data, callback, key, configs = {}) {
90
88
  let child = ElementCreator.getChild(callback(item, indexObserver));
91
89
  cache.set(keyId, {
92
90
  keyId,
93
- isNew: true,
94
- item,
95
- child: new WeakRef(child),
91
+ child: child,
96
92
  indexObserver: (indexObserver ? new WeakRef(indexObserver) : null)
97
93
  });
98
94
  keysCache.set(item, keyId);
99
- if(Validator.isObject(item)) {
100
- nodeCacheByElement.set(item, child);
101
- }
102
95
  return child;
103
96
  } catch (e) {
104
97
  DebugManager.error('ForEach', `Error creating element for key ${keyId}` , e);
105
98
  throw e;
106
99
  }
107
100
  };
108
- const getChildByKey = function(keyId, fragment) {
101
+ const getChildByKey = function(keyId) {
109
102
  const cacheItem = cache.get(keyId);
110
103
  if(!cacheItem) {
111
104
  return null;
112
105
  }
113
- const child = cacheItem.child?.deref();
106
+ const child = cacheItem.child;
114
107
  if(!child) {
115
108
  removeCacheItem(cacheItem, false);
116
109
  return null;
@@ -123,7 +116,7 @@ export function ForEachArray(data, callback, key, configs = {}) {
123
116
  if(!cacheItem) {
124
117
  return null;
125
118
  }
126
- const child = cacheItem.child?.deref();
119
+ const child = cacheItem.child;
127
120
  if(!child) {
128
121
  return null;
129
122
  }
@@ -157,7 +150,7 @@ export function ForEachArray(data, callback, key, configs = {}) {
157
150
  let child = null;
158
151
  const fragment = document.createDocumentFragment();
159
152
  for(const item of items) {
160
- child = nodeCacheByElement.get(item);
153
+ child = getItemChild(item);
161
154
  if(child) {
162
155
  fragment.appendChild(child);
163
156
  }
@@ -166,11 +159,9 @@ export function ForEachArray(data, callback, key, configs = {}) {
166
159
  element.appendElement(fragment, blockEnd);
167
160
  },
168
161
  removeOne(element, index) {
169
- let child = nodeCacheByElement.get(element);
162
+ let child = getItemChild(element);
170
163
  if(child) {
171
- child.remove();
172
- nodeCacheByElement.delete(element);
173
- removeCacheItemByKey(getItemKey(element, index));
164
+ removeCacheItemByKey(getItemKey(element, index), true);
174
165
  }
175
166
  child = null;
176
167
  },
@@ -246,8 +237,8 @@ export function ForEachArray(data, callback, key, configs = {}) {
246
237
  swap(args, elements) {
247
238
  const parent = blockEnd.parentNode;
248
239
 
249
- let childA = nodeCacheByElement.get(elements[0]);
250
- let childB = nodeCacheByElement.get(elements[1]);
240
+ let childA = getItemChild(elements[0]);
241
+ let childB = getItemChild(elements[1]);
251
242
  if(!childA || !childB) {
252
243
  return;
253
244
  }
@@ -96,7 +96,7 @@ export const ElementCreator = {
96
96
  return child;
97
97
  }
98
98
  if(Validator.isNDElement(child)) {
99
- return child.$element;
99
+ return child.$element ?? child.$build?.() ?? null;
100
100
  }
101
101
  return ElementCreator.createStaticTextNode(null, child);
102
102
  },
@@ -2,10 +2,10 @@ import DocumentObserver from "./DocumentObserver";
2
2
  import { EVENTS } from "../utils/events";
3
3
 
4
4
  export function NDElement(element) {
5
- this.__$isNDElement = true;
6
5
  this.$element = element;
7
6
  this.$observer = null;
8
7
  }
8
+ NDElement.prototype.__$isNDElement = true;
9
9
 
10
10
  for(const event of EVENTS) {
11
11
  const eventName = event.toLowerCase();
@@ -38,18 +38,18 @@ for(const event of EVENTS) {
38
38
  }
39
39
 
40
40
  NDElement.prototype.ref = function(target, name) {
41
- target[name] = element;
41
+ target[name] = this.$element;
42
42
  return this;
43
43
  };
44
44
 
45
45
  NDElement.prototype.unmountChildren = function() {
46
46
  let element = this.$element;
47
47
  for(let i = 0, length = element.children.length; i < length; i++) {
48
- let elementchildren = element.children[i];
49
- if(!elementchildren.$ndProx) {
50
- elementchildren.nd?.remove();
48
+ let elementChildren = element.children[i];
49
+ if(!elementChildren.$ndProx) {
50
+ elementChildren.nd?.remove();
51
51
  }
52
- elementchildren = null;
52
+ elementChildren = null;
53
53
  }
54
54
  element = null;
55
55
  return this;
@@ -77,7 +77,7 @@ NDElement.prototype.mounted = function(callback) {
77
77
  return this.lifecycle({ mounted: callback });
78
78
  };
79
79
 
80
- NDElement.prototype.mounted = function(callback) {
80
+ NDElement.prototype.unmounted = function(callback) {
81
81
  return this.lifecycle({ unmounted: callback });
82
82
  };
83
83