@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.
- package/LICENSE +191 -0
- package/NOTICE +4 -0
- package/dist/browser/pacem-core.js +9099 -0
- package/dist/browser/pacem-core.js.map +1 -0
- package/dist/browser/pacem-core.min.js +2 -0
- package/dist/browser/pacem-core.min.js.map +1 -0
- package/dist/bundle/pacem-core.min.mjs +3 -0
- package/dist/bundle/pacem-core.mjs +8479 -0
- package/dist/bundle/pacem-core.mjs.map +7 -0
- package/dist/esm/adapter.js +7 -0
- package/dist/esm/command.js +10 -0
- package/dist/esm/comparers.js +8 -0
- package/dist/esm/converters.js +70 -0
- package/dist/esm/decorators-next.js +783 -0
- package/dist/esm/decorators.js +736 -0
- package/dist/esm/defaults.js +5 -0
- package/dist/esm/events.js +89 -0
- package/dist/esm/eventtarget.js +131 -0
- package/dist/esm/expression.js +228 -0
- package/dist/esm/index-animations.js +1 -0
- package/dist/esm/index-behaviors.js +1 -0
- package/dist/esm/index-components.js +1 -0
- package/dist/esm/index-iife.js +3 -0
- package/dist/esm/index-logging.js +1 -0
- package/dist/esm/index-net.js +1 -0
- package/dist/esm/index-root.js +20 -0
- package/dist/esm/index-ui.js +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/logger.js +11 -0
- package/dist/esm/prefix.js +6 -0
- package/dist/esm/promise.js +34 -0
- package/dist/esm/regex.js +22 -0
- package/dist/esm/storage.js +56 -0
- package/dist/esm/template.js +4 -0
- package/dist/esm/transforms.js +248 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/utils-customelement.js +524 -0
- package/dist/esm/utils-customevent.js +60 -0
- package/dist/esm/utils.js +1073 -0
- package/package.json +37 -0
- 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
|
+
}
|