native-document 1.0.10 → 1.0.12
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 +212 -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/elements/anchor.js +1 -1
- 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/router.js
CHANGED
|
@@ -5,10 +5,15 @@ const MemoryManager = (function() {
|
|
|
5
5
|
|
|
6
6
|
let $nexObserverId = 0;
|
|
7
7
|
const $observables = new Map();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
heldValue
|
|
11
|
-
|
|
8
|
+
let $registry = null;
|
|
9
|
+
try {
|
|
10
|
+
$registry = new FinalizationRegistry((heldValue) => {
|
|
11
|
+
DebugManager.log('MemoryManager', '🧹 Auto-cleanup observable:', heldValue);
|
|
12
|
+
heldValue.listeners.splice(0);
|
|
13
|
+
});
|
|
14
|
+
} catch (e) {
|
|
15
|
+
DebugManager.warn('MemoryManager', 'FinalizationRegistry not supported, observables will not be cleaned automatically');
|
|
16
|
+
}
|
|
12
17
|
|
|
13
18
|
return {
|
|
14
19
|
/**
|
|
@@ -24,10 +29,15 @@ const MemoryManager = (function() {
|
|
|
24
29
|
id: id,
|
|
25
30
|
listeners
|
|
26
31
|
};
|
|
27
|
-
$registry
|
|
32
|
+
if($registry) {
|
|
33
|
+
$registry.register(observable, heldValue);
|
|
34
|
+
}
|
|
28
35
|
$observables.set(id, new WeakRef(observable));
|
|
29
36
|
return id;
|
|
30
37
|
},
|
|
38
|
+
getObservableById(id) {
|
|
39
|
+
return $observables.get(id)?.deref();
|
|
40
|
+
},
|
|
31
41
|
cleanup() {
|
|
32
42
|
for (const [_, weakObservableRef] of $observables) {
|
|
33
43
|
const observable = weakObservableRef.deref();
|
package/src/data/Observable.js
CHANGED
|
@@ -14,18 +14,31 @@ export function Observable(value) {
|
|
|
14
14
|
return new ObservableItem(value);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
Observable.computed = function(callback, dependencies = []) {
|
|
19
18
|
const initialValue = callback();
|
|
20
19
|
const observable = new ObservableItem(initialValue);
|
|
21
20
|
|
|
22
|
-
const updatedValue =
|
|
21
|
+
const updatedValue = () => observable.set(callback());
|
|
23
22
|
|
|
24
23
|
dependencies.forEach(dependency => dependency.subscribe(updatedValue));
|
|
25
24
|
|
|
26
25
|
return observable;
|
|
27
26
|
};
|
|
28
27
|
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @param id
|
|
31
|
+
* @returns {ObservableItem|null}
|
|
32
|
+
*/
|
|
33
|
+
Observable.getById = function(id) {
|
|
34
|
+
const item = MemoryManager.getObservableById(parseInt(id));
|
|
35
|
+
if(!item) {
|
|
36
|
+
throw new NativeDocumentError('Observable.getById : No observable found with id ' + id);
|
|
37
|
+
}
|
|
38
|
+
return item;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
|
|
29
42
|
/**
|
|
30
43
|
*
|
|
31
44
|
* @param {ObservableItem} observable
|
|
@@ -17,6 +17,9 @@ export default function ObservableChecker($observable, $checker) {
|
|
|
17
17
|
this.val = function() {
|
|
18
18
|
return $checker && $checker($observable.val());
|
|
19
19
|
};
|
|
20
|
+
this.check = function(callback) {
|
|
21
|
+
return $observable.check(() => callback(this.val()));
|
|
22
|
+
};
|
|
20
23
|
|
|
21
24
|
this.set = function(value) {
|
|
22
25
|
return $observable.set(value);
|
package/src/data/Store.js
CHANGED
|
@@ -11,7 +11,7 @@ export const Store = (function() {
|
|
|
11
11
|
* @returns {ObservableItem}
|
|
12
12
|
*/
|
|
13
13
|
use(name) {
|
|
14
|
-
const {observer: originalObserver,
|
|
14
|
+
const {observer: originalObserver, subscribers } = $stores.get(name);
|
|
15
15
|
const observerFollower = Observable(originalObserver.val());
|
|
16
16
|
const unSubscriber = originalObserver.subscribe(value => observerFollower.set(value));
|
|
17
17
|
const updaterUnsubscriber = observerFollower.subscribe(value => originalObserver.set(value));
|
|
@@ -20,7 +20,7 @@ export const Store = (function() {
|
|
|
20
20
|
updaterUnsubscriber();
|
|
21
21
|
observerFollower.cleanup();
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
subscribers.add(observerFollower);
|
|
24
24
|
|
|
25
25
|
return observerFollower;
|
|
26
26
|
},
|
|
@@ -39,7 +39,7 @@ export const Store = (function() {
|
|
|
39
39
|
*/
|
|
40
40
|
create(name, value) {
|
|
41
41
|
const observer = Observable(value);
|
|
42
|
-
$stores.set(name, { observer,
|
|
42
|
+
$stores.set(name, { observer, subscribers: new Set()});
|
|
43
43
|
return observer;
|
|
44
44
|
},
|
|
45
45
|
/**
|
|
@@ -54,9 +54,9 @@ export const Store = (function() {
|
|
|
54
54
|
/**
|
|
55
55
|
*
|
|
56
56
|
* @param {string} name
|
|
57
|
-
* @returns {{observer: ObservableItem,
|
|
57
|
+
* @returns {{observer: ObservableItem, subscribers: Set}}
|
|
58
58
|
*/
|
|
59
|
-
|
|
59
|
+
getWithSubscribers(name) {
|
|
60
60
|
return $stores.get(name);
|
|
61
61
|
},
|
|
62
62
|
/**
|
|
@@ -67,7 +67,7 @@ export const Store = (function() {
|
|
|
67
67
|
const item = $stores.get(name);
|
|
68
68
|
if(!item) return;
|
|
69
69
|
item.observer.cleanup();
|
|
70
|
-
item.
|
|
70
|
+
item.subscribers.forEach(follower => follower.destroy());
|
|
71
71
|
item.observer.clear();
|
|
72
72
|
}
|
|
73
73
|
};
|
package/src/elements/anchor.js
CHANGED
package/src/router/Router.js
CHANGED
|
@@ -9,7 +9,7 @@ import MemoryRouter from "./modes/MemoryRouter.js";
|
|
|
9
9
|
import DebugManager from "../utils/debug-manager.js";
|
|
10
10
|
import {RouterComponent} from "./RouterComponent.js";
|
|
11
11
|
|
|
12
|
-
const DEFAULT_ROUTER_NAME = 'default';
|
|
12
|
+
export const DEFAULT_ROUTER_NAME = 'default';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
*
|
|
@@ -209,21 +209,21 @@ Router.create = function(options, callback) {
|
|
|
209
209
|
|
|
210
210
|
router.init(options.entry);
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
throw new RouterError(`Container not found for selector: ${container}`);
|
|
218
|
-
}
|
|
219
|
-
container = mountContainer;
|
|
220
|
-
} else if(!Validator.isElement(container)) {
|
|
221
|
-
throw new RouterError('Container must be a string or an Element');
|
|
212
|
+
router.mount = function(container) {
|
|
213
|
+
if(Validator.isString(container)) {
|
|
214
|
+
const mountContainer = document.querySelector(container);
|
|
215
|
+
if(!mountContainer) {
|
|
216
|
+
throw new RouterError(`Container not found for selector: ${container}`);
|
|
222
217
|
}
|
|
223
|
-
|
|
224
|
-
|
|
218
|
+
container = mountContainer;
|
|
219
|
+
} else if(!Validator.isElement(container)) {
|
|
220
|
+
throw new RouterError('Container must be a string or an Element');
|
|
225
221
|
}
|
|
222
|
+
|
|
223
|
+
return RouterComponent(router, container);
|
|
226
224
|
};
|
|
225
|
+
|
|
226
|
+
return router;
|
|
227
227
|
};
|
|
228
228
|
|
|
229
229
|
Router.get = function(name) {
|
package/src/router/link.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import Validator from "../utils/validator.js";
|
|
2
2
|
import {Link as NativeLink} from "../../elements.js";
|
|
3
|
-
import Router from "./Router.js";
|
|
3
|
+
import Router, {DEFAULT_ROUTER_NAME} from "./Router.js";
|
|
4
4
|
import RouterError from "../errors/RouterError.js";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
export function Link(
|
|
8
|
-
const
|
|
7
|
+
export function Link(options, children){
|
|
8
|
+
const { to, href, ...attributes } = options;
|
|
9
|
+
const target = to || href;
|
|
9
10
|
if(Validator.isString(target)) {
|
|
10
11
|
const router = Router.get();
|
|
11
12
|
return NativeLink({ ...attributes, href: target}, children).nd.on.prevent.click(() => {
|
|
12
13
|
router.push(target);
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
|
-
const
|
|
16
|
+
const routerName = target.router || DEFAULT_ROUTER_NAME;
|
|
17
|
+
const router = Router.get(routerName);
|
|
18
|
+
console.log(routerName)
|
|
16
19
|
if(!router) {
|
|
17
|
-
throw new RouterError('Router not found "'+
|
|
20
|
+
throw new RouterError('Router not found "'+routerName+'" for link "'+target.name+'"');
|
|
18
21
|
}
|
|
19
22
|
const url = router.generateUrl(target.name, target.params, target.query);
|
|
20
23
|
return NativeLink({ ...attributes, href: url }, children).nd.on.prevent.click(() => {
|
package/src/utils/prototypes.js
CHANGED
|
@@ -29,4 +29,17 @@ String.prototype.use = function(args) {
|
|
|
29
29
|
return data;
|
|
30
30
|
});
|
|
31
31
|
}, Object.values(args));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
String.prototype.resolveObservableTemplate = function() {
|
|
35
|
+
if(!Validator.containsObservableReference(this)) {
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
return this.split(/(\{\{#ObItem::\([0-9]+\)\}\})/g).filter(Boolean).map((value) => {
|
|
39
|
+
if(!Validator.containsObservableReference(value)) {
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
const [_, id] = value.match(/\{\{#ObItem::\(([0-9]+)\)\}\}/);
|
|
43
|
+
return Observable.getById(id);
|
|
44
|
+
});
|
|
32
45
|
}
|
package/src/utils/validator.js
CHANGED
|
@@ -70,6 +70,11 @@ const Validator = {
|
|
|
70
70
|
|
|
71
71
|
return children;
|
|
72
72
|
},
|
|
73
|
+
/**
|
|
74
|
+
* Check if the data contains observables.
|
|
75
|
+
* @param {Array|Object} data
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
73
78
|
containsObservables(data) {
|
|
74
79
|
if(!data) {
|
|
75
80
|
return false;
|
|
@@ -77,6 +82,17 @@ const Validator = {
|
|
|
77
82
|
return Validator.isObject(data)
|
|
78
83
|
&& Object.values(data).some(value => Validator.isObservable(value));
|
|
79
84
|
},
|
|
85
|
+
/**
|
|
86
|
+
* Check if the data contains an observable reference.
|
|
87
|
+
* @param {string} data
|
|
88
|
+
* @returns {boolean}
|
|
89
|
+
*/
|
|
90
|
+
containsObservableReference(data) {
|
|
91
|
+
if(!data || typeof data !== 'string') {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return /\{\{#ObItem::\([0-9]+\)\}\}/.test(data);
|
|
95
|
+
},
|
|
80
96
|
validateAttributes(attributes) {
|
|
81
97
|
if (!attributes || typeof attributes !== 'object') {
|
|
82
98
|
return attributes;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Validator from "../utils/validator";
|
|
2
2
|
import NativeDocumentError from "../errors/NativeDocumentError";
|
|
3
3
|
import {BOOLEAN_ATTRIBUTES} from "./constants.js";
|
|
4
|
+
import {Observable} from "../data/Observable";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
*
|
|
@@ -124,7 +125,16 @@ export default function AttributesWrapper(element, attributes) {
|
|
|
124
125
|
|
|
125
126
|
for(let key in attributes) {
|
|
126
127
|
const attributeName = key.toLowerCase();
|
|
127
|
-
|
|
128
|
+
let value = attributes[attributeName];
|
|
129
|
+
if(Validator.isString(value) && Validator.isFunction(value.resolveObservableTemplate)) {
|
|
130
|
+
value = value.resolveObservableTemplate();
|
|
131
|
+
if(Validator.isArray(value)) {
|
|
132
|
+
const observables = value.filter(item => Validator.isObservable(item));
|
|
133
|
+
value = Observable.computed(() => {
|
|
134
|
+
return value.map(item => Validator.isObservable(item) ? item.val() : item).join(' ') || ' ';
|
|
135
|
+
}, observables);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
128
138
|
if(BOOLEAN_ATTRIBUTES.includes(attributeName)) {
|
|
129
139
|
bindBooleanAttribute(element, attributeName, value);
|
|
130
140
|
continue;
|
|
@@ -117,6 +117,9 @@ export const ElementCreator = {
|
|
|
117
117
|
const childrenArray = Array.isArray(children) ? children : [children];
|
|
118
118
|
childrenArray.forEach(child => {
|
|
119
119
|
if (child === null) return;
|
|
120
|
+
if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
|
|
121
|
+
child = child.resolveObservableTemplate();
|
|
122
|
+
}
|
|
120
123
|
if(Validator.isFunction(child)) {
|
|
121
124
|
this.processChildren(child(), parent);
|
|
122
125
|
return;
|
|
@@ -195,7 +198,7 @@ export default function HtmlElementWrapper(name, customWrapper) {
|
|
|
195
198
|
} catch (error) {
|
|
196
199
|
DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
|
|
197
200
|
}
|
|
198
|
-
}
|
|
201
|
+
};
|
|
199
202
|
|
|
200
203
|
builder.hold = (children, attributes) => (() => builder(children, attributes));
|
|
201
204
|
|