native-document 1.0.172 → 1.0.174
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/CHANGELOG.md +74 -37
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/core/data/ObservableItem.js +5 -5
- package/src/core/data/ObservableObject.js +1 -2
- package/src/core/wrappers/HtmlElementWrapper.js +10 -11
- package/src/core/wrappers/NdPrototype.js +37 -4
- package/src/core/wrappers/template-cloner/NodeCloner.js +10 -6
- package/src/core/wrappers/template-cloner/TemplateCloner.js +7 -4
- package/src/fetch/NativeFetch.js +1 -0
- package/src/ui/components/form/fields/FieldRender.js +1 -1
- package/types/native-fetch.d.ts +1 -1
- package/types/observable-resource.d.ts +2 -2
- package/types/observable.d.ts +137 -21
- package/types/service.d.ts +2 -1
- package/utils.js +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -11,27 +11,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.0.173] - 2026-06-18
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `ObservableItem` TypeScript definitions - `subscribe()` and `on()` no longer
|
|
19
|
+
typed as returning `Unsubscribe`; they return nothing in the actual
|
|
20
|
+
implementation. Use `.unsubscribe(callback)` / `.off(value, callback)` with
|
|
21
|
+
the same reference to remove a subscription.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- `ObservableItem` type definitions - `interceptMutations`, `clone`, `valueOf`
|
|
26
|
+
- `ObservableItem` comparison helpers typed: `isEqualTo`, `isNotEqualTo`,
|
|
27
|
+
`isGreaterThan`, `isGreaterThanOrEqualTo`, `isLessThan`,
|
|
28
|
+
`isLessThanOrEqualTo`, `isBetween`, `isNull`, `isTruthy`, `isFalsy`,
|
|
29
|
+
`isStartingWith`, `isEndingWith`, `isMatchingPattern`, `isEmpty`,
|
|
30
|
+
`isNotEmpty`, `isIncludes`, `isIncludedIn`, `isOneOf`, `isHaving`
|
|
31
|
+
- `ObservableItem` transform helpers typed: `toUpperCase`, `toLowerCase`,
|
|
32
|
+
`toTrimmed`, `toBoolean`, `toLiteral`, `toFormatted`, `toProperty`,
|
|
33
|
+
`toLength`, `toClamped`, `toPercent`
|
|
34
|
+
- `ObservableArray` type definitions - `swapItems`, `insertAfter`, `sync`,
|
|
35
|
+
`clone`, `isNotEmpty`, `indexOf`, `includes`; `deepSubscribe` and `sync`
|
|
36
|
+
correctly typed to return `Unsubscribe`
|
|
37
|
+
- `ObservableObject` type definitions - `$observables()` and `configs()` typed
|
|
38
|
+
as callable methods; `subscribe()` documented as deep-subscribing nested
|
|
39
|
+
observables and observable arrays
|
|
40
|
+
- `ObservableResource` type definitions (previously entirely missing) -
|
|
41
|
+
`ResourceState`, `ObservableResourceConfig`, `data`, `error`, `state`,
|
|
42
|
+
`loading`, `fetch`, `refetch`, `mutate`, `into`, `apply`, `destroy`,
|
|
43
|
+
`isReady`, `isPending`, `isRefreshing`, `isErrored`, `isUnresolved`,
|
|
44
|
+
`onSuccess`, `onError`
|
|
45
|
+
- `ObservableStatic` type definitions - `resource()`, `setLocale()`,
|
|
46
|
+
`useValueProperty()`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
14
50
|
## [1.0.172] - 2026-06-15
|
|
15
51
|
|
|
16
52
|
### Added
|
|
17
53
|
|
|
18
|
-
- `Text(string)`
|
|
19
|
-
- `String.prototype.toNdChildren()`
|
|
20
|
-
- `ObservableItem.prototype.toString()`
|
|
21
|
-
- `NDElement.prototype.className(classes)`
|
|
22
|
-
- `NDElement.prototype.class(className, value)`
|
|
23
|
-
- `__$isNativeNode` flag on DOM prototypes (`Element`, `Text`, `Comment`, `Document`, `DocumentFragment`)
|
|
24
|
-
- `__$isValidNdChild` flag on all valid NdChild prototypes
|
|
25
|
-
- `defineToNdElement`, `defineValidNdChild`, `defineNativeNode`
|
|
26
|
-
- camelCase variants added to `BOOLEAN_ATTRIBUTES`
|
|
54
|
+
- `Text(string)` - shorthand for `string.toNdChildren()`, parses Observable placeholders in template strings
|
|
55
|
+
- `String.prototype.toNdChildren()` - splits a string containing `{{obs:N}}` placeholders into a mixed array of strings and resolved Observables
|
|
56
|
+
- `ObservableItem.prototype.toString()` - auto-registers the Observable in MemoryManager and returns `{{obs:id}}`, enabling reactive interpolation via standard template literals
|
|
57
|
+
- `NDElement.prototype.className(classes)` - adds static classes, bypasses AttributesWrapper
|
|
58
|
+
- `NDElement.prototype.class(className, value)` - adds a reactive class binding, bypasses AttributesWrapper
|
|
59
|
+
- `__$isNativeNode` flag on DOM prototypes (`Element`, `Text`, `Comment`, `Document`, `DocumentFragment`) - faster alternative to `instanceof Node`
|
|
60
|
+
- `__$isValidNdChild` flag on all valid NdChild prototypes - single property check replaces multiple type guards
|
|
61
|
+
- `defineToNdElement`, `defineValidNdChild`, `defineNativeNode` - non-enumerable prototype helpers in `nd-element-extensions`
|
|
62
|
+
- camelCase variants added to `BOOLEAN_ATTRIBUTES` - removes `toLowerCase()` call on each attribute in the hot path
|
|
27
63
|
|
|
28
64
|
### Changed
|
|
29
65
|
|
|
30
|
-
- `ElementCreator.processChildren` and `getChild`
|
|
31
|
-
- `nd-element-extensions`
|
|
32
|
-
- `AttributesWrapper`
|
|
33
|
-
- NDElement events
|
|
34
|
-
- `_prevent`, `_stop`, `_preventStop`
|
|
66
|
+
- `ElementCreator.processChildren` and `getChild` - refactored for better performance
|
|
67
|
+
- `nd-element-extensions` - all prototype extensions converted to non-enumerable `defineProperty`
|
|
68
|
+
- `AttributesWrapper` - `options` defaults to `null` instead of `{}`, fixes truthy check issue
|
|
69
|
+
- NDElement events - inline handler (`on*`) used when no existing handler and no options; falls back to `addEventListener` for subsequent handlers or when options are provided
|
|
70
|
+
- `_prevent`, `_stop`, `_preventStop` - updated with same inline handler strategy
|
|
35
71
|
|
|
36
72
|
---
|
|
37
73
|
|
|
@@ -39,34 +75,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
39
75
|
|
|
40
76
|
### Added
|
|
41
77
|
|
|
42
|
-
- Vitest test suite
|
|
43
|
-
- 372 tests passing across 13 files
|
|
78
|
+
- Vitest test suite - `vitest.config.js`, happy-dom environment, scripts `test`, `test:watch`, `test:ui`, `test:coverage`
|
|
79
|
+
- 372 tests passing across 13 files - 0 failures, 0 todo
|
|
44
80
|
|
|
45
|
-
#### Unit tests
|
|
46
|
-
- `observable-item`
|
|
47
|
-
- `observable-array`
|
|
48
|
-
- `observable-object`
|
|
49
|
-
- `observable-resource`
|
|
50
|
-
- `store`
|
|
81
|
+
#### Unit tests - core
|
|
82
|
+
- `observable-item` - 48 cases: set, val, subscribe, on/off, once, toggle, reset, transform, equals, clone, cleanup, intercept, assocTrigger
|
|
83
|
+
- `observable-array` - 48 cases: push, remove, clear, merge, swap, insertAfter, isIncludes, where, sync, clone, reset, deepSubscribe
|
|
84
|
+
- `observable-object` - 30 cases: constructor, val, get, set, subscribe, keys, observables, reset, clone
|
|
85
|
+
- `observable-resource` - 29 cases: states, fetch, refetch, mutate, abort, race condition, deps, onSuccess, onError, into, apply, destroy
|
|
86
|
+
- `store` - 43 cases: create, createResettable, createComposed, use, follow, reset, delete, group, protected, createPersistent
|
|
51
87
|
|
|
52
|
-
#### Unit tests
|
|
53
|
-
- `route-matching`
|
|
54
|
-
- `router-guards`
|
|
88
|
+
#### Unit tests - router
|
|
89
|
+
- `route-matching` - 26 cases: exact path, params, typed params, custom validators, url(), metadata
|
|
90
|
+
- `router-guards` - 14 cases: middleware execution, group middlewares, subscribe, resolve, currentState
|
|
55
91
|
|
|
56
|
-
#### Unit tests
|
|
57
|
-
- `cache`
|
|
58
|
-
- `filters`
|
|
92
|
+
#### Unit tests - utils
|
|
93
|
+
- `cache` - 12 cases: singleton, once, memoize
|
|
94
|
+
- `filters` - 60 cases: equals, comparison, inArray, isEmpty, match, and/or/not, custom, string filters, date/time filters
|
|
59
95
|
|
|
60
|
-
#### Integration tests
|
|
61
|
-
- `attributes-wrapper`
|
|
62
|
-
- `show-if`
|
|
63
|
-
- `for-each-array`
|
|
64
|
-
- `lifecycle`
|
|
96
|
+
#### Integration tests - UI
|
|
97
|
+
- `attributes-wrapper` - 22 cases: string, class map, style, Observable, boolean, aria attributes
|
|
98
|
+
- `show-if` - 16 cases: static boolean, observable, factory child, HideIf, HideIfNot
|
|
99
|
+
- `for-each-array` - 13 cases: static array, push, removeItem, clear, set, swap, index observable, order preservation
|
|
100
|
+
- `lifecycle` - 11 cases: mounted, unmounted, destroy, destroyOnUnmount
|
|
65
101
|
|
|
66
102
|
### Fixed
|
|
67
103
|
|
|
68
|
-
- `NDElement.prototype.destroy()`
|
|
69
|
-
- `NDElement.prototype.destroy()`
|
|
104
|
+
- `NDElement.prototype.destroy()` - guard against double call when `$element` is already null
|
|
105
|
+
- `NDElement.prototype.destroy()` - disconnect lifecycle observer to prevent callbacks from firing after destroy
|
|
70
106
|
|
|
71
107
|
---
|
|
72
108
|
|
|
@@ -98,7 +134,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
98
134
|
|
|
99
135
|
#### UI (renders)
|
|
100
136
|
- `CardRender` + `card.css` - Card renderer with 5 build slots and `layout(slots)` support
|
|
101
|
-
- `ListRender` + `list.css` - `ForEachArray($desc.items)`
|
|
137
|
+
- `ListRender` + `list.css` - `ForEachArray($desc.items)` - items self-render
|
|
102
138
|
- `ListItemRender` + `list-item.css` - reactive checkbox, select indicator, swipe gesture with pointer events, auto-close and stopPropagation
|
|
103
139
|
- `ListGroupRender` + `list-group.css` - collapsible group with reactive chevron via `Switch`
|
|
104
140
|
- `ListDividerRender` + `list-divider.css`
|
|
@@ -240,7 +276,8 @@ Entries use the following categories:
|
|
|
240
276
|
|
|
241
277
|
---
|
|
242
278
|
|
|
243
|
-
[Unreleased]: https://github.com/afrocodeur/native-document/compare/v1.0.
|
|
279
|
+
[Unreleased]: https://github.com/afrocodeur/native-document/compare/v1.0.173...HEAD
|
|
280
|
+
[1.0.173]: https://github.com/afrocodeur/native-document/compare/v1.0.172...v1.0.173
|
|
244
281
|
[1.0.172]: https://github.com/afrocodeur/native-document/compare/v1.0.170...v1.0.172
|
|
245
282
|
[1.0.170]: https://github.com/afrocodeur/native-document/compare/v1.0.169...v1.0.170
|
|
246
283
|
[1.0.169]: https://github.com/afrocodeur/native-document/compare/v1.0.167...v1.0.169
|
package/index.d.ts
CHANGED
|
@@ -18,4 +18,4 @@ export declare const Observable: ObservableStatic;
|
|
|
18
18
|
export declare const $: ObservableStatic;
|
|
19
19
|
export declare const obs: ObservableStatic;
|
|
20
20
|
export declare const Store: StoreStatic;
|
|
21
|
-
export declare const Router: RouterStatic;
|
|
21
|
+
// export declare const Router: RouterStatic;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-document",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.174",
|
|
4
4
|
"description": "A reactive JavaScript framework that preserves native DOM simplicity without sacrificing modern features",
|
|
5
5
|
"author": "AfroCodeur <https://github.com/afrocodeur>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,10 +18,10 @@ import {$getFromStorage, $saveToStorage} from '../utils/localstorage';
|
|
|
18
18
|
* @param {boolean} [configs.propagation] - Controls whether changes propagate to parent observables
|
|
19
19
|
* @example
|
|
20
20
|
* const count = new ObservableItem(0);
|
|
21
|
-
* const name
|
|
21
|
+
* const name = new ObservableItem('John', { reset: true });
|
|
22
22
|
*/
|
|
23
23
|
export default function ObservableItem(value, configs = null) {
|
|
24
|
-
value =
|
|
24
|
+
value = value?.__$Observable ? value.val() : value;
|
|
25
25
|
|
|
26
26
|
this.$previousValue = null;
|
|
27
27
|
this.$currentValue = value;
|
|
@@ -33,9 +33,9 @@ export default function ObservableItem(value, configs = null) {
|
|
|
33
33
|
this.$listeners = null;
|
|
34
34
|
this.$watchers = null;
|
|
35
35
|
|
|
36
|
-
this.$
|
|
36
|
+
this.$id = null;
|
|
37
37
|
|
|
38
|
-
if(configs) {
|
|
38
|
+
if(configs !== null) {
|
|
39
39
|
this.configs = configs;
|
|
40
40
|
if(configs.reset) {
|
|
41
41
|
this.$initialValue = Validator.isObject(value) ? deepClone(value) : value;
|
|
@@ -308,7 +308,7 @@ ObservableItem.prototype.cleanup = function() {
|
|
|
308
308
|
}
|
|
309
309
|
this.$cleanupListeners = null;
|
|
310
310
|
}
|
|
311
|
-
MemoryManager.unregister(this.$
|
|
311
|
+
MemoryManager.unregister(this.$id);
|
|
312
312
|
this.disconnectAll();
|
|
313
313
|
if(process.env.NODE_ENV === 'development') {
|
|
314
314
|
this.$isCleanedUp = true;
|
|
@@ -198,14 +198,13 @@ ObservableObject.prototype.$updateWith = ObservableObject.prototype.set;
|
|
|
198
198
|
|
|
199
199
|
/**
|
|
200
200
|
* Returns an array of all internal observable instances (one per property).
|
|
201
|
-
* Alias: $observables()
|
|
202
201
|
*
|
|
203
202
|
* @returns {ObservableItem[]} Array of observable instances
|
|
204
203
|
*/
|
|
205
204
|
ObservableObject.prototype.observables = function() {
|
|
206
205
|
return Object.values(this.$observables);
|
|
207
206
|
};
|
|
208
|
-
ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
|
|
207
|
+
// ObservableObject.prototype.$observables = ObservableObject.prototype.observables;
|
|
209
208
|
|
|
210
209
|
/**
|
|
211
210
|
* Returns all property names of the observable object.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import Anchor from '../elements/anchor/anchor';
|
|
2
2
|
import {ElementCreator} from './ElementCreator';
|
|
3
|
-
import {normalizeComponentArgs} from '../utils/args-types';
|
|
4
3
|
import './NdPrototype';
|
|
5
4
|
|
|
6
5
|
import '../../core/utils/prototypes.js';
|
|
@@ -19,7 +18,7 @@ import '../wrappers/prototypes/attributes-extensions';
|
|
|
19
18
|
* @returns {Text} Text node, reactive if value is an ObservableItem
|
|
20
19
|
*/
|
|
21
20
|
export const createTextNode = (value) => {
|
|
22
|
-
if(value) {
|
|
21
|
+
if(value != null) {
|
|
23
22
|
return value.toNdElement();
|
|
24
23
|
}
|
|
25
24
|
return ElementCreator.createTextNode();
|
|
@@ -58,11 +57,12 @@ export const createHtmlElement = (element, _attributes, _children = null) => {
|
|
|
58
57
|
attributes = _children;
|
|
59
58
|
children = _attributes;
|
|
60
59
|
}
|
|
60
|
+
element = element.cloneNode();
|
|
61
61
|
|
|
62
|
-
if(attributes) {
|
|
62
|
+
if(attributes != null) {
|
|
63
63
|
ElementCreator.processAttributes(element, attributes);
|
|
64
64
|
}
|
|
65
|
-
if(children) {
|
|
65
|
+
if(children != null) {
|
|
66
66
|
ElementCreator.processChildren(children, element);
|
|
67
67
|
}
|
|
68
68
|
return element;
|
|
@@ -75,6 +75,7 @@ export const createHtmlElement = (element, _attributes, _children = null) => {
|
|
|
75
75
|
*
|
|
76
76
|
* @param {string} name - HTML tag name (e.g. 'div', 'button', 'input'). Pass empty string to create a Fragment.
|
|
77
77
|
* @param {((element: HTMLElement) => HTMLElement)|null} [customWrapper=null] - Optional function to augment the element before returning
|
|
78
|
+
* @param {boolean} [isVoid=false] - If true, the element is a void element (no children allowed, e.g. input, img)
|
|
78
79
|
* @returns {(attr?: Object, children?: ValidChild) => HTMLElement} Element factory function
|
|
79
80
|
* @example
|
|
80
81
|
* const Div = HtmlElementWrapper('div');
|
|
@@ -89,14 +90,12 @@ export const createHtmlElement = (element, _attributes, _children = null) => {
|
|
|
89
90
|
export default function HtmlElementWrapper(name, customWrapper = null, isVoid = false) {
|
|
90
91
|
const elementCreator = isVoid ? createVoidHtmlElement : createHtmlElement;
|
|
91
92
|
if(name) {
|
|
92
|
-
if(customWrapper) {
|
|
93
|
+
if(customWrapper == null) {
|
|
93
94
|
let node = null;
|
|
94
95
|
let createElement = (attr, children) => {
|
|
95
96
|
node = document.createElement(name);
|
|
96
|
-
createElement = (
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
return elementCreator(customWrapper(node.cloneNode()), attr, children);;
|
|
97
|
+
createElement = elementCreator.bind(null, node);
|
|
98
|
+
return elementCreator(node.cloneNode(), attr, children);
|
|
100
99
|
};
|
|
101
100
|
|
|
102
101
|
return (attr, children) => createElement(attr, children);
|
|
@@ -106,9 +105,9 @@ export default function HtmlElementWrapper(name, customWrapper = null, isVoid =
|
|
|
106
105
|
let createElement = (attr, children) => {
|
|
107
106
|
node = document.createElement(name);
|
|
108
107
|
createElement = (attr, children) => {
|
|
109
|
-
return elementCreator(node.cloneNode(), attr, children);
|
|
108
|
+
return elementCreator(customWrapper(node.cloneNode()), attr, children);
|
|
110
109
|
};
|
|
111
|
-
return elementCreator(node.cloneNode(), attr, children)
|
|
110
|
+
return elementCreator(customWrapper(node.cloneNode()), attr, children);;
|
|
112
111
|
};
|
|
113
112
|
|
|
114
113
|
return (attr, children) => createElement(attr, children);
|
|
@@ -26,7 +26,8 @@ Object.defineProperty(NDElement.prototype, 'nd', {
|
|
|
26
26
|
EVENTS.forEach(eventSourceName => {
|
|
27
27
|
const eventName = eventSourceName.toLowerCase();
|
|
28
28
|
const inlineHandler = 'on'+eventName;
|
|
29
|
-
|
|
29
|
+
const fnName = 'on'+eventSourceName;
|
|
30
|
+
NDElement.prototype[fnName] = function(callback = null, options = null) {
|
|
30
31
|
if(!this.$element[inlineHandler] && !options) {
|
|
31
32
|
this.$element[inlineHandler] = callback;
|
|
32
33
|
return this;
|
|
@@ -34,26 +35,58 @@ EVENTS.forEach(eventSourceName => {
|
|
|
34
35
|
this.$element.addEventListener(eventName, callback, options);
|
|
35
36
|
return this;
|
|
36
37
|
};
|
|
38
|
+
if(!HTMLElement.prototype[fnName]) {
|
|
39
|
+
HTMLElement.prototype[fnName] = function(callback, options) {
|
|
40
|
+
if(!this[inlineHandler] && !options) {
|
|
41
|
+
this[inlineHandler] = callback;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
this.addEventListener(eventName, callback, options);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
37
48
|
});
|
|
38
49
|
|
|
39
50
|
EVENTS_WITH_STOP.forEach(eventSourceName => {
|
|
40
51
|
const eventName = eventSourceName.toLowerCase();
|
|
41
|
-
|
|
52
|
+
const stopFnName = 'onStop'+eventSourceName;
|
|
53
|
+
const preventStopFnName = 'onPreventStop'+eventSourceName;
|
|
54
|
+
|
|
55
|
+
NDElement.prototype[stopFnName] = function(callback = null, options = null) {
|
|
42
56
|
_stop(this.$element, eventName, callback, options);
|
|
43
57
|
return this;
|
|
44
58
|
};
|
|
45
|
-
NDElement.prototype[
|
|
59
|
+
NDElement.prototype[preventStopFnName] = function(callback = null, options = null) {
|
|
46
60
|
_preventStop(this.$element, eventName, callback, options);
|
|
47
61
|
return this;
|
|
48
62
|
};
|
|
63
|
+
if(HTMLElement.prototype[stopFnName]) {
|
|
64
|
+
HTMLElement.prototype[stopFnName] = function(callback = null, options = null) {
|
|
65
|
+
_stop(this, eventName, callback, options);
|
|
66
|
+
return this;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if(!HTMLElement.prototype[preventStopFnName]) {
|
|
70
|
+
HTMLElement.prototype[preventStopFnName] = function(callback = null, options = null) {
|
|
71
|
+
_preventStop(this, eventName, callback, options);
|
|
72
|
+
return this;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
49
75
|
});
|
|
50
76
|
|
|
51
77
|
EVENTS_WITH_PREVENT.forEach(eventSourceName => {
|
|
52
78
|
const eventName = eventSourceName.toLowerCase();
|
|
53
|
-
|
|
79
|
+
const preventFnName = 'onPrevent'+eventSourceName;
|
|
80
|
+
NDElement.prototype[preventFnName] = function(callback = null, options = null) {
|
|
54
81
|
_prevent(this.$element, eventName, callback, options);
|
|
55
82
|
return this;
|
|
56
83
|
};
|
|
84
|
+
if(HTMLElement.prototype[preventFnName]) {
|
|
85
|
+
HTMLElement.prototype[preventFnName] = function(callback = null, options = null) {
|
|
86
|
+
_prevent(this, eventName, callback, options);
|
|
87
|
+
return this;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
57
90
|
});
|
|
58
91
|
|
|
59
92
|
|
|
@@ -17,8 +17,12 @@ export default function NodeCloner($element) {
|
|
|
17
17
|
this.$styles = null;
|
|
18
18
|
this.$attrs = null;
|
|
19
19
|
this.$ndMethods = null;
|
|
20
|
+
this.$content = null;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
NodeCloner.prototype.shouldBeHydrate = function() {
|
|
24
|
+
return this.$attrs !== null || this.$classes !== null || this.$styles !== null || this.$ndMethods !== null || this.$content !== null;
|
|
25
|
+
};
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
28
|
* Attaches a template binding to the element by hydrating it with the specified method.
|
|
@@ -171,16 +175,16 @@ NodeCloner.prototype.attach = function(methodName, callback) {
|
|
|
171
175
|
* Registers a reactive text content binding for the element.
|
|
172
176
|
*
|
|
173
177
|
* @internal
|
|
174
|
-
* @param {Function}
|
|
178
|
+
* @param {Function} valueOrProperty - Function receiving data and returning the text content
|
|
175
179
|
* @returns {NodeCloner} this
|
|
176
180
|
*/
|
|
177
|
-
NodeCloner.prototype.text = function(
|
|
178
|
-
this.$content =
|
|
179
|
-
if(typeof
|
|
180
|
-
this.cloneNode = (data) => createTextNode(
|
|
181
|
+
NodeCloner.prototype.text = function(valueOrProperty) {
|
|
182
|
+
this.$content = valueOrProperty;
|
|
183
|
+
if(typeof valueOrProperty === 'function') {
|
|
184
|
+
this.cloneNode = (data) => createTextNode(valueOrProperty.apply(null, data));
|
|
181
185
|
return this;
|
|
182
186
|
}
|
|
183
|
-
this.cloneNode = (data) => createTextNode(data[0][
|
|
187
|
+
this.cloneNode = (data) => createTextNode(data[0][valueOrProperty]);
|
|
184
188
|
return this;
|
|
185
189
|
};
|
|
186
190
|
|
|
@@ -24,11 +24,11 @@ export function TemplateCloner($fn) {
|
|
|
24
24
|
|
|
25
25
|
const assignClonerToNode = ($node) => {
|
|
26
26
|
const childNodes = $node.childNodes;
|
|
27
|
-
let containDynamicNode =
|
|
27
|
+
let containDynamicNode = $node.nodeCloner?.shouldBeHydrate();
|
|
28
28
|
const childNodesLength = childNodes.length;
|
|
29
29
|
for(let i = 0; i < childNodesLength; i++) {
|
|
30
30
|
const child = childNodes[i];
|
|
31
|
-
if(child.nodeCloner) {
|
|
31
|
+
if(child.nodeCloner && child.nodeCloner.shouldBeHydrate()) {
|
|
32
32
|
containDynamicNode = true;
|
|
33
33
|
}
|
|
34
34
|
const localContainDynamicNode = assignClonerToNode(child);
|
|
@@ -179,9 +179,12 @@ export function useCache(fn) {
|
|
|
179
179
|
return node;
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
+
if(fn.length === 0) {
|
|
183
|
+
return () => wrapper();
|
|
184
|
+
}
|
|
182
185
|
if(fn.length < 2) {
|
|
183
|
-
return (
|
|
184
|
-
return wrapper(
|
|
186
|
+
return (arg) => {
|
|
187
|
+
return wrapper([arg]);
|
|
185
188
|
};
|
|
186
189
|
}
|
|
187
190
|
return (_, __, ...args) => {
|
package/src/fetch/NativeFetch.js
CHANGED
|
@@ -110,7 +110,7 @@ const buildErrors = ($desc) => {
|
|
|
110
110
|
return ShowIf($desc.hasErrors,
|
|
111
111
|
() => Div({class: 'field-errors', ...($desc.elementsProps.error || {})},
|
|
112
112
|
ForEachArray($desc.errors, (error) =>
|
|
113
|
-
Span({class: 'field-error'}, error),
|
|
113
|
+
Span({class: 'field-error'}, (typeof error === 'string' ? error.toNdChildren() : error)),
|
|
114
114
|
),
|
|
115
115
|
),
|
|
116
116
|
);
|
package/types/native-fetch.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
// import { ObservableResourceConfig, ObservableResource } from './observable';
|
|
2
2
|
|
|
3
|
-
export type { ObservableResource, ObservableResourceConfig };
|
|
3
|
+
// export type { ObservableResource, ObservableResourceConfig };
|
package/types/observable.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {FilterResult, PredicateMap} from
|
|
1
|
+
import { FilterResult, PredicateMap } from './filters';
|
|
2
2
|
|
|
3
3
|
export type Unsubscribe = () => void;
|
|
4
4
|
|
|
@@ -41,45 +41,96 @@ export interface FormattersStatic {
|
|
|
41
41
|
|
|
42
42
|
export declare const Formatters: FormattersStatic;
|
|
43
43
|
|
|
44
|
+
// -- Observable system type definitions ---------------------------------------
|
|
44
45
|
|
|
45
|
-
// Observable system type definitions
|
|
46
46
|
export interface ObservableItem<T = any> {
|
|
47
47
|
readonly $currentValue: T;
|
|
48
48
|
readonly $previousValue: T;
|
|
49
49
|
readonly $initialValue: T | null;
|
|
50
50
|
readonly $isCleanedUp: boolean;
|
|
51
|
+
readonly $memoryId: number | null;
|
|
52
|
+
readonly __$Observable: true;
|
|
53
|
+
readonly __$isObservable: true;
|
|
51
54
|
|
|
52
55
|
$value: T;
|
|
53
|
-
$memoryId: number | null;
|
|
54
56
|
|
|
55
57
|
val(): T;
|
|
56
58
|
set(value: T | ((prev: T) => T)): void;
|
|
57
59
|
trigger(operations?: ObservableOperation): void;
|
|
58
60
|
cleanup(): void;
|
|
61
|
+
clone(): ObservableItem<T>;
|
|
62
|
+
disconnectAll(): void;
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Registers a subscriber. Does NOT return an unsubscribe function —
|
|
66
|
+
* call .unsubscribe(callback) with the same reference to remove it.
|
|
67
|
+
*/
|
|
68
|
+
subscribe(callback: (current: T, previous: T, operations?: ObservableOperation) => void): void;
|
|
61
69
|
unsubscribe(callback: Function): void;
|
|
62
|
-
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Registers a watcher for a specific value. Does NOT return an unsubscribe
|
|
73
|
+
* function — call .off(value, callback) to remove it.
|
|
74
|
+
*/
|
|
75
|
+
on(value: T, callback: ObservableItem<boolean> | ((isActive: boolean) => void)): void;
|
|
76
|
+
off(value: T, callback?: Function): void;
|
|
77
|
+
once(predicate: T | ((value: T) => boolean), callback: (value: T) => void): void;
|
|
78
|
+
onCleanup(callback: () => void): void;
|
|
79
|
+
|
|
80
|
+
intercept(callback: (newValue: T, currentValue: T) => T | undefined): this;
|
|
81
|
+
interceptMutations(callback: (operation: ObservableOperation) => void): this;
|
|
63
82
|
|
|
64
83
|
check<U>(callback: (value: T) => U): ObservableChecker<U>;
|
|
65
84
|
transform<U>(callback: (value: T) => U): ObservableChecker<U>;
|
|
66
85
|
is<U>(callback: (value: T) => U): ObservableChecker<U>;
|
|
67
86
|
select<U>(callback: (value: T) => U): ObservableChecker<U>;
|
|
68
|
-
pluck<U>(
|
|
87
|
+
pluck<U>(key: string): ObservableChecker<U>;
|
|
69
88
|
format(type: FormatType | ((value: T) => string), options?: FormatOptions): ObservableItem<string>;
|
|
70
89
|
get(key: string | number): any;
|
|
71
90
|
when(value: T): ObservableWhen<T>;
|
|
72
|
-
|
|
73
|
-
once(predicate: T | ((value: T) => boolean), callback: (value: T) => void): void;
|
|
74
|
-
onCleanup(callback: () => void): void;
|
|
75
|
-
intercept(callback: (newValue: T, currentValue: T) => T | undefined): this;
|
|
91
|
+
|
|
76
92
|
persist(key: string, options?: {
|
|
77
93
|
get?: (value: any) => T;
|
|
78
94
|
set?: (value: T) => any;
|
|
79
95
|
}): this;
|
|
80
|
-
|
|
96
|
+
|
|
97
|
+
// -- comparison helpers (is*) -----------------------------------------------
|
|
98
|
+
|
|
99
|
+
isEqualTo(value: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
100
|
+
isNotEqualTo(value: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
101
|
+
isGreaterThan(value: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
102
|
+
isGreaterThanOrEqualTo(value: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
103
|
+
isLessThan(value: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
104
|
+
isLessThanOrEqualTo(value: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
105
|
+
isBetween(min: T | ObservableItem<T>, max: T | ObservableItem<T>): ObservableChecker<boolean>;
|
|
106
|
+
isNull(): ObservableChecker<boolean>;
|
|
107
|
+
isTruthy(): ObservableChecker<boolean>;
|
|
108
|
+
isFalsy(): ObservableChecker<boolean>;
|
|
109
|
+
isStartingWith(value: string | ObservableItem<string>): ObservableChecker<boolean>;
|
|
110
|
+
isEndingWith(value: string | ObservableItem<string>): ObservableChecker<boolean>;
|
|
111
|
+
isMatchingPattern(pattern: RegExp | ObservableItem<RegExp>): ObservableChecker<boolean>;
|
|
112
|
+
isEmpty(): ObservableChecker<boolean>;
|
|
113
|
+
isNotEmpty(): ObservableChecker<boolean>;
|
|
114
|
+
isIncludes(value: any | ObservableItem<any>): ObservableChecker<boolean>;
|
|
115
|
+
isIncludedIn(collection: any[] | ObservableItem<any[]>): ObservableChecker<boolean>;
|
|
116
|
+
isOneOf(collection: any[] | ObservableItem<any[]>): ObservableChecker<boolean>;
|
|
117
|
+
isHaving(key: string | ObservableItem<string>): ObservableChecker<boolean>;
|
|
118
|
+
|
|
119
|
+
// -- transform helpers (to*) -------------------------------------------------
|
|
120
|
+
|
|
121
|
+
toUpperCase(): ObservableChecker<string>;
|
|
122
|
+
toLowerCase(): ObservableChecker<string>;
|
|
123
|
+
toTrimmed(): ObservableChecker<string>;
|
|
124
|
+
toBoolean(): ObservableChecker<boolean>;
|
|
125
|
+
toLiteral(template: string, placeholder?: string): ObservableChecker<string>;
|
|
126
|
+
toFormatted(template: string, placeholder?: string): ObservableChecker<string>;
|
|
127
|
+
toProperty<U = any>(path: string): ObservableChecker<U>;
|
|
128
|
+
toLength(): ObservableChecker<number>;
|
|
129
|
+
toClamped(min: number | ObservableItem<number>, max: number | ObservableItem<number>): ObservableChecker<number>;
|
|
130
|
+
toPercent(total: number | ObservableItem<number>): ObservableChecker<number>;
|
|
81
131
|
|
|
82
132
|
toString(): string;
|
|
133
|
+
valueOf(): T;
|
|
83
134
|
equals(value: any): boolean;
|
|
84
135
|
toBool(): boolean;
|
|
85
136
|
toggle(): void;
|
|
@@ -93,7 +144,8 @@ export class ObservableWhen<T = any> {
|
|
|
93
144
|
|
|
94
145
|
constructor(observer: ObservableItem<T>, value: T);
|
|
95
146
|
|
|
96
|
-
|
|
147
|
+
/** Does NOT return an unsubscribe function — use .off() on the underlying observer. */
|
|
148
|
+
subscribe(callback: (value: boolean) => void): void;
|
|
97
149
|
val(): boolean;
|
|
98
150
|
isMatch(): boolean;
|
|
99
151
|
isActive(): boolean;
|
|
@@ -106,11 +158,14 @@ export interface ObservableOperation {
|
|
|
106
158
|
}
|
|
107
159
|
|
|
108
160
|
export interface ObservableChecker<T = any> {
|
|
161
|
+
readonly __$Observable: true;
|
|
109
162
|
readonly __$isObservableChecker: true;
|
|
110
163
|
|
|
111
|
-
|
|
164
|
+
/** Does NOT return an unsubscribe function. */
|
|
165
|
+
subscribe(callback: (value: T) => void): void;
|
|
112
166
|
check<U>(callback: (value: T) => U): ObservableChecker<U>;
|
|
113
167
|
val(): T;
|
|
168
|
+
toNdElement(): Text;
|
|
114
169
|
|
|
115
170
|
set(value: any): void;
|
|
116
171
|
trigger(): void;
|
|
@@ -119,6 +174,7 @@ export interface ObservableChecker<T = any> {
|
|
|
119
174
|
|
|
120
175
|
export interface ObservableArray<T> extends ObservableItem<T[]> {
|
|
121
176
|
readonly length: number;
|
|
177
|
+
readonly __$isObservableArray: true;
|
|
122
178
|
|
|
123
179
|
push(...items: T[]): number;
|
|
124
180
|
pop(): T | undefined;
|
|
@@ -128,16 +184,19 @@ export interface ObservableArray<T> extends ObservableItem<T[]> {
|
|
|
128
184
|
sort(compareFn?: (a: T, b: T) => number): T[];
|
|
129
185
|
splice(start: number, deleteCount?: number, ...items: T[]): T[];
|
|
130
186
|
|
|
131
|
-
|
|
187
|
+
empty(): boolean;
|
|
132
188
|
clear(): boolean;
|
|
133
189
|
merge(values: T[]): void;
|
|
134
190
|
removeItem(item: T): T[];
|
|
135
191
|
remove(index: number): T[];
|
|
136
192
|
swap(indexA: number, indexB: number): boolean;
|
|
137
|
-
|
|
138
|
-
|
|
193
|
+
swapItems(itemA: T, itemB: T): boolean;
|
|
194
|
+
insertAfter(item: T, afterItem: T): T[];
|
|
195
|
+
count(condition: (item: T, index?: number) => boolean): number;
|
|
196
|
+
populateAndRender(items: T[], factory: any, options: any): void;
|
|
139
197
|
|
|
140
198
|
map<U>(callback: (value: T, index: number, array: T[]) => U): U[];
|
|
199
|
+
forEach(callback: (value: T, index: number, array: T[]) => void): void;
|
|
141
200
|
filter(callback: (value: T, index: number, array: T[]) => boolean): T[];
|
|
142
201
|
reduce<U>(callback: (acc: U, value: T, index: number, array: T[]) => U, initial: U): U;
|
|
143
202
|
some(callback: (value: T, index: number, array: T[]) => boolean): boolean;
|
|
@@ -146,12 +205,21 @@ export interface ObservableArray<T> extends ObservableItem<T[]> {
|
|
|
146
205
|
at(index: number): T | undefined;
|
|
147
206
|
findIndex(callback: (value: T, index: number, array: T[]) => boolean): number;
|
|
148
207
|
concat(...items: (T | T[])[]): T[];
|
|
208
|
+
indexOf(item: T): number;
|
|
209
|
+
includes(item: T): boolean;
|
|
149
210
|
|
|
150
|
-
where(predicates: PredicateMap<T>): ObservableArray<T>;
|
|
211
|
+
where(predicates: PredicateMap<T> | ((item: T) => boolean)): ObservableArray<T>;
|
|
151
212
|
whereSome<K extends keyof T>(fields: K[], filter: FilterResult<T[K]>): ObservableArray<T>;
|
|
152
213
|
whereEvery<K extends keyof T>(fields: K[], filter: FilterResult<T[K]>): ObservableArray<T>;
|
|
153
214
|
|
|
154
|
-
|
|
215
|
+
/** Returns an unsubscribe function that stops all deep observation. */
|
|
216
|
+
deepSubscribe(callback: (value: T[]) => void): Unsubscribe;
|
|
217
|
+
|
|
218
|
+
/** One-way sync — pushes mutations from this array to the target. Returns unsubscribe. */
|
|
219
|
+
sync(target: ObservableArray<T>): Unsubscribe;
|
|
220
|
+
|
|
221
|
+
clone(): ObservableArray<T>;
|
|
222
|
+
isNotEmpty(): ObservableChecker<boolean>;
|
|
155
223
|
}
|
|
156
224
|
|
|
157
225
|
export type ObservableObject<T extends Record<string, any>> = ObservableItem<T> & {
|
|
@@ -175,6 +243,12 @@ export type ObservableObject<T extends Record<string, any>> = ObservableItem<T>
|
|
|
175
243
|
keys(): string[];
|
|
176
244
|
$keys(): string[];
|
|
177
245
|
observables(): ObservableItem<any>[];
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Subscribing to an ObservableObject also deep-subscribes to all nested
|
|
249
|
+
* observables and observable arrays — triggers on any nested change.
|
|
250
|
+
*/
|
|
251
|
+
subscribe(callback: (current: T, previous: T, operations?: ObservableOperation) => void): void;
|
|
178
252
|
} & {
|
|
179
253
|
[K in keyof T]: T[K] extends (infer U)[]
|
|
180
254
|
? ObservableArray<U>
|
|
@@ -183,6 +257,40 @@ export type ObservableObject<T extends Record<string, any>> = ObservableItem<T>
|
|
|
183
257
|
: ObservableItem<T[K]>;
|
|
184
258
|
};
|
|
185
259
|
|
|
260
|
+
// -- ObservableResource ---------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
export type ResourceState = 'unresolved' | 'pending' | 'ready' | 'refreshing' | 'errored';
|
|
263
|
+
|
|
264
|
+
export interface ObservableResourceConfig<T = any> {
|
|
265
|
+
auto?: boolean;
|
|
266
|
+
lazy?: boolean;
|
|
267
|
+
debounce?: number;
|
|
268
|
+
into?: ObservableItem<T>;
|
|
269
|
+
apply?: (result: any, data: ObservableItem<T>) => void;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface ObservableResource<T = any> {
|
|
273
|
+
readonly data: ObservableItem<T>;
|
|
274
|
+
readonly error: ObservableItem<Error | null>;
|
|
275
|
+
readonly state: ObservableItem<ResourceState>;
|
|
276
|
+
readonly loading: ObservableItem<boolean>;
|
|
277
|
+
|
|
278
|
+
fetch(): this;
|
|
279
|
+
refetch(): this;
|
|
280
|
+
mutate(value: T): this;
|
|
281
|
+
into(target: ObservableItem<T>): this;
|
|
282
|
+
apply(callback: (result: any, data: ObservableItem<T>) => void): this;
|
|
283
|
+
destroy(): void;
|
|
284
|
+
|
|
285
|
+
isReady(): ObservableChecker<boolean>;
|
|
286
|
+
isPending(): ObservableChecker<boolean>;
|
|
287
|
+
isRefreshing(): ObservableChecker<boolean>;
|
|
288
|
+
isErrored(): ObservableChecker<boolean>;
|
|
289
|
+
isUnresolved(): ObservableChecker<boolean>;
|
|
290
|
+
|
|
291
|
+
onSuccess(callback: (data: T) => void): this;
|
|
292
|
+
onError(callback: (error: Error) => void): this;
|
|
293
|
+
}
|
|
186
294
|
|
|
187
295
|
export interface BatchFunction<TArgs extends any[] = any[], TReturn = any> {
|
|
188
296
|
(...args: TArgs): TReturn;
|
|
@@ -210,8 +318,14 @@ export interface ObservableStatic {
|
|
|
210
318
|
object<T extends Record<string, any>>(value: T, configs?: ObservableConfig | null): ObservableObject<T>;
|
|
211
319
|
json<T extends Record<string, any>>(value: T, configs?: ObservableConfig | null): ObservableObject<T>;
|
|
212
320
|
|
|
213
|
-
|
|
214
|
-
|
|
321
|
+
resource<T = any>(
|
|
322
|
+
fn: (...args: any[]) => Promise<T>,
|
|
323
|
+
dependencies?: ValidComputedDependencies,
|
|
324
|
+
config?: ObservableResourceConfig<T> | boolean,
|
|
325
|
+
): ObservableResource<T>;
|
|
326
|
+
|
|
327
|
+
computed<T>(callback: (...values: any[]) => T, dependencies?: ValidComputedDependencies | BatchFunction): ObservableItem<T>;
|
|
328
|
+
computed<T>(callback: (...values: any[]) => T, batchFunction?: BatchFunction): ObservableItem<T>;
|
|
215
329
|
|
|
216
330
|
batch<TArgs extends any[], TReturn>(
|
|
217
331
|
callback: (...args: TArgs) => TReturn
|
|
@@ -219,9 +333,11 @@ export interface ObservableStatic {
|
|
|
219
333
|
|
|
220
334
|
value(data: any): any;
|
|
221
335
|
update(target: any, data: any): void;
|
|
336
|
+
setLocale(locale: string | ObservableItem<string>): void;
|
|
337
|
+
useValueProperty(propertyName?: string): void;
|
|
222
338
|
|
|
223
339
|
getById(id: number): ObservableItem | null;
|
|
224
340
|
cleanup(observable: ObservableItem): void;
|
|
225
341
|
autoCleanup(enable?: boolean, options?: AutoCleanupOptions): void;
|
|
226
342
|
arrayOfObject<T extends Record<string, any>>(data: T[]): ObservableObject<T>[];
|
|
227
|
-
}
|
|
343
|
+
}
|
package/types/service.d.ts
CHANGED
package/utils.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import NativeFetch from
|
|
2
|
-
import * as Cache from
|
|
3
|
-
import * as filters from
|
|
4
|
-
import {classPropertyAccumulator, cssPropertyAccumulator} from
|
|
1
|
+
import NativeFetch from './src/fetch/NativeFetch';
|
|
2
|
+
import * as Cache from './src/core/utils/cache';
|
|
3
|
+
import * as filters from './src/core/utils/filters/index';
|
|
4
|
+
import {classPropertyAccumulator, cssPropertyAccumulator} from './src/core/utils/property-accumulator';
|
|
5
|
+
|
|
6
|
+
const Service = Cache;
|
|
5
7
|
|
|
6
8
|
export {
|
|
7
9
|
NativeFetch,
|
|
8
10
|
Cache,
|
|
11
|
+
Service,
|
|
9
12
|
filters,
|
|
10
13
|
classPropertyAccumulator,
|
|
11
14
|
cssPropertyAccumulator
|