@pacem/pacem-core 1.0.0-abel

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 (41) hide show
  1. package/LICENSE +191 -0
  2. package/NOTICE +4 -0
  3. package/dist/browser/pacem-core.js +9099 -0
  4. package/dist/browser/pacem-core.js.map +1 -0
  5. package/dist/browser/pacem-core.min.js +2 -0
  6. package/dist/browser/pacem-core.min.js.map +1 -0
  7. package/dist/bundle/pacem-core.min.mjs +3 -0
  8. package/dist/bundle/pacem-core.mjs +8479 -0
  9. package/dist/bundle/pacem-core.mjs.map +7 -0
  10. package/dist/esm/adapter.js +7 -0
  11. package/dist/esm/command.js +10 -0
  12. package/dist/esm/comparers.js +8 -0
  13. package/dist/esm/converters.js +70 -0
  14. package/dist/esm/decorators-next.js +783 -0
  15. package/dist/esm/decorators.js +736 -0
  16. package/dist/esm/defaults.js +5 -0
  17. package/dist/esm/events.js +89 -0
  18. package/dist/esm/eventtarget.js +131 -0
  19. package/dist/esm/expression.js +228 -0
  20. package/dist/esm/index-animations.js +1 -0
  21. package/dist/esm/index-behaviors.js +1 -0
  22. package/dist/esm/index-components.js +1 -0
  23. package/dist/esm/index-iife.js +3 -0
  24. package/dist/esm/index-logging.js +1 -0
  25. package/dist/esm/index-net.js +1 -0
  26. package/dist/esm/index-root.js +20 -0
  27. package/dist/esm/index-ui.js +1 -0
  28. package/dist/esm/index.js +7 -0
  29. package/dist/esm/logger.js +11 -0
  30. package/dist/esm/prefix.js +6 -0
  31. package/dist/esm/promise.js +34 -0
  32. package/dist/esm/regex.js +22 -0
  33. package/dist/esm/storage.js +56 -0
  34. package/dist/esm/template.js +4 -0
  35. package/dist/esm/transforms.js +248 -0
  36. package/dist/esm/types.js +10 -0
  37. package/dist/esm/utils-customelement.js +524 -0
  38. package/dist/esm/utils-customevent.js +60 -0
  39. package/dist/esm/utils.js +1073 -0
  40. package/package.json +37 -0
  41. package/typings/index.d.ts +2521 -0
