@pacem/pacem-core 1.0.0-bessel → 1.0.0-dirac
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/browser/pacem-core.js +870 -9
- package/dist/browser/pacem-core.js.map +1 -1
- package/dist/browser/pacem-core.min.js +2 -2
- package/dist/bundle/pacem-core.min.mjs +6 -6
- package/dist/bundle/pacem-core.mjs +467 -1
- package/dist/bundle/pacem-core.mjs.map +2 -2
- package/dist/docs/pacem-core.json +40109 -0
- package/dist/esm/adapter.js +2 -1
- package/dist/esm/command.js +6 -1
- package/dist/esm/comparers.js +1 -1
- package/dist/esm/converters.js +6 -1
- package/dist/esm/decorators-next.js +1 -1
- package/dist/esm/decorators.js +94 -1
- package/dist/esm/defaults.js +2 -1
- package/dist/esm/events.js +41 -1
- package/dist/esm/eventtarget.js +11 -1
- package/dist/esm/expression.js +37 -1
- package/dist/esm/index-animations.js +1 -1
- package/dist/esm/index-behaviors.js +1 -1
- package/dist/esm/index-components.js +1 -1
- package/dist/esm/index-iife.js +1 -1
- package/dist/esm/index-logging.js +1 -1
- package/dist/esm/index-net.js +1 -1
- package/dist/esm/index-root.js +1 -1
- package/dist/esm/index-ui.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/logger.js +8 -1
- package/dist/esm/prefix.js +2 -1
- package/dist/esm/promise.js +11 -1
- package/dist/esm/regex.js +3 -1
- package/dist/esm/storage.js +14 -1
- package/dist/esm/template.js +2 -1
- package/dist/esm/transforms.js +53 -1
- package/dist/esm/types.js +6 -1
- package/dist/esm/utils-customelement.js +165 -6
- package/dist/esm/utils-customevent.js +27 -1
- package/dist/esm/utils.js +89 -1
- package/package.json +2 -1
- package/typings/index.d.ts +1089 -10
package/dist/esm/template.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-core v1.0.0-
|
|
2
|
+
* @pacem/pacem-core v1.0.0-dirac (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
//namespace Pacem {
|
|
7
|
+
/** Marker base class for elements that behave like `<template>` (e.g. a custom template-proxy element), so template-detection logic can treat them the same as a native `HTMLTemplateElement`. */
|
|
7
8
|
export class TemplateElement extends HTMLElement {
|
|
8
9
|
}
|
|
9
10
|
//}
|
package/dist/esm/transforms.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-core v1.0.0-
|
|
2
|
+
* @pacem/pacem-core v1.0.0-dirac (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
@@ -41,7 +41,21 @@ function buildDiffOptions(modeOrOutput, outputOrCssAdd, cssAddOrRemove, cssRemov
|
|
|
41
41
|
cssRemove: cssRemove ?? 'fill-red-container',
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The framework's built-in library of template-expression transform functions. Each static method here
|
|
46
|
+
* is registered, via the {@link Transformer} decorator, under `Utils.core` (mostly by its own method
|
|
47
|
+
* name - see each method's `@Transformer(...)` name override where present) so it becomes callable by
|
|
48
|
+
* name from binding expressions (e.g. `{{ ^value | size }}`). Not part of the module's exported surface -
|
|
49
|
+
* consumers use these by their registered transform name, not by importing this class.
|
|
50
|
+
*/
|
|
44
51
|
class Transforms {
|
|
52
|
+
/**
|
|
53
|
+
* Wraps every case-insensitive occurrence of each whitespace-separated token of `query` found in `src`
|
|
54
|
+
* (skipping matches inside HTML tags) with a `<span class="{css}">` highlight wrapper.
|
|
55
|
+
* @param src Source HTML/text to search within.
|
|
56
|
+
* @param query Whitespace-separated search terms.
|
|
57
|
+
* @param css CSS class applied to the highlight `<span>`. Defaults to `{PCSS}-highlight`.
|
|
58
|
+
*/
|
|
45
59
|
static highlight(src, query, css = PCSS + '-highlight') {
|
|
46
60
|
if (!query || !src)
|
|
47
61
|
return src;
|
|
@@ -84,9 +98,11 @@ class Transforms {
|
|
|
84
98
|
return acc;
|
|
85
99
|
}, '');
|
|
86
100
|
}
|
|
101
|
+
/** Left-pads `src` (stringified) to `length` characters using `pad`; see {@link Utils.leftPad}. */
|
|
87
102
|
static padleft(src, length, pad) {
|
|
88
103
|
return Utils.leftPad('' + src, length, pad);
|
|
89
104
|
}
|
|
105
|
+
/** Formats a byte count `src` as a human-readable size string (`B`/`kB`/`MB`/`GB`), or `'-'` if not a positive number. */
|
|
90
106
|
static size(src) {
|
|
91
107
|
// TODO: use logarithms
|
|
92
108
|
if (!(src > 0))
|
|
@@ -99,12 +115,25 @@ class Transforms {
|
|
|
99
115
|
return (src / 1_048_576).toFixed(2) + ' MB';
|
|
100
116
|
return (src / 1_073_741_824).toFixed(2) + ' GB';
|
|
101
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Formats a decimal-degrees number `src` as degrees/minutes/seconds (e.g. `41° 53' 24"`).
|
|
120
|
+
* @param src Decimal degrees value (sign indicates direction).
|
|
121
|
+
* @param degSeparator Text following the degrees component.
|
|
122
|
+
* @param minSeparator Text following the minutes component.
|
|
123
|
+
* @param secSeparator Text following the seconds component.
|
|
124
|
+
*/
|
|
102
125
|
static decToDeg(src, degSeparator = '° ', minSeparator = '\' ', secSeparator = '"') {
|
|
103
126
|
const sign = Math.sign(src);
|
|
104
127
|
src = Math.abs(src);
|
|
105
128
|
const deg = Math.floor(src), min0 = (src - deg) * 60, min = Math.floor(min0), sec0 = (min0 - min) * 60, sec = Math.floor(sec0);
|
|
106
129
|
return (sign < 0 ? '-' : '') + deg + degSeparator + min + minSeparator + sec + secSeparator;
|
|
107
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Formats a date-like value `src` (parsed via {@link Utils.parseDate}) as text.
|
|
133
|
+
* @param src Date, timestamp, or parseable date string.
|
|
134
|
+
* @param format Either a named shorthand (`'iso'`, `'isodate'`, `'localdate'`, `'time'`/`'localtime'`, `'full'`, or the default locale date format) or an `Intl.DateTimeFormatOptions` object.
|
|
135
|
+
* @param culture BCP 47 locale to format with. Defaults to the browser's language.
|
|
136
|
+
*/
|
|
108
137
|
static date(src, format, culture) {
|
|
109
138
|
var date = Utils.parseDate(src), lang = culture || navigator.language;
|
|
110
139
|
if (Utils.isNull(format) || typeof format === 'string') {
|
|
@@ -132,6 +161,13 @@ class Transforms {
|
|
|
132
161
|
return date.toLocaleString(culture, format);
|
|
133
162
|
}
|
|
134
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Formats the elapsed time between `start` and `end`.
|
|
166
|
+
* @param start Start date-like value.
|
|
167
|
+
* @param end End date-like value.
|
|
168
|
+
* @param format Either an `Intl.DateTimeFormatOptions` (formatted as a UTC duration, optionally sign-prefixed via `sign: true`), or omitted for the legacy `"{d}d {h}h {m}m {s}s {ms}ms"` textual format.
|
|
169
|
+
* @param culture BCP 47 locale to format with (only used with `Intl.DateTimeFormatOptions`). Defaults to the browser's language.
|
|
170
|
+
*/
|
|
135
171
|
static timespan(start, end, format, culture) {
|
|
136
172
|
const startDate = Utils.parseDate(start), endDate = Utils.parseDate(end), span = endDate.valueOf() - startDate.valueOf(), sign = span < 0 ? '-' : '', elapsed = Math.abs(span);
|
|
137
173
|
if (typeof format === 'object') {
|
|
@@ -142,18 +178,26 @@ class Transforms {
|
|
|
142
178
|
const msecsPerDay = 86400000, msecsPerHr = 3600000, msecsPerMin = 60000, days = Math.floor(elapsed / msecsPerDay), hrs = Math.floor((elapsed % msecsPerDay) / msecsPerHr), mins = Math.floor((elapsed % msecsPerHr) / msecsPerMin), secs = Math.floor((elapsed % msecsPerMin) / 1000), msecs = Math.floor(elapsed % 1000);
|
|
143
179
|
return `${sign}${days}d ${hrs}h ${mins}m ${secs}s ${msecs}ms`;
|
|
144
180
|
}
|
|
181
|
+
/** Formats `src` as a currency amount using `Intl.NumberFormat`. @param currency ISO 4217 currency code. @param culture BCP 47 locale. Defaults to the browser's language. */
|
|
145
182
|
static currency(src, currency, culture) {
|
|
146
183
|
return new Intl.NumberFormat(culture ?? navigator.language, { style: 'currency', currency: currency }).format(src);
|
|
147
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Formats `src` as a number using `Intl.NumberFormat`.
|
|
187
|
+
* @param formatOrCulture Either a BCP 47 locale string, or `Intl.NumberFormatOptions` (in which case `culture` supplies the locale).
|
|
188
|
+
* @param culture BCP 47 locale, used only when `formatOrCulture` is a format-options object. Defaults to the browser's language.
|
|
189
|
+
*/
|
|
148
190
|
static number(src, formatOrCulture, culture) {
|
|
149
191
|
if (typeof formatOrCulture === 'string') {
|
|
150
192
|
return new Intl.NumberFormat(formatOrCulture || navigator.language).format(src);
|
|
151
193
|
}
|
|
152
194
|
return new Intl.NumberFormat(culture ?? navigator.language, formatOrCulture).format(src);
|
|
153
195
|
}
|
|
196
|
+
/** Filters `src` with `filter`; a thin wrapper over `Array.prototype.filter` usable from template expressions. */
|
|
154
197
|
static filter(src, filter) {
|
|
155
198
|
return src.filter(filter);
|
|
156
199
|
}
|
|
200
|
+
/** Sorts `src` in place (ascending, via `>`/`<` comparison), optionally by a property path when items are objects. @param prop Property name to sort by; omit to compare items directly. */
|
|
157
201
|
static orderby(src, prop) {
|
|
158
202
|
return src.sort((a, b) => {
|
|
159
203
|
if (!Utils.isNullOrEmpty(prop)) {
|
|
@@ -164,31 +208,39 @@ class Transforms {
|
|
|
164
208
|
});
|
|
165
209
|
}
|
|
166
210
|
// #region RegExp
|
|
211
|
+
/** Tests `input` against a `RegExp` built from `pattern`/`flags`. Registered as `regex.isMatch`. */
|
|
167
212
|
static isMatch(input, pattern, flags) {
|
|
168
213
|
return new RegExp(pattern, flags).test(input);
|
|
169
214
|
}
|
|
170
215
|
// #endregion
|
|
171
216
|
// #region shortcuts from 'Utils'
|
|
217
|
+
/** Template-expression shortcut for {@link Utils.isEmpty}. */
|
|
172
218
|
static isEmpty(obj) {
|
|
173
219
|
return Utils.isEmpty(obj);
|
|
174
220
|
}
|
|
221
|
+
/** Template-expression shortcut for {@link Utils.isNull}. */
|
|
175
222
|
static isNull(obj) {
|
|
176
223
|
return Utils.isNull(obj);
|
|
177
224
|
}
|
|
225
|
+
/** Template-expression shortcut for {@link Utils.isNullOrEmpty}. */
|
|
178
226
|
static isNullOrEmpty(obj) {
|
|
179
227
|
return Utils.isNullOrEmpty(obj);
|
|
180
228
|
}
|
|
229
|
+
/** Reads a CSS custom property's value off the document; see {@link Utils.Css.getVariableValue}. Registered as `cssvar`. */
|
|
181
230
|
static getCssVariable(name) {
|
|
182
231
|
return Utils.Css.getVariableValue(name);
|
|
183
232
|
}
|
|
233
|
+
/** Reads a query-string parameter's value off the current URL; see {@link Utils.URIs.parseQuery}. Registered as `querystring`. */
|
|
184
234
|
static getQuerystring(name) {
|
|
185
235
|
return Utils.URIs.parseQuery()[name];
|
|
186
236
|
}
|
|
187
237
|
// #endregion
|
|
188
238
|
// #region shortcuts from 'CustomElementUtils'
|
|
239
|
+
/** Template-expression shortcut for {@link CustomElementUtils.Theme}'s `importWebFonts`. */
|
|
189
240
|
static importThemeWebFonts() {
|
|
190
241
|
return CustomElementUtils.Theme.importWebFonts();
|
|
191
242
|
}
|
|
243
|
+
/** Template-expression shortcut for {@link CustomElementUtils.findHostContext}. Registered as `host`. */
|
|
192
244
|
static findHostContext(element) {
|
|
193
245
|
return CustomElementUtils.findHostContext(element);
|
|
194
246
|
}
|
package/dist/esm/types.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-core v1.0.0-
|
|
2
|
+
* @pacem/pacem-core v1.0.0-dirac (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
/// <reference path="events.ts" />
|
|
7
|
+
/** Runtime value (aliasing the built-in `Function`) usable where a constructor reference is needed as a value, paired with the {@link Type} interface for typing it. */
|
|
7
8
|
export const Type = Function;
|
|
8
9
|
;
|
|
9
10
|
// #region CONSTS
|
|
11
|
+
/** Key used to attach, on an element's constructor, the list of its `@Watch`-decorated (watched) property descriptors. */
|
|
10
12
|
export const WATCH_PROPS_VAR = 'pacem:properties';
|
|
13
|
+
/** Key used to attach, on an element instance, the list of attribute names currently holding a pending (not-yet-evaluable) binding expression. */
|
|
11
14
|
export const INSTANCE_BINDINGS_VAR = 'pacem:custom-element:bindings';
|
|
15
|
+
/** Key used to attach, on a templated node, a reference to the custom element instance ("host") whose template produced it — used to resolve binding expression scope. */
|
|
12
16
|
export const INSTANCE_HOST_VAR = 'pacem:custom-element:host';
|
|
17
|
+
/** Key used to attach, on an element instance, its binding-expression evaluation scope object. */
|
|
13
18
|
export const INSTANCE_SCOPE_VAR = 'pacem:custom-element:scope';
|
|
14
19
|
// #endregion
|
|
15
20
|
//}
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-core v1.0.0-
|
|
2
|
+
* @pacem/pacem-core v1.0.0-dirac (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
/// <reference path="expression.ts" />
|
|
7
|
-
/// <reference path="promise.ts" />
|
|
8
|
-
/// <reference path="prefix.ts" />
|
|
9
|
-
/// <reference path="trees/json.ts" />
|
|
10
|
-
/// <reference path="utils.ts" />
|
|
11
6
|
import { Utils } from './utils';
|
|
12
7
|
import { INSTANCE_SCOPE_VAR, WATCH_PROPS_VAR, INSTANCE_HOST_VAR, INSTANCE_BINDINGS_VAR } from './types';
|
|
13
8
|
import { PCSS } from './prefix';
|
|
@@ -19,18 +14,50 @@ const PACEM_BAG = '__pacem__';
|
|
|
19
14
|
const bindingPattern = /^\{\{(.|\n)+\}\}$/;
|
|
20
15
|
// TODO: CSP-proof binding (no unsafe-eval)
|
|
21
16
|
// binding syntax example: `{ binding: { source: ^item.property.path, binder: $pacem.func, parameters: [ ^index, 'foo', : host._baz], mode: 'twoway' } }`
|
|
17
|
+
/**
|
|
18
|
+
* Static grab-bag of low-level helpers underpinning the {@link CustomElement}/{@link Watch} decorators
|
|
19
|
+
* and the binding-expression engine: attached-property storage (a per-instance, non-enumerable "bag"
|
|
20
|
+
* used instead of expando properties), attribute/property name conversion, DOM tree traversal, host/
|
|
21
|
+
* scope resolution for templated content, and binding-attribute parsing.
|
|
22
|
+
*/
|
|
22
23
|
export class CustomElementUtils {
|
|
24
|
+
/** `true` when the platform's `customElements` registry is the Custom Elements polyfill rather than a native implementation. */
|
|
23
25
|
static get polyfilling() {
|
|
24
26
|
return customElements instanceof window['CustomElementRegistry'];
|
|
25
27
|
}
|
|
28
|
+
/** Converts a camelCase property name to its kebab-case attribute-name equivalent (e.g. `myProp` → `my-prop`). */
|
|
26
29
|
static camelToKebab(camelCased) {
|
|
27
30
|
return camelCased && camelCased.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
28
31
|
}
|
|
32
|
+
/** Converts a kebab-case attribute name to its camelCase property-name equivalent (e.g. `my-prop` → `myProp`). */
|
|
29
33
|
static kebabToCamel(kebabCased) {
|
|
30
34
|
return kebabCased && kebabCased.replace(/(-[a-z])/g, (m) => {
|
|
31
35
|
return m[1].toUpperCase();
|
|
32
36
|
});
|
|
33
37
|
}
|
|
38
|
+
/** Waits for the specified alement to be ready. */
|
|
39
|
+
static waitForReady(element) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (Utils.isNull(element)) {
|
|
42
|
+
reject('Not found.');
|
|
43
|
+
}
|
|
44
|
+
// if (element.isConnected && !(element instanceof PacemEventTarget)){
|
|
45
|
+
// reject('Invalid element.');
|
|
46
|
+
// }
|
|
47
|
+
if (element.isReady) {
|
|
48
|
+
resolve();
|
|
49
|
+
}
|
|
50
|
+
const callback = () => {
|
|
51
|
+
element.removeEventListener('load', callback);
|
|
52
|
+
resolve();
|
|
53
|
+
};
|
|
54
|
+
element.addEventListener('load', callback);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Waits for the specified element to be ready, then executes the provided callback. */
|
|
58
|
+
static whenReady(element, callback) {
|
|
59
|
+
CustomElementUtils.waitForReady(element).then(callback);
|
|
60
|
+
}
|
|
34
61
|
static getWatchedProperties(target, includeInherited = true) {
|
|
35
62
|
var properties = [];
|
|
36
63
|
var chain = target instanceof HTMLElement ? target.constructor : target;
|
|
@@ -41,9 +68,16 @@ export class CustomElementUtils {
|
|
|
41
68
|
} while (includeInherited && (chain = Object.getPrototypeOf(chain)));
|
|
42
69
|
return properties;
|
|
43
70
|
}
|
|
71
|
+
/** Retrieves a single `@Watch`-decorated property descriptor by name; see {@link getWatchedProperties}. */
|
|
44
72
|
static getWatchedProperty(target, name) {
|
|
45
73
|
return this.getWatchedProperties(target, true).find(p => p.name === name);
|
|
46
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Loads a `<script>` tag once (deduplicated by `src`, cached via {@link import}), resolving when it fires `load`.
|
|
77
|
+
* @param src Script URL.
|
|
78
|
+
* @param integrity Optional Subresource Integrity hash.
|
|
79
|
+
* @param crossorigin Whether to set `crossorigin` on the tag.
|
|
80
|
+
*/
|
|
47
81
|
static importjs(src, integrity = null, crossorigin = false) {
|
|
48
82
|
var attrs = { 'type': 'text\/javascript', 'src': src };
|
|
49
83
|
if (!Utils.isNullOrEmpty(integrity))
|
|
@@ -52,6 +86,12 @@ export class CustomElementUtils {
|
|
|
52
86
|
Utils.extend(attrs, { 'crossorigin': '' });
|
|
53
87
|
return CustomElementUtils.import(src, 'script', attrs, (document.head || document.getElementsByTagName("head")[0]));
|
|
54
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Loads a `<link rel="stylesheet">` tag once (deduplicated by `src`, cached via {@link import}), resolving when it fires `load`.
|
|
91
|
+
* @param src Stylesheet URL.
|
|
92
|
+
* @param integrity Optional Subresource Integrity hash.
|
|
93
|
+
* @param crossorigin Whether to set `crossorigin="anonymous"` on the tag.
|
|
94
|
+
*/
|
|
55
95
|
static importcss(src, integrity = null, crossorigin = false) {
|
|
56
96
|
var attrs = { 'rel': 'stylesheet', 'href': src };
|
|
57
97
|
if (!Utils.isNullOrEmpty(integrity))
|
|
@@ -60,6 +100,15 @@ export class CustomElementUtils {
|
|
|
60
100
|
Utils.extend(attrs, { 'crossorigin': 'anonymous' });
|
|
61
101
|
return CustomElementUtils.import(src, 'link', attrs, (document.head || document.getElementsByTagName("head")[0]));
|
|
62
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Creates (or reuses an already-present, matching) element of `tagName` with the given attributes and
|
|
105
|
+
* appends it to the DOM, returning a promise that resolves once it loads (or rejects on error). Results
|
|
106
|
+
* are cached per `key` (under `Utils.core.imports`) so repeated calls with the same key are a no-op.
|
|
107
|
+
* @param key Cache/dedupe key (typically the resource URL).
|
|
108
|
+
* @param tagName Tag name of the element to create (e.g. `'script'`, `'link'`).
|
|
109
|
+
* @param attrs Attributes to set on the element, also used to build a matching CSS selector to detect a pre-existing element.
|
|
110
|
+
* @param appendTo Node to append the newly created element to, if none was found. Defaults to `document.body`.
|
|
111
|
+
*/
|
|
63
112
|
static import(key, tagName, attrs, appendTo = document.body) {
|
|
64
113
|
const _p = Utils.core;
|
|
65
114
|
var _imports = _p['imports'] = _p['imports'] || {};
|
|
@@ -89,7 +138,9 @@ export class CustomElementUtils {
|
|
|
89
138
|
}
|
|
90
139
|
});
|
|
91
140
|
}
|
|
141
|
+
/** Theming-related helpers. */
|
|
92
142
|
static { this.Theme = {
|
|
143
|
+
/** Downloads the web fonts referenced by the current theme's `--{@link PCSS}-font-*` CSS custom properties, resolving once at least one font face is ready (or after a short timeout). */
|
|
93
144
|
importWebFonts() {
|
|
94
145
|
function cleanupQuotes(input) {
|
|
95
146
|
// check if the string is wrapped around quotes to escape ';' chars
|
|
@@ -127,6 +178,14 @@ export class CustomElementUtils {
|
|
|
127
178
|
return Promise.all(promises);
|
|
128
179
|
}
|
|
129
180
|
}; }
|
|
181
|
+
/**
|
|
182
|
+
* Stores an arbitrary value "attached" to `target` under `name`, in a private, non-enumerable bag
|
|
183
|
+
* (rather than as a plain expando property) so it doesn't show up in `for...in`/`Object.keys`/JSON
|
|
184
|
+
* serialization. Setting `value` to `null`/`undefined` removes the entry. See {@link getAttachedPropertyValue}.
|
|
185
|
+
* @param target Object to attach the value to.
|
|
186
|
+
* @param name Key under which the value is stored.
|
|
187
|
+
* @param value Value to store, or `null`/`undefined` to remove the entry.
|
|
188
|
+
*/
|
|
130
189
|
static setAttachedPropertyValue(target, name, value) {
|
|
131
190
|
/*(target[PACEM_BAG] = target[PACEM_BAG] || {})[name] = value;*/
|
|
132
191
|
if (Utils.isNull(target))
|
|
@@ -141,9 +200,16 @@ export class CustomElementUtils {
|
|
|
141
200
|
else
|
|
142
201
|
bag[name] = value;
|
|
143
202
|
}
|
|
203
|
+
/** Removes a value previously stored via {@link setAttachedPropertyValue}. */
|
|
144
204
|
static deleteAttachedPropertyValue(target, name) {
|
|
145
205
|
CustomElementUtils.setAttachedPropertyValue(target, name, undefined);
|
|
146
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Reads a value previously stored via {@link setAttachedPropertyValue}.
|
|
209
|
+
* @param target Object the value is attached to.
|
|
210
|
+
* @param name Key the value is stored under.
|
|
211
|
+
* @param ensureValue If provided and no value is currently stored, this value is stored and returned (lazy-initialization).
|
|
212
|
+
*/
|
|
147
213
|
static getAttachedPropertyValue(target, name, ensureValue) {
|
|
148
214
|
const propKey = Object.getOwnPropertyDescriptor(target, PACEM_BAG), bag = propKey && propKey.value;
|
|
149
215
|
if (Utils.isNull(bag && bag[name]) && !Utils.isNull(ensureValue)) {
|
|
@@ -189,6 +255,17 @@ export class CustomElementUtils {
|
|
|
189
255
|
}
|
|
190
256
|
return { target: { value: ref, name: name }, parent: { value: parent || scope, path: path.substring(0, ndx) }, root: { value: root, property: core } };
|
|
191
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Writes `value` at the given dotted `path`, rooted at `element` (see {@link resolvePath}), triggering
|
|
260
|
+
* the appropriate `propertychange` notification. For paths nested more than one level deep (and not
|
|
261
|
+
* targeting an `HTMLElement`, which fires change notifications autonomously), the whole root property
|
|
262
|
+
* is cloned, mutated and reassigned so that a `propertychange` fires with meaningful old/new values -
|
|
263
|
+
* a plain nested assignment wouldn't otherwise be observed. Repeater-bound `item` paths are special-cased
|
|
264
|
+
* to update the owning {@link Repeater}'s `datasource` array in place.
|
|
265
|
+
* @param element Root element the path is resolved against.
|
|
266
|
+
* @param path Dotted path (e.g. `'foo.bar[0].baz'`) to the target property.
|
|
267
|
+
* @param value New value to assign.
|
|
268
|
+
*/
|
|
192
269
|
static set(element, path, value) {
|
|
193
270
|
var obj = CustomElementUtils.resolvePath(path, element);
|
|
194
271
|
var current = obj.target.value;
|
|
@@ -219,6 +296,7 @@ export class CustomElementUtils {
|
|
|
219
296
|
}
|
|
220
297
|
}
|
|
221
298
|
}
|
|
299
|
+
/** Reads the value at the given dotted `path`, rooted at `element`; see {@link resolvePath}. */
|
|
222
300
|
static get(element, path) {
|
|
223
301
|
var obj = CustomElementUtils.resolvePath(path, element);
|
|
224
302
|
return obj && obj.target && obj.target.value;
|
|
@@ -239,12 +317,27 @@ export class CustomElementUtils {
|
|
|
239
317
|
// logFn(`Element "${element.constructor.name}" isn't attached to any context yet.`);
|
|
240
318
|
return retval;
|
|
241
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Finds the `Document` or `ShadowRoot` that `element` belongs to - used as the root for resolving
|
|
322
|
+
* scoped binding expressions (e.g. `$document`/`$host` lookups). Falls back to the previously-attached
|
|
323
|
+
* scope value (see `INSTANCE_SCOPE_VAR`, set on `connectedCallback`) when `element` is currently
|
|
324
|
+
* disconnected, and to a legacy manual-walk implementation as a last resort (needed under Jasmine tests,
|
|
325
|
+
* where `element.isConnected` never reports `true`).
|
|
326
|
+
* @param element Element to resolve the containing document/shadow-root scope for.
|
|
327
|
+
*/
|
|
242
328
|
static findScopeContext(element) {
|
|
243
329
|
let retval = (element.isConnected) ?
|
|
244
330
|
element.getRootNode()
|
|
245
331
|
: CustomElementUtils.getAttachedPropertyValue(element, INSTANCE_SCOPE_VAR);
|
|
246
332
|
return retval ?? /* in Jasmine TESTS `element.isConnected` always returns false (attached to #document-fragment) */ CustomElementUtils._findScopeContextLegacy(element);
|
|
247
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Walks up from `element` (crossing shadow-root boundaries via the host element) looking for the
|
|
336
|
+
* first ancestor matching `predicate`.
|
|
337
|
+
* @param element Element to start the upward search from.
|
|
338
|
+
* @param predicate Test invoked (as `this`-bound and argument-passed) against each ancestor.
|
|
339
|
+
* @returns The first matching ancestor, or `undefined` if none matches.
|
|
340
|
+
*/
|
|
248
341
|
static findAncestor(element, predicate) {
|
|
249
342
|
let el = element;
|
|
250
343
|
let retval;
|
|
@@ -258,12 +351,20 @@ export class CustomElementUtils {
|
|
|
258
351
|
}
|
|
259
352
|
return retval;
|
|
260
353
|
}
|
|
354
|
+
/** Finds the nearest ancestor "shell" element - matching `.{@link PCSS}-body` - or falls back to `document.body`; see {@link findAncestor}. */
|
|
261
355
|
static findAncestorShell(element) {
|
|
262
356
|
return CustomElementUtils.findAncestor(element, el => Utils.is(el, `.${PCSS}-body`) || el === document.body);
|
|
263
357
|
}
|
|
358
|
+
/** Finds the nearest ancestor that is an instance of `ctor`; see {@link findAncestor}. */
|
|
264
359
|
static findAncestorOfType(element, ctor) {
|
|
265
360
|
return CustomElementUtils.findAncestor(element, el => el instanceof ctor);
|
|
266
361
|
}
|
|
362
|
+
/**
|
|
363
|
+
* Collects the descendants of `element` matching `predicate`, via a `TreeWalker` over element nodes.
|
|
364
|
+
* @param element Root element to search from (exclusive - the walker starts on its descendants).
|
|
365
|
+
* @param predicate Test invoked against each descendant element.
|
|
366
|
+
* @param firstOnly When `true`, stops and returns as soon as one match is found.
|
|
367
|
+
*/
|
|
267
368
|
static findDescendants(element, predicate, firstOnly = false) {
|
|
268
369
|
const walker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
|
|
269
370
|
var retval = [];
|
|
@@ -278,10 +379,16 @@ export class CustomElementUtils {
|
|
|
278
379
|
}
|
|
279
380
|
return retval;
|
|
280
381
|
}
|
|
382
|
+
/** Finds the first descendant of `element` matching `predicate`, or `undefined` if none matches; see {@link findDescendants}. */
|
|
281
383
|
static findFirstDescendant(element, predicate) {
|
|
282
384
|
const any = CustomElementUtils.findDescendants(element, predicate, true);
|
|
283
385
|
return Utils.isNullOrEmpty(any) ? void 0 : any[0];
|
|
284
386
|
}
|
|
387
|
+
/**
|
|
388
|
+
* Queries the whole document for elements matching `selector`, additionally filtered by `filter`.
|
|
389
|
+
* @param selector CSS selector to query for. Defaults to `'[pacem]'` (every Pacem custom element, which all carry that attribute once connected).
|
|
390
|
+
* @param filter Extra predicate applied to each matched element. Defaults to accepting everything.
|
|
391
|
+
*/
|
|
285
392
|
static findAll(selector = '[pacem]', filter = (e) => true) {
|
|
286
393
|
const retval = [];
|
|
287
394
|
document.querySelectorAll(selector).forEach((e, i, arr) => {
|
|
@@ -343,6 +450,14 @@ export class CustomElementUtils {
|
|
|
343
450
|
} while (el != null);
|
|
344
451
|
return retval;
|
|
345
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* Tags every element/document-fragment node inside `template`'s content (that isn't already tagged)
|
|
455
|
+
* with `host` under `INSTANCE_HOST_VAR`, so that once cloned and inserted into the DOM, binding
|
|
456
|
+
* expressions in that fragment can resolve back to the custom element instance ("host") that owns
|
|
457
|
+
* this template. See {@link findHostContext}.
|
|
458
|
+
* @param host The custom element instance whose template this is.
|
|
459
|
+
* @param template Template whose content's descendants should be tagged.
|
|
460
|
+
*/
|
|
346
461
|
static assignHostContext(host, template) {
|
|
347
462
|
// navigate through the DOM hierarchy
|
|
348
463
|
const walker = document.createTreeWalker(template.content, NodeFilter.SHOW_ELEMENT + /* traverse templates' content */ NodeFilter.SHOW_DOCUMENT_FRAGMENT);
|
|
@@ -355,6 +470,13 @@ export class CustomElementUtils {
|
|
|
355
470
|
}
|
|
356
471
|
}
|
|
357
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Walks up from `element` looking for the nearest ancestor (inclusive) tagged with a host reference
|
|
475
|
+
* via {@link assignHostContext}; this is the custom element instance whose template produced `element`,
|
|
476
|
+
* used to resolve `$host`/scope references in binding expressions. Returns `undefined` (with a console
|
|
477
|
+
* warning) if `element` is disconnected or isn't part of any templated component.
|
|
478
|
+
* @param element Element to resolve the owning host for.
|
|
479
|
+
*/
|
|
358
480
|
static findHostContext(element) {
|
|
359
481
|
let el = element;
|
|
360
482
|
let retval;
|
|
@@ -377,6 +499,7 @@ export class CustomElementUtils {
|
|
|
377
499
|
logFn(`Element "${element.constructor.name}" isn't a descendant of a templated component.`);
|
|
378
500
|
return retval;
|
|
379
501
|
}
|
|
502
|
+
/** Tests whether an attribute's raw string value has binding-expression syntax (`{{ ... }}`). */
|
|
380
503
|
static isBindingAttribute(attr) {
|
|
381
504
|
/*if (/^\s*\{\s*binding\s+.*\}\s*$/.test(attr)) {
|
|
382
505
|
|
|
@@ -396,6 +519,7 @@ export class CustomElementUtils {
|
|
|
396
519
|
else*/
|
|
397
520
|
return bindingPattern.test(attr);
|
|
398
521
|
}
|
|
522
|
+
/** Strips the `{{ }}` delimiters off a binding attribute's raw value, returning the bare expression text. Throws if `attr` isn't a valid binding attribute; see {@link isBindingAttribute}. */
|
|
399
523
|
static extractBindingAttributeExpression(attr) {
|
|
400
524
|
if (CustomElementUtils.isBindingAttribute(attr)) {
|
|
401
525
|
return attr.substr(2, attr.length - 4);
|
|
@@ -404,6 +528,13 @@ export class CustomElementUtils {
|
|
|
404
528
|
throw `Invalid attribute: incorrect binding syntax.`;
|
|
405
529
|
}
|
|
406
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* Parses a `{{ ... }}` binding attribute value into an {@link Expression}, recognizing the optional
|
|
533
|
+
* trailing `, twoway` / `, once` modifier and stamping it onto the expression's dependencies (`twoway`
|
|
534
|
+
* only applies when the expression has exactly one dependency that itself allows two-way binding).
|
|
535
|
+
* @param attr Raw binding attribute value, including the `{{ }}` delimiters.
|
|
536
|
+
* @param element Element the expression is evaluated/bound against (used to resolve scope references).
|
|
537
|
+
*/
|
|
407
538
|
static parseBindingAttribute(attr, element) {
|
|
408
539
|
// loose syntax {{ ... }}
|
|
409
540
|
let expression = CustomElementUtils.extractBindingAttributeExpression(attr);
|
|
@@ -424,6 +555,14 @@ export class CustomElementUtils {
|
|
|
424
555
|
});
|
|
425
556
|
return expr;
|
|
426
557
|
}
|
|
558
|
+
/**
|
|
559
|
+
* Defines an own property `name` on `o` with `attributes`, unless `o` already has an own property
|
|
560
|
+
* descriptor for that name (in which case the existing descriptor is returned untouched).
|
|
561
|
+
* @param o Object to define the property on.
|
|
562
|
+
* @param name Property name.
|
|
563
|
+
* @param attributes Property descriptor to apply if the property isn't already defined.
|
|
564
|
+
* @returns The (possibly pre-existing) property descriptor.
|
|
565
|
+
*/
|
|
427
566
|
static ensureMember(o, name, attributes) {
|
|
428
567
|
let original = Object.getOwnPropertyDescriptor(o, name);
|
|
429
568
|
if (original != undefined)
|
|
@@ -485,6 +624,7 @@ function avoidObservedAttributes(element, freeze = true) {
|
|
|
485
624
|
const GET_VAL = CustomElementUtils.getAttachedPropertyValue;
|
|
486
625
|
const SET_VAL = CustomElementUtils.setAttachedPropertyValue;
|
|
487
626
|
const REPEATERITEM_PLACEHOLDER = 'pacem:repeater-item';
|
|
627
|
+
/** Static lookup helpers for locating the {@link RepeaterItem} context a given element belongs to. */
|
|
488
628
|
export class Repeater {
|
|
489
629
|
/**
|
|
490
630
|
* Seeks for a PacemRepeaterItem element upwards through the DOM tree, given a starting element and the number of nesting levels.
|
|
@@ -499,7 +639,22 @@ export class Repeater {
|
|
|
499
639
|
return retval;
|
|
500
640
|
}
|
|
501
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* Base class tracking a single rendered repetition of a {@link Repeater}'s template: it associates a
|
|
644
|
+
* DOM placeholder node with the `item`/`index` scope values that template's binding expressions
|
|
645
|
+
* evaluate against, and is itself attached to that placeholder (see {@link getRepeaterItem}) so
|
|
646
|
+
* descendant elements can find their way back to it via {@link findUpwards}.
|
|
647
|
+
*/
|
|
502
648
|
export class RepeaterItem {
|
|
649
|
+
/**
|
|
650
|
+
* Seeks upwards from `element` (previous siblings, then parent, repeated) for the nearest ancestor
|
|
651
|
+
* placeholder tagged as a {@link RepeaterItem}, skipping `upLevels` matches before returning one -
|
|
652
|
+
* this is how deeply-nested repeaters resolve which repetition (of which ancestor repeater) an
|
|
653
|
+
* expression's `^item`/`^^item`/... token refers to.
|
|
654
|
+
* @param element Element to start the upward search from.
|
|
655
|
+
* @param upLevels 0-based number of matching ancestors to skip before returning one (`0` = nearest).
|
|
656
|
+
* @param logFn Called with a warning message if no match is found and `element` is connected to the DOM.
|
|
657
|
+
*/
|
|
503
658
|
static findUpwards(element, upLevels = 0, logFn = console.warn) {
|
|
504
659
|
if (Utils.isNull(element) || element.localName === 'template' || element instanceof /* template-like element? */ TemplateElement) {
|
|
505
660
|
return null;
|
|
@@ -512,16 +667,20 @@ export class RepeaterItem {
|
|
|
512
667
|
}
|
|
513
668
|
return item;
|
|
514
669
|
}
|
|
670
|
+
/** Tests whether `node` is the placeholder of some {@link RepeaterItem}. */
|
|
515
671
|
static isRepeaterItem(node) {
|
|
516
672
|
return !Utils.isNull(GET_VAL(node, REPEATERITEM_PLACEHOLDER));
|
|
517
673
|
}
|
|
674
|
+
/** Retrieves the {@link RepeaterItem} attached to `node`, if `node` is a repeater-item placeholder; see {@link isRepeaterItem}. */
|
|
518
675
|
static getRepeaterItem(node) {
|
|
519
676
|
return GET_VAL(node, REPEATERITEM_PLACEHOLDER);
|
|
520
677
|
}
|
|
678
|
+
/** @param _placeholder DOM node (typically a comment) that stands in for this repetition and gets tagged so {@link getRepeaterItem} can retrieve this instance back from it. */
|
|
521
679
|
constructor(_placeholder) {
|
|
522
680
|
this._placeholder = _placeholder;
|
|
523
681
|
SET_VAL(_placeholder, REPEATERITEM_PLACEHOLDER, this);
|
|
524
682
|
}
|
|
683
|
+
/** The DOM placeholder node standing in for this repetition. */
|
|
525
684
|
get placeholder() {
|
|
526
685
|
return this._placeholder;
|
|
527
686
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @pacem/pacem-core v1.0.0-
|
|
2
|
+
* @pacem/pacem-core v1.0.0-dirac (https://js.pacem.it)
|
|
3
3
|
* Pacem (https://pacem.it)
|
|
4
4
|
* Licensed under Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { Utils } from './utils';
|
|
7
|
+
/** String tokens accepted by {@link CustomEventUtils.matchModifiers} to describe a keyboard/mouse modifier. */
|
|
7
8
|
export var EventKeyModifier;
|
|
8
9
|
(function (EventKeyModifier) {
|
|
9
10
|
EventKeyModifier["AltKey"] = "Alt";
|
|
@@ -11,11 +12,24 @@ export var EventKeyModifier;
|
|
|
11
12
|
EventKeyModifier["ShiftKey"] = "Shift";
|
|
12
13
|
EventKeyModifier["MetaKey"] = "Cmd";
|
|
13
14
|
})(EventKeyModifier || (EventKeyModifier = {}));
|
|
15
|
+
/** Static helpers for inspecting and normalizing native/custom events (coordinates, keyboard modifiers, type checks). */
|
|
14
16
|
export class CustomEventUtils {
|
|
17
|
+
/**
|
|
18
|
+
* Type-guarding check for whether `evt` is an instance of the given event `type`.
|
|
19
|
+
* @param evt Event to test.
|
|
20
|
+
* @param type Event constructor to test against.
|
|
21
|
+
* @returns `true` (narrowing `evt` to `TEvent`) if `evt instanceof type`.
|
|
22
|
+
*/
|
|
15
23
|
static isInstanceOf(evt, type) {
|
|
16
24
|
return evt instanceof type;
|
|
17
25
|
// || CustomElementUtils.getAttachedPropertyValue(evt, 'pacem:custom-event') === evt.type;
|
|
18
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Extracts page/client/screen coordinates from a mouse or (single-)touch event, using the first
|
|
29
|
+
* active touch (`touches[0]`, falling back to `changedTouches[0]`) for `TouchEvent`s.
|
|
30
|
+
* @param evt Source mouse/touch event, or an already-extracted {@link UIEventLike}.
|
|
31
|
+
* @returns The event's position in all three coordinate systems.
|
|
32
|
+
*/
|
|
19
33
|
static getEventCoordinates(evt) {
|
|
20
34
|
var src;
|
|
21
35
|
if ('touches' in evt) {
|
|
@@ -35,6 +49,11 @@ export class CustomEventUtils {
|
|
|
35
49
|
client: { x: src.clientX, y: src.clientY }
|
|
36
50
|
};
|
|
37
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Extracts the keyboard-modifier flags (and, for mouse events, the pressed button as `which`) from a native event.
|
|
54
|
+
* @param evt Source keyboard/mouse/touch event, or an already-extracted {@link KeyEventLike}.
|
|
55
|
+
* @returns The captured modifier flags.
|
|
56
|
+
*/
|
|
38
57
|
static getEventKeyModifiers(evt) {
|
|
39
58
|
var which = evt.which;
|
|
40
59
|
if (evt instanceof MouseEvent) {
|
|
@@ -55,6 +74,13 @@ export class CustomEventUtils {
|
|
|
55
74
|
}
|
|
56
75
|
return true;
|
|
57
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Works around a legacy Edge/ChakraCore bug (see {@link https://github.com/Microsoft/ChakraCore/issues/3952})
|
|
79
|
+
* where an instance built via `super(...)` from a `CustomEvent` subclass constructor doesn't correctly
|
|
80
|
+
* pass `instanceof` checks against that subclass; forcibly resets its prototype when so.
|
|
81
|
+
* @param evt Event instance to fix up.
|
|
82
|
+
* @param type The subclass constructor `evt` is supposed to be an instance of.
|
|
83
|
+
*/
|
|
58
84
|
static fixEdgeCustomEventSubClassInstance(evt, type) {
|
|
59
85
|
// this an optimistic BUGGED workaround due to a damn' Edge bug: https://github.com/Microsoft/ChakraCore/issues/3952
|
|
60
86
|
if (!(evt instanceof type)) {
|