native-document 1.0.10 → 1.0.11
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/dist/native-document.dev.js +2671 -0
- package/dist/native-document.min.js +1 -0
- package/docs/anchor.md +208 -0
- package/docs/conditional-rendering.md +628 -0
- package/docs/contributing.md +51 -0
- package/docs/core-concepts.md +513 -0
- package/docs/elements.md +383 -0
- package/docs/getting-started.md +403 -0
- package/docs/lifecycle-events.md +106 -0
- package/docs/memory-management.md +90 -0
- package/docs/observables.md +265 -0
- package/docs/routing.md +817 -0
- package/docs/state-management.md +423 -0
- package/docs/validation.md +193 -0
- package/elements.js +3 -1
- package/index.js +1 -0
- package/package.json +1 -1
- package/readme.md +189 -425
- package/router.js +2 -0
- package/src/data/MemoryManager.js +15 -5
- package/src/data/Observable.js +15 -2
- package/src/data/ObservableChecker.js +3 -0
- package/src/data/ObservableItem.js +4 -0
- package/src/data/Store.js +6 -6
- package/src/router/Router.js +13 -13
- package/src/router/link.js +8 -5
- package/src/utils/prototypes.js +13 -0
- package/src/utils/validator.js +16 -0
- package/src/wrappers/AttributesWrapper.js +11 -1
- package/src/wrappers/HtmlElementWrapper.js +4 -1
package/docs/elements.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# Elements
|
|
2
|
+
|
|
3
|
+
NativeDocument provides a simple and intuitive way to create HTML elements with a declarative syntax. Every HTML element has a corresponding function that creates reactive DOM elements.
|
|
4
|
+
|
|
5
|
+
## Basic Element Creation
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Simple elements with attributes
|
|
9
|
+
const title = H1({ class: "main-title" }, "Welcome to my app");
|
|
10
|
+
const description = P({ class: "description" }, "This is a paragraph");
|
|
11
|
+
|
|
12
|
+
// Elements without attributes (attributes omitted)
|
|
13
|
+
const simpleTitle = H1("Welcome to my app");
|
|
14
|
+
const simplePara = P("This is a paragraph");
|
|
15
|
+
const container = Div("Content here");
|
|
16
|
+
|
|
17
|
+
// Elements without content
|
|
18
|
+
const separator = Hr();
|
|
19
|
+
const lineBreak = Br();
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Element Structure
|
|
23
|
+
|
|
24
|
+
All element functions follow the same pattern:
|
|
25
|
+
```javascript
|
|
26
|
+
ElementName(attributes, children)
|
|
27
|
+
// or
|
|
28
|
+
ElementName(children) // attributes are optional
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- **attributes**: Object with HTML attributes (optional, can be `null` or omitted)
|
|
32
|
+
- **children**: Content inside the element (text, number, observable, other elements, or arrays)
|
|
33
|
+
|
|
34
|
+
## Working with Attributes
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
// Static attributes
|
|
38
|
+
const link = Link({
|
|
39
|
+
href: "/about",
|
|
40
|
+
class: "nav-link",
|
|
41
|
+
id: "about-link"
|
|
42
|
+
}, "About Us");
|
|
43
|
+
|
|
44
|
+
// Boolean attributes
|
|
45
|
+
const input = Input({
|
|
46
|
+
type: "checkbox",
|
|
47
|
+
checked: true,
|
|
48
|
+
disabled: false
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Data attributes
|
|
52
|
+
const element = Div({
|
|
53
|
+
"data-id": "123",
|
|
54
|
+
"data-category": "important"
|
|
55
|
+
}, "Content");
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Reactive Attributes with Observables
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
const isVisible = Observable(true);
|
|
62
|
+
const userName = Observable("Guest");
|
|
63
|
+
const theme = Observable("dark");
|
|
64
|
+
|
|
65
|
+
// Reactive attributes
|
|
66
|
+
const greeting = Div({
|
|
67
|
+
class: theme, // Updates when theme changes
|
|
68
|
+
hidden: isVisible.check(val => !val) // Hide when isVisible is false
|
|
69
|
+
}, `Hello ${userName}!`); // Reactive text content
|
|
70
|
+
|
|
71
|
+
// Reactive styles
|
|
72
|
+
const box = Div({
|
|
73
|
+
style: {
|
|
74
|
+
backgroundColor: theme.check(t => t === "dark" ? "#333" : "#fff"),
|
|
75
|
+
color: theme.check(t => t === "dark" ? "#fff" : "#333")
|
|
76
|
+
}
|
|
77
|
+
}, "Themed content");
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Children and Content
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// Text content (no attributes needed)
|
|
84
|
+
const simple = P("Simple text");
|
|
85
|
+
|
|
86
|
+
// Single child element
|
|
87
|
+
const wrapper = Div({ class: "wrapper" },
|
|
88
|
+
P("Wrapped paragraph")
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Multiple children as array
|
|
92
|
+
const list = Div({ class: "item-list" }, [
|
|
93
|
+
P("First item"),
|
|
94
|
+
P("Second item"),
|
|
95
|
+
P("Third item")
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
// Mixed content
|
|
99
|
+
const mixed = Div([
|
|
100
|
+
H2("Title"),
|
|
101
|
+
"Some text between elements",
|
|
102
|
+
P("A paragraph"),
|
|
103
|
+
Button("Click me")
|
|
104
|
+
]);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Event Handling with .nd API
|
|
108
|
+
|
|
109
|
+
The `.nd` (NativeDocument) API provides a fluent interface for adding functionality to elements.
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const button = Button("Click me")
|
|
113
|
+
.nd.on.click(() => {
|
|
114
|
+
console.log("Button clicked!");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// With attributes and events
|
|
118
|
+
const styledButton = Button({ class: "btn" }, "Click me")
|
|
119
|
+
.nd.on.click(() => {
|
|
120
|
+
console.log("Button clicked!");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Multiple events
|
|
124
|
+
const input = Input({ type: "text", placeholder: "Type here..." })
|
|
125
|
+
.nd.on.focus(() => console.log("Input focused"))
|
|
126
|
+
.nd.on.blur(() => console.log("Input blurred"))
|
|
127
|
+
.nd.on.input(event => console.log("Input value:", event.target.value));
|
|
128
|
+
|
|
129
|
+
// Or
|
|
130
|
+
const input = Input({ type: "text", placeholder: "Type here..." })
|
|
131
|
+
.nd.on({
|
|
132
|
+
focus: () => console.log("Input focused"),
|
|
133
|
+
blur: () => console.log("Input blurred"),
|
|
134
|
+
input: event => console.log("Input value:", event.target.value)
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Prevent default behavior
|
|
138
|
+
const form = Form()
|
|
139
|
+
.nd.on.prevent.submit(event => {
|
|
140
|
+
console.log("Form submitted without page reload");
|
|
141
|
+
// Handle form submission
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Form Elements and Two-Way Binding
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
const name = Observable("");
|
|
149
|
+
const email = Observable("");
|
|
150
|
+
const isChecked = Observable(false);
|
|
151
|
+
|
|
152
|
+
// Text input with two-way binding
|
|
153
|
+
const nameInput = Input({
|
|
154
|
+
type: "text",
|
|
155
|
+
value: name, // Automatic two-way binding
|
|
156
|
+
placeholder: "Enter your name"
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Email input
|
|
160
|
+
const emailInput = Input({
|
|
161
|
+
type: "email",
|
|
162
|
+
value: email,
|
|
163
|
+
placeholder: "Enter your email"
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Checkbox with binding
|
|
167
|
+
const checkbox = Input({
|
|
168
|
+
type: "checkbox",
|
|
169
|
+
checked: isChecked // Automatic two-way binding
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Conditional Classes and Styles
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
const isActive = Observable(false);
|
|
177
|
+
const count = Observable(0);
|
|
178
|
+
|
|
179
|
+
// Conditional classes
|
|
180
|
+
const item = Div({
|
|
181
|
+
class: {
|
|
182
|
+
"item": true, // Always present
|
|
183
|
+
"active": isActive, // Present when isActive is true
|
|
184
|
+
"highlighted": count.check(c => c > 5) // Present when count > 5
|
|
185
|
+
}
|
|
186
|
+
}, "List item");
|
|
187
|
+
|
|
188
|
+
// Dynamic styles
|
|
189
|
+
const progress = Div({
|
|
190
|
+
style: {
|
|
191
|
+
width: count.check(c => `${c}%`),
|
|
192
|
+
backgroundColor: count.check(c => c > 50 ? "green" : "red")
|
|
193
|
+
}
|
|
194
|
+
}, "Progress bar");
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Lifecycle Management
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
const component = Div("Component content")
|
|
201
|
+
.nd.mounted(element => {
|
|
202
|
+
console.log("Component mounted to DOM");
|
|
203
|
+
// Initialize component
|
|
204
|
+
})
|
|
205
|
+
.nd.unmounted(element => {
|
|
206
|
+
console.log("Component removed from DOM");
|
|
207
|
+
// Cleanup resources
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Combined lifecycle
|
|
211
|
+
const widget = Div("Widget")
|
|
212
|
+
.nd.lifecycle({
|
|
213
|
+
mounted: element => console.log("Widget mounted"),
|
|
214
|
+
unmounted: element => console.log("Widget unmounted")
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Element References
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const refs = {};
|
|
222
|
+
|
|
223
|
+
const app = Div([
|
|
224
|
+
Input({ type: "text" })
|
|
225
|
+
.nd.ref(refs, "nameInput"), // Store reference as refs.nameInput
|
|
226
|
+
|
|
227
|
+
Button("Focus Input")
|
|
228
|
+
.nd.on.click(() => {
|
|
229
|
+
refs.nameInput.focus(); // Use the reference
|
|
230
|
+
})
|
|
231
|
+
]);
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Practical Example: Simple Button with Event
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const count = Observable(0);
|
|
238
|
+
|
|
239
|
+
const incrementButton = Button({
|
|
240
|
+
class: "btn btn-primary",
|
|
241
|
+
type: "button"
|
|
242
|
+
}, "Increment")
|
|
243
|
+
.nd.on.click(() => {
|
|
244
|
+
count.set(count.val() + 1);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const display = Div({ class: "counter-display" }, [
|
|
248
|
+
P("Current count: "),
|
|
249
|
+
Strong(count) // Reactive display
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
const app = Div({ class: "counter-app" }, [
|
|
253
|
+
display,
|
|
254
|
+
incrementButton
|
|
255
|
+
]);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Practical Example: Form with Validation
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const formData = Observable.object({
|
|
262
|
+
name: "",
|
|
263
|
+
email: "",
|
|
264
|
+
age: ""
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const errors = Observable.object({
|
|
268
|
+
name: "",
|
|
269
|
+
email: "",
|
|
270
|
+
age: ""
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Validation function
|
|
274
|
+
const validateForm = () => {
|
|
275
|
+
const data = formData.$val();
|
|
276
|
+
const newErrors = {};
|
|
277
|
+
|
|
278
|
+
newErrors.name = data.name.length < 2 ? "Name must be at least 2 characters" : "";
|
|
279
|
+
newErrors.email = !data.email.includes("@") ? "Invalid email address" : "";
|
|
280
|
+
newErrors.age = isNaN(data.age) || data.age < 1 ? "Age must be a valid number" : "";
|
|
281
|
+
|
|
282
|
+
Observable.update(errors, newErrors);
|
|
283
|
+
|
|
284
|
+
return Object.values(newErrors).every(error => error === "");
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const contactForm = Form({ class: "contact-form" }, [
|
|
288
|
+
// Name field
|
|
289
|
+
Div({ class: "field" }, [
|
|
290
|
+
Label("Name:"),
|
|
291
|
+
Input({
|
|
292
|
+
type: "text",
|
|
293
|
+
value: formData.name,
|
|
294
|
+
placeholder: "Enter your name"
|
|
295
|
+
}).nd.on.blur(validateForm),
|
|
296
|
+
|
|
297
|
+
ShowIf(errors.name.check(err => err !== ""),
|
|
298
|
+
Span({ class: "error" }, errors.name)
|
|
299
|
+
)
|
|
300
|
+
]),
|
|
301
|
+
|
|
302
|
+
// Email field
|
|
303
|
+
Div({ class: "field" }, [
|
|
304
|
+
Label("Email:"),
|
|
305
|
+
Input({
|
|
306
|
+
type: "email",
|
|
307
|
+
value: formData.email,
|
|
308
|
+
placeholder: "Enter your email"
|
|
309
|
+
}).nd.on.blur(validateForm),
|
|
310
|
+
|
|
311
|
+
ShowIf(errors.email.check(err => err !== ""),
|
|
312
|
+
Span({ class: "error" }, errors.email)
|
|
313
|
+
)
|
|
314
|
+
]),
|
|
315
|
+
|
|
316
|
+
// Age field
|
|
317
|
+
Div({ class: "field" }, [
|
|
318
|
+
Label("Age:"),
|
|
319
|
+
Input({
|
|
320
|
+
type: "number",
|
|
321
|
+
value: formData.age,
|
|
322
|
+
placeholder: "Enter your age"
|
|
323
|
+
}).nd.on.blur(validateForm),
|
|
324
|
+
|
|
325
|
+
ShowIf(errors.age.check(err => err !== ""),
|
|
326
|
+
Span({ class: "error" }, errors.age)
|
|
327
|
+
)
|
|
328
|
+
]),
|
|
329
|
+
|
|
330
|
+
// Submit button
|
|
331
|
+
Button({
|
|
332
|
+
type: "submit",
|
|
333
|
+
class: "btn btn-primary"
|
|
334
|
+
}, "Submit")
|
|
335
|
+
])
|
|
336
|
+
.nd.on.prevent.submit(() => {
|
|
337
|
+
if (validateForm()) {
|
|
338
|
+
console.log("Form is valid!", formData.$val());
|
|
339
|
+
// Handle successful submission
|
|
340
|
+
} else {
|
|
341
|
+
console.log("Form has errors");
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Available Elements
|
|
347
|
+
|
|
348
|
+
NativeDocument provides functions for all standard HTML elements:
|
|
349
|
+
|
|
350
|
+
**Text Elements:** `H1`, `H2`, `H3`, `H4`, `H5`, `H6`, `P`, `Span`, `Strong`, `Em`, `Small`, `Mark`
|
|
351
|
+
|
|
352
|
+
**Layout Elements:** `Div`, `Section`, `Article`, `Aside`, `Header`, `Footer`, `Nav`, `Main`
|
|
353
|
+
|
|
354
|
+
**Form Elements:** `Form`, `Input`, `TextArea`, `Select`, `Option`, `Button`, `Label`, `FieldSet`, `Legend`
|
|
355
|
+
|
|
356
|
+
**List Elements:** `Ul`, `Ol`, `Li`, `Dl`, `Dt`, `Dd`
|
|
357
|
+
|
|
358
|
+
**Media Elements:** `Img`, `Audio`, `Video`, `Canvas`, `Svg`
|
|
359
|
+
|
|
360
|
+
**Interactive Elements:** `Link`, `Details`, `Summary`, `Dialog`, `Menu`
|
|
361
|
+
|
|
362
|
+
And many more following the same naming pattern!
|
|
363
|
+
|
|
364
|
+
## Best Practices
|
|
365
|
+
|
|
366
|
+
1. **Use semantic HTML elements** for better accessibility
|
|
367
|
+
2. **Leverage reactive attributes** with observables for dynamic UIs
|
|
368
|
+
3. **Group related elements** in logical containers
|
|
369
|
+
4. **Use the `.nd` API** for event handling and lifecycle management
|
|
370
|
+
5. **Validate form data** reactively for better user experience
|
|
371
|
+
6. **Store element references** when you need to manipulate them later
|
|
372
|
+
7. **Use conditional rendering** with `ShowIf` for dynamic content
|
|
373
|
+
|
|
374
|
+
## Next Steps
|
|
375
|
+
|
|
376
|
+
Now that you understand NativeDocument's elements, explore these advanced topics:
|
|
377
|
+
|
|
378
|
+
- **[Conditional Rendering](docs/conditional-rendering.md)** - Dynamic content
|
|
379
|
+
- **[Routing](docs/routing.md)** - Navigation and URL management
|
|
380
|
+
- **[State Management](docs/state-management.md)** - Global state patterns
|
|
381
|
+
- **[Lifecycle Events](docs/lifecycle-events.md)** - Lifecycle events
|
|
382
|
+
- **[Memory Management](docs/memory-management.md)** - Memory management
|
|
383
|
+
- **[Anchor](docs/anchor.md)** - Anchor
|