@@ -0,0 +1,783 @@
1
+ import { WATCH_PROPS_VAR, INSTANCE_BINDINGS_VAR, INSTANCE_HOST_VAR } from './types';
2
+ import { P } from './prefix';
3
+ import { Utils } from './utils';
4
+ import { PropertyChangeEventName, PropertyChangeEvent, AttributeChangeEvent } from './events';
5
+ import { CustomElementUtils } from './utils-customelement';
6
+ import { Expression } from './expression';
7
+ import { DeferPromise } from './promise';
8
+ import { Http } from './net/http';
9
+ import { PropertyConverters } from './converters';
10
+ import { DefaultComparer } from './comparers';
11
+ // namespace Pacem {
12
+ // #region CUSTOM ELEMENT
13
+ //export const ViewActivatedEventName: string = 'viewactivate';
14
+ const GET_VAL = CustomElementUtils.getAttachedPropertyValue;
15
+ const SET_VAL = CustomElementUtils.setAttachedPropertyValue;
16
+ const DEL_VAL = CustomElementUtils.deleteAttachedPropertyValue;
17
+ const WATCH_PROP_PREFIX = 'pacem:property:';
18
+ const WATCH_VERS_PROP = 'pacem:version';
19
+ const WATCH_BIND_PREFIX = 'pacem:binding:';
20
+ //const WATCH_THROTTLE_PREFIX: string = 'pacem:throttle:';
21
+ const INSTANCE_CHILDREN_VAR = 'pacem:view-children';
22
+ const HAS_BIND_PREFIX = 'pacem:has-binding:';
23
+ const HAS_TEMPLATE_VAR = 'pacem:custom-element:has-template';
24
+ const HASBEEN_TEMPLATED_VAR = 'pacem:custom-element:has-templated';
25
+ const INSTANCE_PROMISE_VAR = 'pacem:custom-element:promise';
26
+ const INSTANCE_READY_VAR = 'pacem:custom-element:ready';
27
+ const INSTANCE_ONREADY_VAR = 'pacem:custom-element:startup';
28
+ //const TYPE_OPTIONS_VAR = 'pacem:custom-element:options';
29
+ function processBinding(element, property, binding) {
30
+ var _this = element;
31
+ const prop = property;
32
+ const value = binding;
33
+ // reset previous dependencies
34
+ var deps = GET_VAL(_this, WATCH_BIND_PREFIX + prop);
35
+ //var handleName = WATCH_THROTTLE_PREFIX + prop;
36
+ deps && // reset previous dependencies
37
+ deps.forEach(i => {
38
+ //cancelAnimationFrame(GET_VAL(_this, handleName));
39
+ i.dep.element.removeEventListener(PropertyChangeEventName, i.callback, false);
40
+ });
41
+ if (Utils.isNull(value) || GET_VAL(element, INSTANCE_READY_VAR) !== true) {
42
+ DEL_VAL(_this, WATCH_BIND_PREFIX + prop);
43
+ DEL_VAL(_this, HAS_BIND_PREFIX + prop);
44
+ }
45
+ else {
46
+ if (value.pending) {
47
+ SET_VAL(_this, WATCH_BIND_PREFIX + prop, []);
48
+ }
49
+ else {
50
+ let mode = 'oneway'; // default
51
+ switch (value.dependencies && value.dependencies.length && value.dependencies[0].mode) {
52
+ case 'twoway':
53
+ if ( /* extra-safety here: */value.dependencies.length == 1)
54
+ mode = 'twoway';
55
+ break;
56
+ case 'once':
57
+ mode = 'once';
58
+ }
59
+ SET_VAL(_this, HAS_BIND_PREFIX + prop, mode);
60
+ const mapped = value.dependencies.map(d => {
61
+ // gather changes and evaluate at `next frame` (?)
62
+ var retval = {
63
+ dep: d, callback: function (evt) {
64
+ if (evt.detail.propertyName === d.property) {
65
+ //cancelAnimationFrame(_this[handleName]);
66
+ //_this[handleName] = requestAnimationFrame(() => {
67
+ _this[prop] = value.evaluate /*Async*/() /*.then(val => {
68
+ _this[prop] = val;
69
+ })*/;
70
+ //});
71
+ }
72
+ }
73
+ };
74
+ //try {
75
+ d.element.addEventListener(PropertyChangeEventName, retval.callback, false);
76
+ //} catch (ex) {
77
+ // console.log(_this.outerHTML + '\n' + d.property);
78
+ // throw (ex);
79
+ //}
80
+ return retval;
81
+ });
82
+ SET_VAL(_this, WATCH_BIND_PREFIX + prop, mapped);
83
+ _this[prop] = value.evaluate();
84
+ }
85
+ }
86
+ }
87
+ function retrieveTemplateAndWaitForDOMReady(config) {
88
+ var deferred = DeferPromise.defer();
89
+ const on_load = (tmpl) => {
90
+ if (Utils.isDOMReady()) {
91
+ deferred.resolve(tmpl);
92
+ }
93
+ else {
94
+ Utils.onDOMReady((evt) => {
95
+ deferred.resolve(tmpl);
96
+ });
97
+ }
98
+ };
99
+ //
100
+ if (config.template) {
101
+ on_load(config.template);
102
+ }
103
+ else if (config.templateUrl) {
104
+ let http = new Http();
105
+ http.get(config.templateUrl).success(r => {
106
+ let tmpl = config.template = r.text;
107
+ on_load(tmpl);
108
+ return r;
109
+ }).error(err => {
110
+ console.error(err.message);
111
+ on_load(null);
112
+ });
113
+ }
114
+ else {
115
+ on_load(null);
116
+ }
117
+ //
118
+ return deferred.promise;
119
+ }
120
+ function retrieveDescendantTemplateActivationPromises(host, root) {
121
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
122
+ var promises = [];
123
+ while (walker.nextNode()) {
124
+ var node = walker.currentNode;
125
+ if (true === GET_VAL(node.constructor, HAS_TEMPLATE_VAR)) {
126
+ var deferred = GET_VAL(node, INSTANCE_PROMISE_VAR, DeferPromise.defer());
127
+ promises.push(deferred.promise);
128
+ }
129
+ }
130
+ return promises;
131
+ }
132
+ function getTarget(context) {
133
+ return context.static
134
+ ? this
135
+ : Object.getPrototypeOf(this);
136
+ }
137
+ export const CustomElement = (config) => {
138
+ return (value, context) => {
139
+ //return (target: Type<any>) => {
140
+ const target = value;
141
+ // might be already registered
142
+ if (Utils.customElements.get(config.tagName)) {
143
+ console.warn(`${config.tagName} registration skipped, since already defined.`);
144
+ return;
145
+ }
146
+ // retrieved watched properties (inherited included)
147
+ var properties = CustomElementUtils.getWatchedProperties(target, true);
148
+ SET_VAL(target, HAS_TEMPLATE_VAR, !(Utils.isNullOrEmpty(config.template) && Utils.isNullOrEmpty(config.templateUrl)));
149
+ if (properties && properties.length > 0) {
150
+ // attribute change whitelist
151
+ const OBS_ATTR_PROP = 'observedAttributes';
152
+ var extraObserved = [];
153
+ if (target.hasOwnProperty(OBS_ATTR_PROP)) {
154
+ extraObserved = target[OBS_ATTR_PROP].slice();
155
+ delete target[OBS_ATTR_PROP];
156
+ }
157
+ // attributes to watch
158
+ let attrs = extraObserved;
159
+ for (var p of properties) {
160
+ let a = CustomElementUtils.camelToKebab(p.name);
161
+ attrs.unshift(a);
162
+ }
163
+ // set static `observedAttributes` property
164
+ Object.defineProperty(target, OBS_ATTR_PROP, {
165
+ get: function () {
166
+ return attrs;
167
+ },
168
+ enumerable: true,
169
+ configurable: false
170
+ });
171
+ }
172
+ {
173
+ const proto = target.prototype;
174
+ // connectedCallback override
175
+ const originalConnectedCallback = Object.getOwnPropertyDescriptor(proto, 'connectedCallback') || proto.connectedCallback;
176
+ const originalViewActivatedCallback = Object.getOwnPropertyDescriptor(proto, 'viewActivatedCallback') || proto.viewActivatedCallback;
177
+ Object.defineProperty(proto, 'connectedCallback', {
178
+ value: function connectedCallback() {
179
+ const _this = this;
180
+ //console.log(`${_this.localName} connected.`);
181
+ _this.setAttribute('pacem', '');
182
+ _this.setAttribute('cloak', '');
183
+ // reset INSTANCE_CHILDREN_VAR at connectedCallback
184
+ DEL_VAL(_this, INSTANCE_CHILDREN_VAR);
185
+ // ready to fire
186
+ if (originalConnectedCallback && originalConnectedCallback.value)
187
+ originalConnectedCallback.value.apply(_this, []);
188
+ else if (typeof originalConnectedCallback == 'function')
189
+ originalConnectedCallback.apply(_this);
190
+ /*
191
+ There's no guarantee that at this point in the element lifecycle its inner DOM has been processed.
192
+ On the contrary, it is fairly likely it hasn't.
193
+ If the element is statically declared on the page after the class declaration (.js file), then its innerHTML is empty for sure.
194
+ **Any template relevant logic must be moved at a point in time when the DOM is fully loaded!**
195
+ */
196
+ retrieveTemplateAndWaitForDOMReady(config).then(tmpl => {
197
+ // still connected?
198
+ // check first!
199
+ if (!_this.isConnected) {
200
+ return;
201
+ }
202
+ var promises = [];
203
+ // apply the template if not already applied(!!)...
204
+ if (GET_VAL(_this, INSTANCE_READY_VAR) !== true
205
+ // ...and if any.
206
+ && !Utils.isNullOrEmpty(tmpl)
207
+ && GET_VAL(_this, HASBEEN_TEMPLATED_VAR) != true) {
208
+ SET_VAL(_this, HASBEEN_TEMPLATED_VAR, true);
209
+ const element = _this, useShadowDOM = config.shadow, originalContent = document.createElement('template').content, currentContent = document.createElement('template'); // config.originalContent === true;
210
+ if (!useShadowDOM) {
211
+ for (var j = element.childNodes.length - 1; j >= 0; j--) {
212
+ originalContent.insertBefore(element.childNodes.item(j), originalContent.firstChild);
213
+ }
214
+ }
215
+ currentContent.innerHTML = tmpl;
216
+ // use pacem-content as a placeholder, then `insertBefore`, then remove.
217
+ const placeholders = currentContent.content.querySelectorAll(P + '-content');
218
+ // assign host and create view activation promise
219
+ CustomElementUtils.assignHostContext(_this, currentContent);
220
+ // placeholder replacement.
221
+ if (placeholders.length == 1) {
222
+ const placeholder = placeholders.item(0);
223
+ var ph = placeholder.nextSibling;
224
+ const parent = placeholder.parentElement || currentContent.content;
225
+ const nodes = originalContent.childNodes;
226
+ parent.removeChild(placeholder);
227
+ while (nodes.length > 0) {
228
+ try {
229
+ let node = nodes.item(0);
230
+ parent.insertBefore(node, ph);
231
+ //ph = node;
232
+ }
233
+ catch (e) {
234
+ console.error(e.toString());
235
+ }
236
+ }
237
+ }
238
+ else if (placeholders.length > 1) {
239
+ throw `There can only be at most 1 content placeholder inside a template.`;
240
+ }
241
+ var root;
242
+ if (config.shadow) {
243
+ root = element.attachShadow({ mode: 'open' /*, slotAssignment: 'named' (default) */ });
244
+ root.appendChild(currentContent.content);
245
+ }
246
+ else {
247
+ root = element;
248
+ var currentChildren = currentContent.content.children;
249
+ while (currentChildren.length > 0) {
250
+ root.append(currentChildren.item(0));
251
+ }
252
+ }
253
+ promises = retrieveDescendantTemplateActivationPromises(_this, root);
254
+ }
255
+ // flag as ready
256
+ SET_VAL(_this, INSTANCE_READY_VAR, true);
257
+ // retry with unresolved properties
258
+ for (let property of properties) {
259
+ const prop = property.name;
260
+ const config = property.config;
261
+ if (!config.converter.retryConversionWhenReady || !Utils.isNull(_this[prop])) {
262
+ continue;
263
+ }
264
+ // check attr
265
+ const attrName = CustomElementUtils.camelToKebab(prop), attr = _this.getAttribute(attrName);
266
+ if (!Utils.isNullOrEmpty(attr) && !CustomElementUtils.isBindingAttribute(attr)) {
267
+ // retry conversion
268
+ _this[prop] = config.converter.convert(attr, _this);
269
+ }
270
+ }
271
+ // execute todos (aka `reflect-backs` of prop values onto attr string values)
272
+ const todos = (GET_VAL(_this, INSTANCE_ONREADY_VAR) || []);
273
+ todos.forEach(todo => {
274
+ todo.apply(_this);
275
+ });
276
+ //
277
+ const pendingBindings = (evt) => {
278
+ // re-evaluate pending expressions...
279
+ var bindings = (GET_VAL(_this, INSTANCE_BINDINGS_VAR) || []);
280
+ bindings.forEach(attrName => {
281
+ const attrNameBind = attrName;
282
+ if (!(attrNameBind in _this.attributes)) {
283
+ throw `Howcome? ${attrNameBind} not in ${_this.constructor.name} attributes!?...`;
284
+ }
285
+ const attrValueBind = _this.attributes[attrNameBind].value;
286
+ // still a binding attribute? (might have changed its shape while pending...)
287
+ if (CustomElementUtils.isBindingAttribute(attrValueBind)) {
288
+ var expr = CustomElementUtils.parseBindingAttribute(attrValueBind, _this);
289
+ processBinding(_this, CustomElementUtils.kebabToCamel(attrName), expr);
290
+ }
291
+ });
292
+ };
293
+ //...now that the document is ready:
294
+ pendingBindings();
295
+ const fireViewActivated = (evt) => {
296
+ //(<HTMLElement>_this).dispatchEvent(new Event(ViewActivatedEventName));
297
+ if (originalViewActivatedCallback && originalViewActivatedCallback.value)
298
+ originalViewActivatedCallback.value.apply(_this, []);
299
+ else if (typeof originalViewActivatedCallback == 'function')
300
+ originalViewActivatedCallback.apply(_this);
301
+ var deferred = GET_VAL(_this, INSTANCE_PROMISE_VAR);
302
+ if (!Utils.isNull(deferred))
303
+ deferred.resolve();
304
+ };
305
+ _this.removeAttribute('cloak');
306
+ //...now that the document is ready:
307
+ // fire original viewActivatedCallback
308
+ // when all the descendant templated elements have already fired
309
+ Promise.all(promises)
310
+ .then(_ => fireViewActivated());
311
+ });
312
+ }, configurable: true, enumerable: true
313
+ });
314
+ // disconnectedCallback override
315
+ const originalDisconnectedCallback = Object.getOwnPropertyDescriptor(proto, 'disconnectedCallback') || proto.disconnectedCallback;
316
+ Object.defineProperty(proto, 'disconnectedCallback', {
317
+ value: function disconnectedCallback() {
318
+ const _this = this;
319
+ // 1. dispose as of dev's desires
320
+ if (originalDisconnectedCallback && originalDisconnectedCallback.value)
321
+ originalDisconnectedCallback.value.apply(_this, []);
322
+ else if (typeof originalDisconnectedCallback === 'function')
323
+ originalDisconnectedCallback.apply(_this, []);
324
+ // 2. dismantle everything
325
+ SET_VAL(_this, INSTANCE_READY_VAR, false);
326
+ // re-evaluate pending expressions (in order to dismiss them, having the element `disconnected`)
327
+ const bindings = (GET_VAL(_this, INSTANCE_BINDINGS_VAR) || []);
328
+ bindings.forEach(attrName => {
329
+ processBinding(_this, CustomElementUtils.kebabToCamel(attrName) /*, expr*/);
330
+ });
331
+ }, configurable: true, enumerable: true
332
+ });
333
+ // watchify attributes
334
+ const originalAttributeChangedCallback = Object.getOwnPropertyDescriptor(proto, 'attributeChangedCallback') || proto.attributeChangedCallback;
335
+ Object.defineProperty(proto, 'attributeChangedCallback', {
336
+ value: function attributeChangedCallback(name, old, val) {
337
+ const _this = this;
338
+ const ready = GET_VAL(_this, INSTANCE_READY_VAR) === true;
339
+ /*
340
+ attributeChangedCallback has to fire BEFORE propertyChangedCallback IN THIS CASE
341
+ */
342
+ if (originalAttributeChangedCallback && originalAttributeChangedCallback.value)
343
+ originalAttributeChangedCallback.value.apply(_this, [name, old, val]);
344
+ else if (typeof originalAttributeChangedCallback === 'function')
345
+ originalAttributeChangedCallback.apply(_this, [name, old, val]);
346
+ // dispatch custom event 'attributechange'
347
+ const evt = new AttributeChangeEvent({ attributeName: name, oldValue: old, currentValue: val, firstChange: !ready });
348
+ _this.dispatchEvent(evt);
349
+ // binding attribute?
350
+ const binding = CustomElementUtils.isBindingAttribute(val);
351
+ const plainAttrName = name;
352
+ const prop = CustomElementUtils.kebabToCamel(plainAttrName);
353
+ // when module scripts (esm) are involved you're at risk trying to process bindings too early, causing errors when - for instance - global variables are referenced.
354
+ // nevertheless, setting moduleProofBindings to `true` prevents early attribute evaluation with startup issues
355
+ // let's keep it false (legacy bheavior)
356
+ const moduleProofBindings = false; // TODO: allow to configure
357
+ if (binding) {
358
+ let value = CustomElementUtils.parseBindingAttribute(val, _this);
359
+ if (value instanceof Expression) {
360
+ if (value.independent && !moduleProofBindings) {
361
+ // constant expression, just assign its value
362
+ _this[prop] = value.evaluate();
363
+ processBinding(_this, prop /* remove eventual old bindings */);
364
+ }
365
+ else {
366
+ var bindings = GET_VAL(_this, INSTANCE_BINDINGS_VAR, []);
367
+ bindings.push(plainAttrName);
368
+ if (!value.pending) {
369
+ processBinding(_this, prop, value);
370
+ }
371
+ }
372
+ }
373
+ else {
374
+ throw `Howcome? ${val} misinterpreted as ${plainAttrName} binding...`;
375
+ }
376
+ }
377
+ else {
378
+ // retrieve property type and cast coherently...
379
+ var property = properties.find(p => p.name === prop);
380
+ if (!Utils.isNull(property)) {
381
+ processBinding(_this, prop /* remove eventual old bindings */);
382
+ // eventual retry at Ln.289
383
+ _this[prop] = property.config.converter.convert(val, _this);
384
+ }
385
+ }
386
+ }, configurable: true, enumerable: true
387
+ });
388
+ // watchify properties
389
+ const originalPropertyChangedCallback = Object.getOwnPropertyDescriptor(proto, 'propertyChangedCallback') || proto.propertyChangedCallback;
390
+ Object.defineProperty(proto, 'propertyChangedCallback', {
391
+ value: function propertyChangedCallback(name, old, val, firstChange, options) {
392
+ const _this = this;
393
+ const ready = GET_VAL(_this, INSTANCE_READY_VAR) === true;
394
+ const todo = () => {
395
+ const bindingType = GET_VAL(_this, HAS_BIND_PREFIX + name);
396
+ /*
397
+ propertyChangedCallback has to fire BEFORE attributeChangedCallback in this case
398
+ */
399
+ if (originalPropertyChangedCallback && originalPropertyChangedCallback.value)
400
+ originalPropertyChangedCallback.value.apply(_this, [name, old, val, !ready]);
401
+ else if (typeof originalPropertyChangedCallback === 'function')
402
+ originalPropertyChangedCallback.apply(_this, [name, old, val, !ready]);
403
+ // dispatch custom event 'propertychange'
404
+ if (!options || options.emit !== false) {
405
+ const evt = new PropertyChangeEvent({ propertyName: name, oldValue: old, currentValue: val, firstChange: !ready });
406
+ _this.dispatchEvent(evt);
407
+ }
408
+ if (Utils.isNullOrEmpty(bindingType) /* reflect to attribute only if this is not a binding property... */) {
409
+ var property = properties.find(p => p.name === name);
410
+ if (options
411
+ && options.reflectBack === true
412
+ && /* not polyfilled */ !CustomElementUtils.polyfilling) {
413
+ var attrName = CustomElementUtils.camelToKebab(name), attr = _this.attributes.getNamedItem(attrName);
414
+ // reflectBack only when ready (or you'll lose eventual bindings for attributes with default non-undefined value):
415
+ // e.g. see `autofetch` property for `PacemFetchElement`
416
+ if (ready
417
+ // Allow to reflectBack proprties onto attributes also if the element isn't ready yet,
418
+ // put proved to have relevant attribute empty. (e.g. some widget utils like `PacemWidgetFetchParameterElement` need this)
419
+ || Utils.isNullOrEmpty(attr)) {
420
+ var config;
421
+ if (val === undefined || val === null) {
422
+ _this.removeAttribute(attrName);
423
+ }
424
+ else if (property && (config = property.config) && typeof config.converter.convertBack === 'function') {
425
+ const sval = config.converter.convertBack(val, _this);
426
+ /* ...and the values are different */
427
+ if (attr == null || sval !== attr.value)
428
+ _this.setAttribute(attrName, sval);
429
+ }
430
+ }
431
+ }
432
+ }
433
+ else if (bindingType === 'twoway') {
434
+ const binding = GET_VAL(_this, WATCH_BIND_PREFIX + name)[0].dep;
435
+ CustomElementUtils.set(binding.element, binding.path, val);
436
+ }
437
+ else if (bindingType === 'once') {
438
+ // remove the binding
439
+ processBinding(_this, name /*, null*/);
440
+ }
441
+ };
442
+ // the following filter is needed in order to avoid attribute reflect-back and error:
443
+ // `Uncaught DOMException: Failed to construct 'CustomElement': The result must not have attributes`
444
+ if (ready) {
445
+ todo.apply(_this);
446
+ }
447
+ else {
448
+ var todos = GET_VAL(_this, INSTANCE_ONREADY_VAR, []);
449
+ todos.push(todo);
450
+ }
451
+ }, configurable: true, enumerable: true
452
+ });
453
+ }
454
+ Utils.customElements.define(config.tagName, target, config.options);
455
+ Utils.customElements.whenDefined(config.tagName).then(() => {
456
+ //console.log(`${config.tagName} defined.`);
457
+ });
458
+ };
459
+ };
460
+ // #endregion
461
+ // #region WATCH PROPERTIES
462
+ const DefaultPropertyConverter = PropertyConverters.None;
463
+ export function Watch(config) {
464
+ return (value, context) => {
465
+ //return (target: any, prop: string, descriptor?: PropertyDescriptor) => {
466
+ const prop = String(context.name);
467
+ context.addInitializer(function () {
468
+ const target = getTarget(context);
469
+ if (!(target instanceof HTMLElement)) {
470
+ throw new Error('ViewChild decorator must be applied to a field inside a HTMLElement subclass.');
471
+ }
472
+ var watchableProperties = GET_VAL(target.constructor, WATCH_PROPS_VAR, []);
473
+ watchableProperties.push({
474
+ name: prop, config: Utils.extend({ emit: true, converter: DefaultPropertyConverter }, config)
475
+ });
476
+ // property/field
477
+ const descriptor = value;
478
+ // comparer
479
+ const comparer = DefaultComparer;
480
+ // original setter?
481
+ const setter = descriptor.set;
482
+ // backing field value
483
+ const propref = WATCH_PROP_PREFIX + prop; // <- pacem:property:'prop'
484
+ const ver = WATCH_VERS_PROP; // <- pacem:version
485
+ const propver = WATCH_VERS_PROP + ':' + prop; // <- pacem:version:'prop'
486
+ // getter
487
+ function getter() {
488
+ return GET_VAL(target, propref);
489
+ }
490
+ ;
491
+ function onChange(key, oldValue, currentValue) {
492
+ const _this = this;
493
+ const debounce = config && config.debounce;
494
+ // does it implement OnPropertyChanged?
495
+ let propertyChangedCallback;
496
+ if (typeof (propertyChangedCallback = _this['propertyChangedCallback']) === 'function') {
497
+ let fn = () => propertyChangedCallback.apply(_this, [key, oldValue, GET_VAL(_this, propref), false /* dummy */, config]);
498
+ if (typeof debounce === 'number' && debounce > 0) {
499
+ clearTimeout(this['_handle_' + prop]);
500
+ this['_handle_' + prop] = setTimeout(fn, debounce);
501
+ }
502
+ else if (debounce === true) {
503
+ cancelAnimationFrame(this['_handle_' + prop]);
504
+ this['_handle_' + prop] = requestAnimationFrame(fn);
505
+ }
506
+ else {
507
+ fn();
508
+ }
509
+ }
510
+ }
511
+ ;
512
+ function decorateArray(instance) {
513
+ const _this = this;
514
+ instance = instance || _this[prop];
515
+ if ( /* already decorated? */GET_VAL(instance, 'pacem:array:decorated') === true) {
516
+ return;
517
+ }
518
+ SET_VAL(instance, 'pacem:array:decorated', true);
519
+ const proto = Array.prototype;
520
+ const methods = {
521
+ splice: proto.splice,
522
+ pop: proto.pop,
523
+ copyWithin: proto.copyWithin,
524
+ push: proto.push,
525
+ shift: proto.shift,
526
+ unshift: proto.unshift
527
+ };
528
+ for (let name in methods) {
529
+ let fn = name; // < closure
530
+ instance[fn] = function (...args) {
531
+ const retval = methods[fn].apply(this, args);
532
+ // TODO?: specifically notify which items have changed.
533
+ // .length property risks to not be affected, fire it as well.
534
+ SET_VAL(this, ver, Date.now());
535
+ onChange.call(_this, prop, this, this);
536
+ return retval;
537
+ };
538
+ }
539
+ }
540
+ function setterCore(oldVal, newVal) {
541
+ const _this = this, isArray = Utils.isArray(newVal);
542
+ // different?
543
+ const diffrent = !comparer(oldVal, newVal);
544
+ if (diffrent || (isArray && GET_VAL(newVal, ver) !== GET_VAL(_this, propver))) {
545
+ SET_VAL(_this, propref, newVal);
546
+ if (isArray) {
547
+ SET_VAL(_this, propver, GET_VAL(newVal, ver));
548
+ decorateArray.call(_this, newVal);
549
+ }
550
+ onChange.call(_this, prop, oldVal, newVal);
551
+ }
552
+ }
553
+ // setter
554
+ if (setter) {
555
+ descriptor.set = function (v) {
556
+ const _this = this, oldVal = _this[prop];
557
+ setter.call(_this, v);
558
+ const newVal = _this[prop];
559
+ //
560
+ setterCore.call(_this, oldVal, newVal);
561
+ };
562
+ }
563
+ else {
564
+ //#region this is needed when creating property at runtime.
565
+ try {
566
+ // TODO: find better way!
567
+ if (target.ownerDocument) {
568
+ target[propref] = target[prop];
569
+ }
570
+ }
571
+ catch (e) { /*Illegal invocation*/ }
572
+ //#endregion
573
+ if (delete target[prop]) {
574
+ Object.defineProperty(target, prop, {
575
+ get: function () {
576
+ return getter.call(this);
577
+ },
578
+ set: function (newVal) {
579
+ const _this = this;
580
+ const oldVal = GET_VAL(_this, propref);
581
+ //
582
+ setterCore.call(_this, oldVal, newVal);
583
+ },
584
+ enumerable: true,
585
+ configurable: true
586
+ });
587
+ }
588
+ }
589
+ });
590
+ };
591
+ }
592
+ export function ViewChild(selector) {
593
+ return (value, context) => {
594
+ //return (target: any, prop: string, descriptor?: PropertyDescriptor) => {
595
+ const prop = String(context.name);
596
+ context.addInitializer(function () {
597
+ const target = getTarget(context);
598
+ if (!(target instanceof HTMLElement)) {
599
+ throw new Error('ViewChild decorator must be applied to a field inside a HTMLElement subclass.');
600
+ }
601
+ const key = `${prop}_${Utils.uniqueCode()}`;
602
+ function findElement() {
603
+ const _this = target, nodelist = (_this.shadowRoot ?? _this).querySelectorAll(selector);
604
+ for (let i = 0; i < nodelist.length; i++) {
605
+ let item = nodelist.item(i);
606
+ if (GET_VAL(item, INSTANCE_HOST_VAR) === _this) {
607
+ return item;
608
+ }
609
+ }
610
+ return null;
611
+ }
612
+ function getter() {
613
+ const _this = target;
614
+ // reset INSTANCE_CHILDREN_VAR on connectedCallback!
615
+ const dict = GET_VAL(_this, INSTANCE_CHILDREN_VAR, {});
616
+ return dict[key] ??= findElement.call(_this);
617
+ }
618
+ if (delete target[prop]) {
619
+ Object.defineProperty(target, prop, {
620
+ get: function () {
621
+ return getter.call(this);
622
+ },
623
+ enumerable: true,
624
+ configurable: true
625
+ });
626
+ }
627
+ });
628
+ };
629
+ }
630
+ // #endregion
631
+ // #region CONCURRENT/DEBOUNCE EXECUTION
632
+ export function Concurrent() {
633
+ function isPromiseLike(obj) {
634
+ return obj && typeof obj.then === 'function';
635
+ }
636
+ return function (value, context) {
637
+ //return function (target: any /* type, actually */, key: string, descriptor?: PropertyDescriptor) {
638
+ const key = String(context.name);
639
+ const originalMethod = value;
640
+ const bufferKey = `pacem:concurrent-buffer:${key}`, lockKey = `pacem:concurrent-running:${key}`;
641
+ context.addInitializer(function () {
642
+ this[context.name] = function (...args) {
643
+ var _this = this;
644
+ var buffer = CustomElementUtils.getAttachedPropertyValue(_this, bufferKey, []);
645
+ var locked = CustomElementUtils.getAttachedPropertyValue(_this, lockKey, false);
646
+ if (locked) {
647
+ // previous process still going:
648
+ // save args for later use,
649
+ let deferred = DeferPromise.defer();
650
+ //let logFn = console && console.debug;
651
+ //if (typeof logFn === 'function')
652
+ // logFn(`Method ${key} is busy. Procrastinating args '${JSON.stringify(args)}'...`);
653
+ args.push(deferred);
654
+ buffer.push(args);
655
+ // and exit.
656
+ return deferred.promise;
657
+ }
658
+ var result = originalMethod.apply(_this, args);
659
+ if (isPromiseLike(result)) {
660
+ let deferred = args && args.length > 0 && args[args.length - 1];
661
+ if (deferred && deferred.promise instanceof DeferPromise)
662
+ args.pop();
663
+ else
664
+ deferred = DeferPromise.defer();
665
+ // hi-jack only if promise-like...
666
+ CustomElementUtils.setAttachedPropertyValue(_this, lockKey, true);
667
+ result.then(r => {
668
+ deferred.resolve(r);
669
+ CustomElementUtils.setAttachedPropertyValue(_this, lockKey, false);
670
+ // warning: here's a possibly sneaky point (TODO: strengthen the lock)
671
+ if (buffer.length > 0)
672
+ originalMethod.apply(_this, buffer.shift());
673
+ }, err => {
674
+ CustomElementUtils.setAttachedPropertyValue(_this, lockKey, false);
675
+ deferred.reject(err);
676
+ });
677
+ return deferred.promise;
678
+ }
679
+ // ...otherwise return the - hopefully - safe single-threaded result.
680
+ else
681
+ return result;
682
+ };
683
+ });
684
+ };
685
+ }
686
+ export function Debounce(msecs = 50, keyFactory = (key, ...args) => `pacem:debouncer:${key}`) {
687
+ return function (value, context) {
688
+ //return function (target: any /* type, actually */, key: string, descriptor?: PropertyDescriptor) {
689
+ const originalMethod = value;
690
+ const methodName = context.name;
691
+ const key = String(methodName);
692
+ context.addInitializer(function () {
693
+ const target = this;
694
+ if (msecs === true) {
695
+ target[methodName] = function (...args) {
696
+ const handleKey = keyFactory(key, ...args);
697
+ var _this = this;
698
+ cancelAnimationFrame(GET_VAL(_this, handleKey));
699
+ SET_VAL(_this, handleKey, requestAnimationFrame(() => {
700
+ originalMethod.apply(_this, args);
701
+ }));
702
+ };
703
+ }
704
+ else if (msecs > 0) {
705
+ target[methodName] = function (...args) {
706
+ const handleKey = keyFactory(key, ...args);
707
+ var _this = this;
708
+ clearTimeout(GET_VAL(_this, handleKey, 0));
709
+ SET_VAL(_this, handleKey, setTimeout(() => {
710
+ originalMethod.apply(_this, args);
711
+ }, msecs));
712
+ };
713
+ }
714
+ });
715
+ };
716
+ }
717
+ export function Throttle(msecs = 50, keyFactory = (key, ...args) => `pacem:throttler:${key}`) {
718
+ return function (value, context) {
719
+ //return function (target: any /* type, actually */, key: string, descriptor?: PropertyDescriptor) {
720
+ const originalMethod = value;
721
+ const methodName = context.name;
722
+ const key = String(methodName);
723
+ context.addInitializer(function () {
724
+ const target = this;
725
+ target[methodName] = function (...args) {
726
+ const handleKey = keyFactory(key, ...args);
727
+ const argsKey = `${handleKey}:args`;
728
+ const _this = this, reset = () => {
729
+ DEL_VAL(_this, handleKey);
730
+ const lastArgs = GET_VAL(_this, argsKey);
731
+ DEL_VAL(_this, argsKey);
732
+ if (lastArgs) {
733
+ originalMethod.apply(_this, args);
734
+ }
735
+ };
736
+ // guard: the presence of an handleKey
737
+ // indicates a too-close call => exit
738
+ if (!Utils.isNull(GET_VAL(_this, handleKey))) {
739
+ SET_VAL(_this, argsKey, args ?? ['pacem']);
740
+ return;
741
+ }
742
+ SET_VAL(_this, handleKey, 1);
743
+ originalMethod.apply(_this, args);
744
+ if (msecs === true) {
745
+ requestAnimationFrame(reset);
746
+ }
747
+ else if (msecs > 0) {
748
+ setTimeout(reset, msecs);
749
+ }
750
+ };
751
+ });
752
+ };
753
+ }
754
+ // #endregion
755
+ // #region TRANSFORMS
756
+ class Transforms {
757
+ static register(name, fn) {
758
+ let _set = Utils.core;
759
+ const segments = (name || fn.name).split('.');
760
+ for (let segment of segments.slice(0, -1)) {
761
+ _set = _set[segment] || (_set[segment] = {});
762
+ }
763
+ const methodName = segments[segments.length - 1] || fn.name;
764
+ if (!Utils.isNull(_set[methodName])) {
765
+ console.warn(`A transform named ${methodName} already exists in this set.`);
766
+ }
767
+ else {
768
+ _set[methodName] = fn;
769
+ }
770
+ }
771
+ }
772
+ export function Transformer(name) {
773
+ return function (value, context) {
774
+ Transforms.register(name, value);
775
+ return value;
776
+ };
777
+ // return function (target: any, key: string, descriptor?: PropertyDescriptor) {
778
+ // const method = <TransformFunction>descriptor.value;
779
+ // Transforms.register(name, method);
780
+ // }
781
+ }
782
+ // #endregion
783
+ //}