native-document 1.0.18 → 1.0.20

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.
@@ -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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as HtmlElementWrapper } from './src/wrappers/HtmlElementWrapper'
2
2
  export { ElementCreator } from './src/wrappers/ElementCreator'
3
+ export { NDElement } from './src/wrappers/NDElement'
3
4
 
4
5
  import './src/utils/prototypes.js';
5
6
 
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "native-document",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
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": "",
11
12
  "license": "ISC",
12
13
  "description": "",
13
14
  "devDependencies": {
14
- "@rollup/plugin-terser": "^0.4.4"
15
+ "@rollup/plugin-replace": "^6.0.2",
16
+ "@rollup/plugin-terser": "^0.4.4",
17
+ "eslint": "^9.33.0"
15
18
  }
16
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
 
package/rollup.config.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import terser from '@rollup/plugin-terser';
2
+ import replace from '@rollup/plugin-replace';
2
3
 
4
+ const isProduction = process.env.NODE_ENV === 'production';
3
5
  export default [
4
6
  {
5
7
  input: {
@@ -9,8 +11,15 @@ export default [
9
11
  dir: 'dist',
10
12
  entryFileNames: 'native-document.dev.js',
11
13
  format: 'iife',
12
- name: 'NativeDocument'
14
+ name: 'NativeDocument',
15
+ sourcemap: true
13
16
  },
17
+ plugins: [
18
+ replace({
19
+ 'process.env.NODE_ENV': JSON.stringify('development'),
20
+ preventAssignment: true,
21
+ })
22
+ ]
14
23
  },
15
24
  {
16
25
  input: {
@@ -23,6 +32,10 @@ export default [
23
32
  name: 'NativeDocument'
24
33
  },
25
34
  plugins: [
35
+ replace({
36
+ 'process.env.NODE_ENV': JSON.stringify('production'),
37
+ preventAssignment: true,
38
+ }),
26
39
  terser()
27
40
  ]
28
41
  }
@@ -10,6 +10,8 @@ export default function ObservableChecker($observable, $checker) {
10
10
  this.unSubscriptions = [];
11
11
  }
12
12
 
13
+ ObservableChecker.prototype.__$isObservableChecker = true;
14
+
13
15
  ObservableChecker.prototype.subscribe = function(callback) {
14
16
  const unSubscribe = this.observable.subscribe((value) => {
15
17
  callback && callback(this.checker(value));
@@ -36,6 +36,8 @@ Object.defineProperty(ObservableItem.prototype, '$value', {
36
36
  configurable: true,
37
37
  });
38
38
 
39
+ ObservableItem.prototype.__$isObservable = true;
40
+
39
41
  ObservableItem.prototype.triggerListeners = function(operations) {
40
42
  const $listeners = this.$listeners;
41
43
  const $previousValue = this.$previousValue;
@@ -77,9 +77,10 @@ Observable.value = function(data) {
77
77
  }
78
78
  if(Validator.isArray(data)) {
79
79
  const result = [];
80
- data.forEach(item => {
80
+ for(let i = 0, length = data.length; i < length; i++) {
81
+ const item = data[i];
81
82
  result.push(Observable.value(item));
82
- });
83
+ }
83
84
  return result;
84
85
  }
85
86
  return data;
@@ -264,7 +264,6 @@ export function ForEachArray(data, callback, key, configs = {}) {
264
264
  if(operations?.action === 'populate') {
265
265
  Actions.populate(operations.args, operations.result);
266
266
  } else {
267
- console.log(lastNumberOfItems);
268
267
  if(operations.action === 'clear' || !items.length) {
269
268
  if(lastNumberOfItems === 0) {
270
269
  return;
@@ -284,7 +283,6 @@ export function ForEachArray(data, callback, key, configs = {}) {
284
283
  }
285
284
  }
286
285
 
287
- console.log(items)
288
286
  updateIndexObservers(items, 0);
289
287
  };
290
288
 
@@ -15,7 +15,6 @@ export function Link(options, children){
15
15
  }
16
16
  const routerName = target.router || DEFAULT_ROUTER_NAME;
17
17
  const router = Router.get(routerName);
18
- console.log(routerName)
19
18
  if(!router) {
20
19
  throw new RouterError('Router not found "'+routerName+'" for link "'+target.name+'"');
21
20
  }
@@ -1,31 +1,43 @@
1
- const DebugManager = {
2
- enabled: false,
1
+ let DebugManager = {};
3
2
 
4
- enable() {
5
- this.enabled = true;
6
- console.log('🔍 NativeDocument Debug Mode enabled');
7
- },
3
+ if(process.env.NODE_ENV === 'development') {
4
+ DebugManager = {
5
+ enabled: false,
8
6
 
9
- disable() {
10
- this.enabled = false;
11
- },
7
+ enable() {
8
+ this.enabled = true;
9
+ console.log('🔍 NativeDocument Debug Mode enabled');
10
+ },
12
11
 
13
- log(category, message, data) {
14
- if (!this.enabled) return;
15
- console.group(`🔍 [${category}] ${message}`);
16
- if (data) console.log(data);
17
- console.trace();
18
- console.groupEnd();
19
- },
12
+ disable() {
13
+ this.enabled = false;
14
+ },
20
15
 
21
- warn(category, message, data) {
22
- if (!this.enabled) return;
23
- console.warn(`⚠️ [${category}] ${message}`, data);
24
- },
16
+ log(category, message, data) {
17
+ if (!this.enabled) return;
18
+ console.group(`🔍 [${category}] ${message}`);
19
+ if (data) console.log(data);
20
+ console.trace();
21
+ console.groupEnd();
22
+ },
25
23
 
26
- error(category, message, error) {
27
- console.error(`❌ [${category}] ${message}`, error);
28
- }
29
- };
24
+ warn(category, message, data) {
25
+ if (!this.enabled) return;
26
+ console.warn(`⚠️ [${category}] ${message}`, data);
27
+ },
30
28
 
29
+ error(category, message, error) {
30
+ console.error(`❌ [${category}] ${message}`, error);
31
+ }
32
+ };
33
+
34
+ }
35
+ if(process.env.NODE_ENV === 'production') {
36
+ DebugManager = {
37
+ log() {},
38
+ warn() {},
39
+ error() {},
40
+ disable() {}
41
+ };
42
+ }
31
43
  export default DebugManager;
@@ -6,13 +6,13 @@ import {NDElement} from "../wrappers/NDElement";
6
6
 
7
7
  const Validator = {
8
8
  isObservable(value) {
9
- return value instanceof ObservableItem || value instanceof ObservableChecker;
9
+ return value instanceof ObservableItem || value instanceof ObservableChecker || value?.__$isObservable;
10
10
  },
11
11
  isProxy(value) {
12
12
  return value?.__isProxy__
13
13
  },
14
14
  isObservableChecker(value) {
15
- return value instanceof ObservableChecker;
15
+ return value instanceof ObservableChecker || value?.__$isObservableChecker;
16
16
  },
17
17
  isArray(value) {
18
18
  return Array.isArray(value);
@@ -55,7 +55,7 @@ const Validator = {
55
55
  ['string', 'number', 'boolean'].includes(typeof child);
56
56
  },
57
57
  isNDElement(child) {
58
- return child instanceof NDElement;
58
+ return child instanceof NDElement || child?.constructor?.__$isNDElement;
59
59
  },
60
60
  isValidChildren(children) {
61
61
  if (!Array.isArray(children)) {
@@ -5,6 +5,7 @@ export function NDElement(element) {
5
5
  this.$element = element;
6
6
  this.$observer = null;
7
7
  }
8
+ NDElement.prototype.__$isNDElement = true;
8
9
 
9
10
  for(const event of EVENTS) {
10
11
  const eventName = event.toLowerCase();
@@ -37,18 +38,18 @@ for(const event of EVENTS) {
37
38
  }
38
39
 
39
40
  NDElement.prototype.ref = function(target, name) {
40
- target[name] = element;
41
+ target[name] = this.$element;
41
42
  return this;
42
43
  };
43
44
 
44
45
  NDElement.prototype.unmountChildren = function() {
45
46
  let element = this.$element;
46
47
  for(let i = 0, length = element.children.length; i < length; i++) {
47
- let elementchildren = element.children[i];
48
- if(!elementchildren.$ndProx) {
49
- elementchildren.nd?.remove();
48
+ let elementChildren = element.children[i];
49
+ if(!elementChildren.$ndProx) {
50
+ elementChildren.nd?.remove();
50
51
  }
51
- elementchildren = null;
52
+ elementChildren = null;
52
53
  }
53
54
  element = null;
54
55
  return this;
@@ -76,6 +77,12 @@ NDElement.prototype.mounted = function(callback) {
76
77
  return this.lifecycle({ mounted: callback });
77
78
  };
78
79
 
79
- NDElement.prototype.mounted = function(callback) {
80
+ NDElement.prototype.unmounted = function(callback) {
80
81
  return this.lifecycle({ unmounted: callback });
81
- };
82
+ };
83
+
84
+ NDElement.prototype.htmlElement = function() {
85
+ return this.$element;
86
+ };
87
+
88
+ NDElement.prototype.node = NDElement.prototype.htmlElement;
@@ -1,6 +1,7 @@
1
1
  import { NDElement } from "./NDElement";
2
2
 
3
3
  Object.defineProperty(HTMLElement.prototype, 'nd', {
4
+ configurable: true,
4
5
  get() {
5
6
  if(this.$nd) {
6
7
  return this.$nd;