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