@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.
Files changed (39) hide show
  1. package/dist/browser/pacem-core.js +870 -9
  2. package/dist/browser/pacem-core.js.map +1 -1
  3. package/dist/browser/pacem-core.min.js +2 -2
  4. package/dist/bundle/pacem-core.min.mjs +6 -6
  5. package/dist/bundle/pacem-core.mjs +467 -1
  6. package/dist/bundle/pacem-core.mjs.map +2 -2
  7. package/dist/docs/pacem-core.json +40109 -0
  8. package/dist/esm/adapter.js +2 -1
  9. package/dist/esm/command.js +6 -1
  10. package/dist/esm/comparers.js +1 -1
  11. package/dist/esm/converters.js +6 -1
  12. package/dist/esm/decorators-next.js +1 -1
  13. package/dist/esm/decorators.js +94 -1
  14. package/dist/esm/defaults.js +2 -1
  15. package/dist/esm/events.js +41 -1
  16. package/dist/esm/eventtarget.js +11 -1
  17. package/dist/esm/expression.js +37 -1
  18. package/dist/esm/index-animations.js +1 -1
  19. package/dist/esm/index-behaviors.js +1 -1
  20. package/dist/esm/index-components.js +1 -1
  21. package/dist/esm/index-iife.js +1 -1
  22. package/dist/esm/index-logging.js +1 -1
  23. package/dist/esm/index-net.js +1 -1
  24. package/dist/esm/index-root.js +1 -1
  25. package/dist/esm/index-ui.js +1 -1
  26. package/dist/esm/index.js +1 -1
  27. package/dist/esm/logger.js +8 -1
  28. package/dist/esm/prefix.js +2 -1
  29. package/dist/esm/promise.js +11 -1
  30. package/dist/esm/regex.js +3 -1
  31. package/dist/esm/storage.js +14 -1
  32. package/dist/esm/template.js +2 -1
  33. package/dist/esm/transforms.js +53 -1
  34. package/dist/esm/types.js +6 -1
  35. package/dist/esm/utils-customelement.js +165 -6
  36. package/dist/esm/utils-customevent.js +27 -1
  37. package/dist/esm/utils.js +89 -1
  38. package/package.json +2 -1
  39. package/typings/index.d.ts +1089 -10
@@ -1,9 +1,10 @@
1
1
  /*!
2
- * @pacem/pacem-core v1.0.0-bessel (https://js.pacem.it)
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
  //}
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @pacem/pacem-core v1.0.0-bessel (https://js.pacem.it)
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-bessel (https://js.pacem.it)
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-bessel (https://js.pacem.it)
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-bessel (https://js.pacem.it)
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)) {