@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.
@@ -1,87 +1,163 @@
1
- /* eslint-disable max-statements */
2
1
  /**
3
2
  * @license
4
- * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
5
- * This code may only be used under the BSD style license found at
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
- import { isPrimitive } from 'lit-html/lib/parts';
16
- import { directive } from 'lit-html';
17
- const _state = new WeakMap(),
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 = 0x7fffffff;
20
- /**
21
- * Renders one of a series of values, including Promises, to a Part.
22
- *
23
- * This is a variant of until, which never falls back to lower priority values.
24
- * Already rendered content is re-used, even when called with new Promises.
25
- *
26
- * Values are rendered in priority order, with the first argument having the
27
- * highest priority and the last argument having the lowest priority. If a
28
- * value is a Promise, low-priority values will be rendered until it resolves.
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
- const value = args[i];
61
- // Render non-Promise values immediately
62
- if (isPrimitive(value) || typeof value.then !== 'function') {
63
- part.setValue(value);
64
- state.lastRenderedIndex = i;
65
- // Since a lower-priority value will never overwrite a higher-priority
66
- // synchronous value, we can stop processing now.
67
- break;
68
- }
69
- // If this is a Promise we've already handled, skip it.
70
- if (i < previousLength && value === previousValues[i]) {
71
- continue;
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
- Promise.resolve(value).then(resolvedValue => {
75
- const index = state.values.indexOf(value);
76
- // If state.values doesn't contain the value, we've re-rendered without
77
- // the value, so don't render it. Then, only render if the value is
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 { directive } from 'lit-html';
1
+ import { nothing } from 'lit-html';
2
+ import { Directive, directive } from 'lit-html/directive.js';
2
3
  import { array } from '../array';
3
4
 
4
- export const measure = directive((select, onMeasure) => part => {
5
- // Set up the resize observer.
6
- if (!part._resizeObserver) {
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
- // Observe the same elements, if the selection function did not change,
19
- if (part._select === select) {
20
- return;
10
+ update(part, [select, onMeasure]) {
11
+ this.measure(part.element, select, onMeasure);
12
+ return nothing;
21
13
  }
22
14
 
23
- // Stop observing previously selected elements.
24
- if (part._observedElements) {
25
- part._observedElements.forEach(element => part._resizeObserver.unobserve(element));
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
- // Reset measurements.
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);
@@ -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
- directive, NodePart, html
3
- } from 'lit-html';
4
-
5
- const destroyOutlet = part => {
6
- if (!part._outletPart) {
7
- return;
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
- export const
30
- // eslint-disable-next-line max-statements
31
- portal = directive((content, outlet = document.body) => sourcePart => {
32
- // Initialize a disconnect-observer element that cleans up the outletPart when the sourcePart is removed from DOM.
33
- if (!sourcePart._portalCleanerSetUp) {
34
- sourcePart._portalCleanerSetUp = true;
35
- sourcePart.setValue(html`<disconnect-observer .onDisconnect="${ () => {
36
- destroyOutlet(sourcePart);
37
- sourcePart._portalCleanerSetUp = undefined;
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
- // Clean up a previously set up outletPart if the outlet target has changed
42
- if (sourcePart._outletPart && sourcePart._outlet !== outlet) {
43
- destroyOutlet(sourcePart);
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
- // Create a new sourcePart to be used as output of this directive.
47
- if (!sourcePart._outletPart) {
48
- // Use a microtask so the disconnect-observer has time to initialize.
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
- // Update the outlet's content.
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
@@ -1,4 +1,10 @@
1
- import { html } from 'haunted';
1
+ import { html as htm } from 'haunted';
2
+
3
+ const html = (arr, ...thru) =>
4
+ htm(
5
+ Object.assign(arr, { raw: true }),
6
+ ...thru
7
+ );
2
8
 
3
9
  export const
4
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neovici/cosmoz-utils",
3
- "version": "3.29.0",
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": "^4.8.0"
53
+ "haunted": "^5.0.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@commitlint/cli": "^16.0.0",
57
- "@commitlint/config-conventional": "^16.0.0",
58
- "@neovici/eslint-config": "^1.3.0",
59
- "@open-wc/testing": "^2.5.0",
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
- "@web/test-runner-selenium": "^0.5.0",
65
- "husky": "^7.0.0",
66
- "lit-html": "^1.0.0",
67
- "semantic-release": "^18.0.0",
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
  }
@@ -1,6 +0,0 @@
1
- import { directive } from 'lit-html';
2
-
3
- export const ref = directive(ref => part => {
4
- // support both AttributePart and NodePart
5
- ref.current = part?.committer?.element || part?.startNode?.parentNode;
6
- });