@neovici/cosmoz-utils 3.29.0 → 4.0.0-beta.1
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/lib/directives/lazy-until.js +152 -76
- package/lib/directives/measure.js +24 -30
- package/lib/directives/portal.js +47 -55
- package/lib/object.js +1 -1
- package/lib/tag.js +7 -1
- package/package.json +10 -11
- package/lib/directives/ref.js +0 -6
|
@@ -1,87 +1,163 @@
|
|
|
1
|
-
/* eslint-disable max-statements */
|
|
2
1
|
/**
|
|
3
2
|
* @license
|
|
4
|
-
* Copyright
|
|
5
|
-
*
|
|
6
|
-
* http://polymer.github.io/LICENSE.txt
|
|
7
|
-
* The complete set of authors may be found at
|
|
8
|
-
* http://polymer.github.io/AUTHORS.txt
|
|
9
|
-
* The complete set of contributors may be found at
|
|
10
|
-
* http://polymer.github.io/CONTRIBUTORS.txt
|
|
11
|
-
* Code distributed by Google as part of the polymer project is also
|
|
12
|
-
* subject to an additional IP rights grant found at
|
|
13
|
-
* http://polymer.github.io/PATENTS.txt
|
|
3
|
+
* Copyright 2017 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
14
5
|
*/
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
|
|
6
|
+
/* eslint-disable import/group-exports */
|
|
7
|
+
import { noChange } from 'lit-html';
|
|
8
|
+
import { directive } from 'lit-html/directive.js';
|
|
9
|
+
import { isPrimitive } from 'lit-html/directive-helpers.js';
|
|
10
|
+
import { AsyncDirective } from 'lit-html/async-directive.js';
|
|
11
|
+
|
|
12
|
+
class PseudoWeakRef {
|
|
13
|
+
constructor(ref) {
|
|
14
|
+
this._ref = ref;
|
|
15
|
+
}
|
|
16
|
+
disconnect() {
|
|
17
|
+
this._ref = undefined;
|
|
18
|
+
}
|
|
19
|
+
reconnect(ref) {
|
|
20
|
+
this._ref = ref;
|
|
21
|
+
}
|
|
22
|
+
deref() {
|
|
23
|
+
return this._ref;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class Pauser {
|
|
28
|
+
_promise = undefined;
|
|
29
|
+
_resolve = undefined;
|
|
30
|
+
get() {
|
|
31
|
+
return this._promise;
|
|
32
|
+
}
|
|
33
|
+
pause() {
|
|
34
|
+
this._promise ??= new Promise((resolve) => (this._resolve = resolve));
|
|
35
|
+
}
|
|
36
|
+
resume() {
|
|
37
|
+
this._resolve?.();
|
|
38
|
+
this._promise = this._resolve = undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isPromise = (x) => {
|
|
43
|
+
return !isPrimitive(x) && typeof x.then === 'function';
|
|
44
|
+
},
|
|
18
45
|
// Effectively infinity, but a SMI.
|
|
19
|
-
_infinity =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
* The priority of values can be used to create placeholder content for async
|
|
31
|
-
* data. For example, a Promise with pending content can be the first,
|
|
32
|
-
* highest-priority, argument, and a non_promise loading indicator template can
|
|
33
|
-
* be used as the second, lower-priority, argument. The loading indicator will
|
|
34
|
-
* render immediately, and the primary content will render when the Promise
|
|
35
|
-
* resolves.
|
|
36
|
-
*
|
|
37
|
-
* Example:
|
|
38
|
-
*
|
|
39
|
-
* const content = fetch('./content.txt').then(r => r.text());
|
|
40
|
-
* html`${until(content, html`<span>Loading...</span>`)}`
|
|
41
|
-
*/
|
|
42
|
-
export const lazyUntil = directive((...args) => part => {
|
|
43
|
-
let state = _state.get(part);
|
|
44
|
-
if (state === undefined) {
|
|
45
|
-
state = {
|
|
46
|
-
lastRenderedIndex: _infinity,
|
|
47
|
-
values: []
|
|
48
|
-
};
|
|
49
|
-
_state.set(part, state);
|
|
46
|
+
_infinity = 0x3fffffff;
|
|
47
|
+
|
|
48
|
+
export class UntilDirective extends AsyncDirective {
|
|
49
|
+
__lastRenderedIndex = _infinity;
|
|
50
|
+
__values = [];
|
|
51
|
+
__weakThis = new PseudoWeakRef(this);
|
|
52
|
+
__pauser = new Pauser();
|
|
53
|
+
|
|
54
|
+
render(...args) {
|
|
55
|
+
return args.find((x) => !isPromise(x)) ?? noChange;
|
|
50
56
|
}
|
|
51
|
-
const previousValues = state.values,
|
|
52
|
-
previousLength = previousValues.length;
|
|
53
|
-
state.values = args;
|
|
54
|
-
for (let i = 0; i < args.length; i++) {
|
|
55
|
-
// If we've rendered a higher-priority value already, stop.
|
|
56
|
-
if (i > state.lastRenderedIndex) {
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// If
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
// eslint-disable-next-line max-statements
|
|
59
|
+
update(_part, args) {
|
|
60
|
+
const previousValues = this.__values,
|
|
61
|
+
previousLength = previousValues.length;
|
|
62
|
+
this.__values = args;
|
|
63
|
+
|
|
64
|
+
const weakThis = this.__weakThis,
|
|
65
|
+
pauser = this.__pauser;
|
|
66
|
+
|
|
67
|
+
// If our initial render occurs while disconnected, ensure that the pauser
|
|
68
|
+
// and weakThis are in the disconnected state
|
|
69
|
+
if (!this.isConnected) {
|
|
70
|
+
this.disconnected();
|
|
72
71
|
}
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// higher-priority than what's already been rendered.
|
|
79
|
-
if (index > -1 && index <= state.lastRenderedIndex) {
|
|
80
|
-
state.lastRenderedIndex = index;
|
|
81
|
-
part.setValue(resolvedValue);
|
|
82
|
-
part.commit();
|
|
73
|
+
for (let i = 0; i < args.length; i++) {
|
|
74
|
+
// If we've rendered a higher-priority value already, stop.
|
|
75
|
+
if (i > this.__lastRenderedIndex) {
|
|
76
|
+
break;
|
|
83
77
|
}
|
|
84
|
-
|
|
78
|
+
|
|
79
|
+
const value = args[i];
|
|
80
|
+
|
|
81
|
+
// Render non-Promise values immediately
|
|
82
|
+
if (!isPromise(value)) {
|
|
83
|
+
this.__lastRenderedIndex = i;
|
|
84
|
+
// Since a lower-priority value will never overwrite a higher-priority
|
|
85
|
+
// synchronous value, we can stop processing now.
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// If this is a Promise we've already handled, skip it.
|
|
90
|
+
if (i < previousLength && value === previousValues[i]) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Note, the callback avoids closing over `this` so that the directive
|
|
95
|
+
// can be gc'ed before the promise resolves; instead `this` is retrieved
|
|
96
|
+
// from `weakThis`, which can break the hard reference in the closure when
|
|
97
|
+
// the directive disconnects
|
|
98
|
+
Promise.resolve(value).then(async (result) => {
|
|
99
|
+
// If we're disconnected, wait until we're (maybe) reconnected
|
|
100
|
+
// The while loop here handles the case that the connection state
|
|
101
|
+
// thrashes, causing the pauser to resume and then get re-paused
|
|
102
|
+
while (pauser.get()) {
|
|
103
|
+
await pauser.get();
|
|
104
|
+
}
|
|
105
|
+
// If the callback gets here and there is no `this`, it means that the
|
|
106
|
+
// directive has been disconnected and garbage collected and we don't
|
|
107
|
+
// need to do anything else
|
|
108
|
+
const _this = weakThis.deref();
|
|
109
|
+
if (_this !== undefined) {
|
|
110
|
+
const index = _this.__values.indexOf(value);
|
|
111
|
+
// If state.values doesn't contain the value, we've re-rendered without
|
|
112
|
+
// the value, so don't render it. Then, only render if the value is
|
|
113
|
+
// higher-priority than what's already been rendered.
|
|
114
|
+
if (index > -1 && index <= _this.__lastRenderedIndex) {
|
|
115
|
+
_this.__lastRenderedIndex = index;
|
|
116
|
+
_this.setValue(result);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return noChange;
|
|
85
123
|
}
|
|
86
|
-
});
|
|
87
124
|
|
|
125
|
+
disconnected() {
|
|
126
|
+
this.__weakThis.disconnect();
|
|
127
|
+
this.__pauser.pause();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
reconnected() {
|
|
131
|
+
this.__weakThis.reconnect(this);
|
|
132
|
+
this.__pauser.resume();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Renders one of a series of values, including Promises, to a Part.
|
|
138
|
+
*
|
|
139
|
+
* Values are rendered in priority order, with the first argument having the
|
|
140
|
+
* highest priority and the last argument having the lowest priority. If a
|
|
141
|
+
* value is a Promise, low-priority values will be rendered until it resolves.
|
|
142
|
+
*
|
|
143
|
+
* The priority of values can be used to create placeholder content for async
|
|
144
|
+
* data. For example, a Promise with pending content can be the first,
|
|
145
|
+
* highest-priority, argument, and a non_promise loading indicator template can
|
|
146
|
+
* be used as the second, lower-priority, argument. The loading indicator will
|
|
147
|
+
* render immediately, and the primary content will render when the Promise
|
|
148
|
+
* resolves.
|
|
149
|
+
*
|
|
150
|
+
* Example:
|
|
151
|
+
*
|
|
152
|
+
* ```js
|
|
153
|
+
* const content = fetch('./content.txt').then(r => r.text());
|
|
154
|
+
* html`${until(content, html`<span>Loading...</span>`)}`
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export const lazyUntil = directive(UntilDirective);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The type of the class that powers this directive. Necessary for naming the
|
|
161
|
+
* directive's return type.
|
|
162
|
+
*/
|
|
163
|
+
// export type {UntilDirective};
|
|
@@ -1,38 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { nothing } from 'lit-html';
|
|
2
|
+
import { Directive, directive } from 'lit-html/directive.js';
|
|
2
3
|
import { array } from '../array';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
part._resizeObserver = new ResizeObserver(entries => {
|
|
8
|
-
// Ensure that the onMeasure callback is called with *all* selected elements dimensions.
|
|
9
|
-
// NOTE: `entries` can be a subset of the observed elements, i.e. the ones that actually changed dimensions.
|
|
10
|
-
entries.forEach(entry => {
|
|
11
|
-
part._measurements[part._observedElements.indexOf(entry.target)] = entry.contentRect;
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
onMeasure(part._measurements);
|
|
15
|
-
});
|
|
5
|
+
class MeasureDirective extends Directive {
|
|
6
|
+
render() {
|
|
7
|
+
return nothing;
|
|
16
8
|
}
|
|
17
9
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return;
|
|
10
|
+
update(part, [select, onMeasure]) {
|
|
11
|
+
this.measure(part.element, select, onMeasure);
|
|
12
|
+
return nothing;
|
|
21
13
|
}
|
|
22
14
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
15
|
+
measure(element, select, onMeasure) {
|
|
16
|
+
this._observer?.disconnect();
|
|
17
|
+
const elements = array(select(element)),
|
|
18
|
+
observer = (this._observer = new ResizeObserver((entries) => {
|
|
19
|
+
onMeasure(
|
|
20
|
+
entries
|
|
21
|
+
.sort(
|
|
22
|
+
(a, b) => elements.indexOf(a.target) - elements.indexOf(b.target)
|
|
23
|
+
)
|
|
24
|
+
.map((e) => e.contentRect)
|
|
25
|
+
);
|
|
26
|
+
}));
|
|
27
|
+
elements.forEach((el) => observer.observe(el));
|
|
28
|
+
return nothing;
|
|
26
29
|
}
|
|
30
|
+
}
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
part._measurements = [];
|
|
30
|
-
part._select = select;
|
|
31
|
-
|
|
32
|
-
// Postpone the selection until the DOM has been updated.
|
|
33
|
-
requestAnimationFrame(() => {
|
|
34
|
-
// Start observing selected elements.
|
|
35
|
-
part._observedElements = array(part._select(part.committer.element));
|
|
36
|
-
part._observedElements.forEach(element => part._resizeObserver.observe(element));
|
|
37
|
-
});
|
|
38
|
-
});
|
|
32
|
+
export const measure = directive(MeasureDirective);
|
package/lib/directives/portal.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
|
+
import { html, nothing, render } from 'lit-html';
|
|
2
|
+
import { AsyncDirective, directive } from 'lit-html/async-directive.js';
|
|
1
3
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
part._outletPart.clear();
|
|
11
|
-
|
|
12
|
-
const parent = part._outletPart.startNode.parentNode;
|
|
13
|
-
parent.removeChild(part._outletPart.startNode);
|
|
14
|
-
parent.removeChild(part._outletPart.endNode);
|
|
15
|
-
part._outletPart = undefined;
|
|
16
|
-
};
|
|
4
|
+
setChildPartValue,
|
|
5
|
+
clearPart,
|
|
6
|
+
removePart,
|
|
7
|
+
} from 'lit-html/directive-helpers.js';
|
|
8
|
+
const createMarker = () => document.createComment(''),
|
|
9
|
+
ChildPart = render(nothing, new DocumentFragment()).constructor;
|
|
17
10
|
|
|
18
11
|
/**
|
|
19
12
|
* Helper element with a customizable disconnect behavior.
|
|
@@ -23,51 +16,50 @@ class DisconnectObserver extends HTMLElement {
|
|
|
23
16
|
this.onDisconnect();
|
|
24
17
|
}
|
|
25
18
|
}
|
|
26
|
-
|
|
27
19
|
customElements.define('disconnect-observer', DisconnectObserver);
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
21
|
+
class PortalDirective extends AsyncDirective {
|
|
22
|
+
render() {
|
|
23
|
+
return html`<disconnect-observer
|
|
24
|
+
.onDisconnect=${() => {
|
|
25
|
+
this.isConnected = false;
|
|
26
|
+
this.disconnected();
|
|
27
|
+
}}
|
|
28
|
+
></disconnect-observer>`;
|
|
29
|
+
}
|
|
30
|
+
update(part, [content, outlet = document.body]) {
|
|
31
|
+
this.updateOutlet(outlet, content);
|
|
32
|
+
return this.render();
|
|
33
|
+
}
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
35
|
+
updateOutlet(outlet, content) {
|
|
36
|
+
if (this._outlet !== outlet) {
|
|
37
|
+
this.clearOutlet();
|
|
44
38
|
}
|
|
39
|
+
this._outlet = outlet;
|
|
40
|
+
const part = (this._op ??= new ChildPart(
|
|
41
|
+
outlet.appendChild(createMarker()),
|
|
42
|
+
outlet.appendChild(createMarker())
|
|
43
|
+
));
|
|
44
|
+
setChildPartValue(part, (this.content = content));
|
|
45
|
+
}
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
queueMicrotask(() => {
|
|
50
|
-
// Don't create the outletPart if the sourcePart is not connected.
|
|
51
|
-
if (!sourcePart.startNode.isConnected) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Due to use of microtask, we have to make sure the outlet isn't initialized twice.
|
|
56
|
-
if (sourcePart._outletPart) {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
sourcePart._outletPart = new NodePart(sourcePart.options);
|
|
61
|
-
sourcePart._outletPart.appendInto(outlet);
|
|
62
|
-
sourcePart._outletPart.setValue(content);
|
|
63
|
-
sourcePart._outletPart.commit();
|
|
64
|
-
sourcePart._outlet = outlet;
|
|
65
|
-
});
|
|
66
|
-
|
|
47
|
+
clearOutlet() {
|
|
48
|
+
const part = this._op;
|
|
49
|
+
if (!part) {
|
|
67
50
|
return;
|
|
68
51
|
}
|
|
52
|
+
clearPart(part);
|
|
53
|
+
removePart(part);
|
|
54
|
+
this._op = undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
disconnected() {
|
|
58
|
+
this.clearOutlet();
|
|
59
|
+
}
|
|
60
|
+
reconnected() {
|
|
61
|
+
this.updateOutlet(this._outlet, this._content);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
sourcePart._outletPart.setValue(content);
|
|
72
|
-
sourcePart._outletPart.commit();
|
|
73
|
-
});
|
|
65
|
+
export const portal = directive(PortalDirective);
|
package/lib/object.js
CHANGED
|
@@ -3,7 +3,7 @@ import { identity } from './function';
|
|
|
3
3
|
export const prop = key => key && (object => object[key]) || identity,
|
|
4
4
|
strProp = key => {
|
|
5
5
|
const p = prop(key);
|
|
6
|
-
return o => p(o)?.toString() || '';
|
|
6
|
+
return o => typeof o === 'string' ? o : p(o)?.toString() || '';
|
|
7
7
|
},
|
|
8
8
|
isObject = obj => Object.prototype.toString.call(obj) === '[object Object]',
|
|
9
9
|
merge = (...objs) => objs.reduce((acc, obj) => {
|
package/lib/tag.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neovici/cosmoz-utils",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-beta.1",
|
|
4
4
|
"description": "Date, money and template management functions commonly needed in Cosmoz views.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"polymer",
|
|
@@ -50,21 +50,20 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"haunted": "^
|
|
53
|
+
"haunted": "^5.0.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@commitlint/cli": "^
|
|
57
|
-
"@commitlint/config-conventional": "^
|
|
58
|
-
"@neovici/
|
|
59
|
-
"@open-wc/testing": "^
|
|
56
|
+
"@commitlint/cli": "^17.0.0",
|
|
57
|
+
"@commitlint/config-conventional": "^17.0.0",
|
|
58
|
+
"@neovici/cfg": "^1.15.1",
|
|
59
|
+
"@open-wc/testing": "^3.1.0",
|
|
60
60
|
"@polymer/polymer": "^3.3.1",
|
|
61
61
|
"@semantic-release/changelog": "^6.0.0",
|
|
62
62
|
"@semantic-release/git": "^10.0.0",
|
|
63
63
|
"@web/test-runner": "^0.13.0",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"sinon": "^12.0.0"
|
|
64
|
+
"husky": "^8.0.0",
|
|
65
|
+
"lit-html": "^2.0.0",
|
|
66
|
+
"semantic-release": "^19.0.0",
|
|
67
|
+
"sinon": "^14.0.0"
|
|
69
68
|
}
|
|
70
69
|
}
|