native-document 1.0.9 → 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 +2 -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 +35 -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/plugins-manager.js +12 -0
- package/src/utils/prototypes.js +13 -0
- package/src/utils/validator.js +23 -1
- package/src/wrappers/AttributesWrapper.js +11 -1
- package/src/wrappers/HtmlElementWrapper.js +10 -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
|
|
@@ -120,6 +133,26 @@ Observable.init = function(value) {
|
|
|
120
133
|
|
|
121
134
|
Observable.object = Observable.init;
|
|
122
135
|
Observable.json = Observable.init;
|
|
136
|
+
Observable.update = function($target, data) {
|
|
137
|
+
for(const key in data) {
|
|
138
|
+
const targetItem = $target[key];
|
|
139
|
+
const newValue = data[key];
|
|
140
|
+
|
|
141
|
+
if(Validator.isObservable(targetItem)) {
|
|
142
|
+
if(Validator.isArray(newValue)) {
|
|
143
|
+
Observable.update(targetItem, newValue);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
targetItem.set(newValue);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if(Validator.isProxy(targetItem)) {
|
|
150
|
+
Observable.update(targetItem, newValue);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
$target[key] = newValue;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
123
156
|
/**
|
|
124
157
|
*
|
|
125
158
|
* @param {Array} target
|
|
@@ -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/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,7 +70,29 @@ const Validator = {
|
|
|
70
70
|
|
|
71
71
|
return children;
|
|
72
72
|
},
|
|
73
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Check if the data contains observables.
|
|
75
|
+
* @param {Array|Object} data
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
containsObservables(data) {
|
|
79
|
+
if(!data) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return Validator.isObject(data)
|
|
83
|
+
&& Object.values(data).some(value => Validator.isObservable(value));
|
|
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
|
+
},
|
|
74
96
|
validateAttributes(attributes) {
|
|
75
97
|
if (!attributes || typeof attributes !== 'object') {
|
|
76
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;
|
|
@@ -5,6 +5,7 @@ import DocumentObserver from "./DocumentObserver";
|
|
|
5
5
|
import Validator from "../utils/validator";
|
|
6
6
|
import DebugManager from "../utils/debug-manager";
|
|
7
7
|
import Anchor from "../elements/anchor";
|
|
8
|
+
import PluginsManager from "../utils/plugins-manager";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
*
|
|
@@ -116,6 +117,9 @@ export const ElementCreator = {
|
|
|
116
117
|
const childrenArray = Array.isArray(children) ? children : [children];
|
|
117
118
|
childrenArray.forEach(child => {
|
|
118
119
|
if (child === null) return;
|
|
120
|
+
if(Validator.isString(child) && Validator.isFunction(child.resolveObservableTemplate)) {
|
|
121
|
+
child = child.resolveObservableTemplate();
|
|
122
|
+
}
|
|
119
123
|
if(Validator.isFunction(child)) {
|
|
120
124
|
this.processChildren(child(), parent);
|
|
121
125
|
return;
|
|
@@ -160,6 +164,11 @@ export const ElementCreator = {
|
|
|
160
164
|
HtmlElementEventsWrapper(element);
|
|
161
165
|
const item = (typeof customWrapper === 'function') ? customWrapper(element) : element;
|
|
162
166
|
addUtilsMethods(item);
|
|
167
|
+
|
|
168
|
+
PluginsManager.list().forEach(plugin => {
|
|
169
|
+
plugin?.element?.setup && plugin.element.setup(item, attributes);
|
|
170
|
+
});
|
|
171
|
+
|
|
163
172
|
return item;
|
|
164
173
|
}
|
|
165
174
|
};
|
|
@@ -189,7 +198,7 @@ export default function HtmlElementWrapper(name, customWrapper) {
|
|
|
189
198
|
} catch (error) {
|
|
190
199
|
DebugManager.error('ElementCreation', `Error creating ${$tagName}`, error);
|
|
191
200
|
}
|
|
192
|
-
}
|
|
201
|
+
};
|
|
193
202
|
|
|
194
203
|
builder.hold = (children, attributes) => (() => builder(children, attributes));
|
|
195
204
|
|