@joint/core 4.0.0
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 +376 -0
- package/README.md +49 -0
- package/dist/geometry.js +6486 -0
- package/dist/geometry.min.js +8 -0
- package/dist/joint.d.ts +5536 -0
- package/dist/joint.js +39629 -0
- package/dist/joint.min.js +8 -0
- package/dist/joint.nowrap.js +39626 -0
- package/dist/joint.nowrap.min.js +8 -0
- package/dist/vectorizer.js +9135 -0
- package/dist/vectorizer.min.js +8 -0
- package/dist/version.mjs +3 -0
- package/index.js +3 -0
- package/joint.mjs +27 -0
- package/package.json +192 -0
- package/src/V/annotation.mjs +0 -0
- package/src/V/index.mjs +2642 -0
- package/src/anchors/index.mjs +123 -0
- package/src/config/index.mjs +12 -0
- package/src/connectionPoints/index.mjs +202 -0
- package/src/connectionStrategies/index.mjs +73 -0
- package/src/connectors/curve.mjs +553 -0
- package/src/connectors/index.mjs +6 -0
- package/src/connectors/jumpover.mjs +452 -0
- package/src/connectors/normal.mjs +12 -0
- package/src/connectors/rounded.mjs +17 -0
- package/src/connectors/smooth.mjs +44 -0
- package/src/connectors/straight.mjs +110 -0
- package/src/dia/Cell.mjs +945 -0
- package/src/dia/CellView.mjs +1316 -0
- package/src/dia/Element.mjs +519 -0
- package/src/dia/ElementView.mjs +859 -0
- package/src/dia/Graph.mjs +1112 -0
- package/src/dia/HighlighterView.mjs +319 -0
- package/src/dia/Link.mjs +565 -0
- package/src/dia/LinkView.mjs +2207 -0
- package/src/dia/Paper.mjs +3171 -0
- package/src/dia/PaperLayer.mjs +75 -0
- package/src/dia/ToolView.mjs +69 -0
- package/src/dia/ToolsView.mjs +128 -0
- package/src/dia/attributes/calc.mjs +128 -0
- package/src/dia/attributes/connection.mjs +75 -0
- package/src/dia/attributes/defs.mjs +76 -0
- package/src/dia/attributes/eval.mjs +64 -0
- package/src/dia/attributes/index.mjs +69 -0
- package/src/dia/attributes/legacy.mjs +148 -0
- package/src/dia/attributes/offset.mjs +53 -0
- package/src/dia/attributes/props.mjs +30 -0
- package/src/dia/attributes/shape.mjs +92 -0
- package/src/dia/attributes/text.mjs +180 -0
- package/src/dia/index.mjs +13 -0
- package/src/dia/layers/GridLayer.mjs +176 -0
- package/src/dia/ports.mjs +874 -0
- package/src/elementTools/Control.mjs +153 -0
- package/src/elementTools/HoverConnect.mjs +37 -0
- package/src/elementTools/index.mjs +5 -0
- package/src/env/index.mjs +43 -0
- package/src/g/bezier.mjs +175 -0
- package/src/g/curve.mjs +956 -0
- package/src/g/ellipse.mjs +245 -0
- package/src/g/extend.mjs +64 -0
- package/src/g/geometry.helpers.mjs +58 -0
- package/src/g/index.mjs +17 -0
- package/src/g/intersection.mjs +511 -0
- package/src/g/line.bearing.mjs +30 -0
- package/src/g/line.length.mjs +5 -0
- package/src/g/line.mjs +356 -0
- package/src/g/line.squaredLength.mjs +10 -0
- package/src/g/path.mjs +2260 -0
- package/src/g/point.mjs +375 -0
- package/src/g/points.mjs +247 -0
- package/src/g/polygon.mjs +51 -0
- package/src/g/polyline.mjs +523 -0
- package/src/g/rect.mjs +556 -0
- package/src/g/types.mjs +10 -0
- package/src/highlighters/addClass.mjs +27 -0
- package/src/highlighters/index.mjs +5 -0
- package/src/highlighters/list.mjs +111 -0
- package/src/highlighters/mask.mjs +220 -0
- package/src/highlighters/opacity.mjs +17 -0
- package/src/highlighters/stroke.mjs +100 -0
- package/src/layout/index.mjs +4 -0
- package/src/layout/ports/port.mjs +188 -0
- package/src/layout/ports/portLabel.mjs +224 -0
- package/src/linkAnchors/index.mjs +76 -0
- package/src/linkTools/Anchor.mjs +235 -0
- package/src/linkTools/Arrowhead.mjs +103 -0
- package/src/linkTools/Boundary.mjs +48 -0
- package/src/linkTools/Button.mjs +121 -0
- package/src/linkTools/Connect.mjs +85 -0
- package/src/linkTools/HoverConnect.mjs +161 -0
- package/src/linkTools/Segments.mjs +393 -0
- package/src/linkTools/Vertices.mjs +253 -0
- package/src/linkTools/helpers.mjs +33 -0
- package/src/linkTools/index.mjs +8 -0
- package/src/mvc/Collection.mjs +560 -0
- package/src/mvc/Data.mjs +46 -0
- package/src/mvc/Dom/Dom.mjs +587 -0
- package/src/mvc/Dom/Event.mjs +130 -0
- package/src/mvc/Dom/animations.mjs +122 -0
- package/src/mvc/Dom/events.mjs +69 -0
- package/src/mvc/Dom/index.mjs +13 -0
- package/src/mvc/Dom/methods.mjs +392 -0
- package/src/mvc/Dom/props.mjs +77 -0
- package/src/mvc/Dom/vars.mjs +5 -0
- package/src/mvc/Events.mjs +337 -0
- package/src/mvc/Listener.mjs +33 -0
- package/src/mvc/Model.mjs +239 -0
- package/src/mvc/View.mjs +323 -0
- package/src/mvc/ViewBase.mjs +182 -0
- package/src/mvc/index.mjs +9 -0
- package/src/mvc/mvcUtils.mjs +90 -0
- package/src/polyfills/array.js +4 -0
- package/src/polyfills/base64.js +68 -0
- package/src/polyfills/index.mjs +5 -0
- package/src/polyfills/number.js +3 -0
- package/src/polyfills/string.js +3 -0
- package/src/polyfills/typedArray.js +47 -0
- package/src/routers/index.mjs +6 -0
- package/src/routers/manhattan.mjs +856 -0
- package/src/routers/metro.mjs +91 -0
- package/src/routers/normal.mjs +6 -0
- package/src/routers/oneSide.mjs +60 -0
- package/src/routers/orthogonal.mjs +323 -0
- package/src/routers/rightAngle.mjs +1056 -0
- package/src/shapes/index.mjs +3 -0
- package/src/shapes/standard.mjs +755 -0
- package/src/util/cloneCells.mjs +67 -0
- package/src/util/getRectPoint.mjs +65 -0
- package/src/util/index.mjs +5 -0
- package/src/util/svgTagTemplate.mjs +110 -0
- package/src/util/util.mjs +1754 -0
- package/src/util/utilHelpers.mjs +2402 -0
- package/src/util/wrappers.mjs +56 -0
- package/types/geometry.d.ts +815 -0
- package/types/index.d.ts +53 -0
- package/types/joint.d.ts +4391 -0
- package/types/joint.head.d.ts +12 -0
- package/types/vectorizer.d.ts +327 -0
|
@@ -0,0 +1,1754 @@
|
|
|
1
|
+
import $ from '../mvc/Dom/index.mjs';
|
|
2
|
+
import V from '../V/index.mjs';
|
|
3
|
+
import { config } from '../config/index.mjs';
|
|
4
|
+
import {
|
|
5
|
+
isBoolean,
|
|
6
|
+
isObject,
|
|
7
|
+
isNumber,
|
|
8
|
+
isString,
|
|
9
|
+
mixin,
|
|
10
|
+
deepMixin,
|
|
11
|
+
supplement,
|
|
12
|
+
defaults,
|
|
13
|
+
defaultsDeep,
|
|
14
|
+
deepSupplement,
|
|
15
|
+
assign,
|
|
16
|
+
invoke,
|
|
17
|
+
invokeProperty,
|
|
18
|
+
sortedIndex,
|
|
19
|
+
uniq,
|
|
20
|
+
clone,
|
|
21
|
+
cloneDeep,
|
|
22
|
+
isEmpty,
|
|
23
|
+
isEqual,
|
|
24
|
+
isFunction,
|
|
25
|
+
isPlainObject,
|
|
26
|
+
toArray,
|
|
27
|
+
debounce,
|
|
28
|
+
groupBy,
|
|
29
|
+
sortBy,
|
|
30
|
+
flattenDeep,
|
|
31
|
+
without,
|
|
32
|
+
difference,
|
|
33
|
+
intersection,
|
|
34
|
+
union,
|
|
35
|
+
has,
|
|
36
|
+
result,
|
|
37
|
+
omit,
|
|
38
|
+
pick,
|
|
39
|
+
bindAll,
|
|
40
|
+
forIn,
|
|
41
|
+
camelCase,
|
|
42
|
+
uniqueId,
|
|
43
|
+
merge
|
|
44
|
+
} from './utilHelpers.mjs';
|
|
45
|
+
|
|
46
|
+
export const addClassNamePrefix = function(className) {
|
|
47
|
+
|
|
48
|
+
if (!className) return className;
|
|
49
|
+
|
|
50
|
+
return className.toString().split(' ').map(function(_className) {
|
|
51
|
+
|
|
52
|
+
if (_className.substr(0, config.classNamePrefix.length) !== config.classNamePrefix) {
|
|
53
|
+
_className = config.classNamePrefix + _className;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return _className;
|
|
57
|
+
|
|
58
|
+
}).join(' ');
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const removeClassNamePrefix = function(className) {
|
|
62
|
+
|
|
63
|
+
if (!className) return className;
|
|
64
|
+
|
|
65
|
+
return className.toString().split(' ').map(function(_className) {
|
|
66
|
+
|
|
67
|
+
if (_className.substr(0, config.classNamePrefix.length) === config.classNamePrefix) {
|
|
68
|
+
_className = _className.substr(config.classNamePrefix.length);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return _className;
|
|
72
|
+
|
|
73
|
+
}).join(' ');
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const parseDOMJSON = function(json, namespace) {
|
|
77
|
+
|
|
78
|
+
const selectors = {};
|
|
79
|
+
const groupSelectors = {};
|
|
80
|
+
const svgNamespace = V.namespace.svg;
|
|
81
|
+
|
|
82
|
+
const ns = namespace || svgNamespace;
|
|
83
|
+
const fragment = document.createDocumentFragment();
|
|
84
|
+
|
|
85
|
+
const parseNode = function(siblingsDef, parentNode, ns) {
|
|
86
|
+
for (let i = 0; i < siblingsDef.length; i++) {
|
|
87
|
+
const nodeDef = siblingsDef[i];
|
|
88
|
+
|
|
89
|
+
// Text node
|
|
90
|
+
if (typeof nodeDef === 'string') {
|
|
91
|
+
const textNode = document.createTextNode(nodeDef);
|
|
92
|
+
parentNode.appendChild(textNode);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// TagName
|
|
97
|
+
if (!nodeDef.hasOwnProperty('tagName')) throw new Error('json-dom-parser: missing tagName');
|
|
98
|
+
const tagName = nodeDef.tagName;
|
|
99
|
+
|
|
100
|
+
let node;
|
|
101
|
+
|
|
102
|
+
// Namespace URI
|
|
103
|
+
if (nodeDef.hasOwnProperty('namespaceURI')) ns = nodeDef.namespaceURI;
|
|
104
|
+
node = document.createElementNS(ns, tagName);
|
|
105
|
+
const svg = (ns === svgNamespace);
|
|
106
|
+
|
|
107
|
+
const wrapperNode = (svg) ? V(node) : $(node);
|
|
108
|
+
// Attributes
|
|
109
|
+
const attributes = nodeDef.attributes;
|
|
110
|
+
if (attributes) wrapperNode.attr(attributes);
|
|
111
|
+
// Style
|
|
112
|
+
const style = nodeDef.style;
|
|
113
|
+
if (style) $(node).css(style);
|
|
114
|
+
// ClassName
|
|
115
|
+
if (nodeDef.hasOwnProperty('className')) {
|
|
116
|
+
const className = nodeDef.className;
|
|
117
|
+
if (svg) {
|
|
118
|
+
node.className.baseVal = className;
|
|
119
|
+
} else {
|
|
120
|
+
node.className = className;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// TextContent
|
|
124
|
+
if (nodeDef.hasOwnProperty('textContent')) {
|
|
125
|
+
node.textContent = nodeDef.textContent;
|
|
126
|
+
}
|
|
127
|
+
// Selector
|
|
128
|
+
if (nodeDef.hasOwnProperty('selector')) {
|
|
129
|
+
const nodeSelector = nodeDef.selector;
|
|
130
|
+
if (selectors[nodeSelector]) throw new Error('json-dom-parser: selector must be unique');
|
|
131
|
+
selectors[nodeSelector] = node;
|
|
132
|
+
wrapperNode.attr('joint-selector', nodeSelector);
|
|
133
|
+
}
|
|
134
|
+
// Groups
|
|
135
|
+
if (nodeDef.hasOwnProperty('groupSelector')) {
|
|
136
|
+
let nodeGroups = nodeDef.groupSelector;
|
|
137
|
+
if (!Array.isArray(nodeGroups)) nodeGroups = [nodeGroups];
|
|
138
|
+
for (let j = 0; j < nodeGroups.length; j++) {
|
|
139
|
+
const nodeGroup = nodeGroups[j];
|
|
140
|
+
let group = groupSelectors[nodeGroup];
|
|
141
|
+
if (!group) group = groupSelectors[nodeGroup] = [];
|
|
142
|
+
group.push(node);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
parentNode.appendChild(node);
|
|
147
|
+
|
|
148
|
+
// Children
|
|
149
|
+
const childrenDef = nodeDef.children;
|
|
150
|
+
if (Array.isArray(childrenDef)) {
|
|
151
|
+
parseNode(childrenDef, node, ns);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
parseNode(json, fragment, ns);
|
|
156
|
+
return {
|
|
157
|
+
fragment: fragment,
|
|
158
|
+
selectors: selectors,
|
|
159
|
+
groupSelectors: groupSelectors
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Return a simple hash code from a string. See http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/.
|
|
164
|
+
export const hashCode = function(str) {
|
|
165
|
+
|
|
166
|
+
let hash = 0;
|
|
167
|
+
if (str.length === 0) return hash;
|
|
168
|
+
for (let i = 0; i < str.length; i++) {
|
|
169
|
+
const c = str.charCodeAt(i);
|
|
170
|
+
hash = ((hash << 5) - hash) + c;
|
|
171
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
172
|
+
}
|
|
173
|
+
return hash;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const getByPath = function(obj, path, delimiter) {
|
|
177
|
+
|
|
178
|
+
var keys = Array.isArray(path) ? path : path.split(delimiter || '/');
|
|
179
|
+
var key;
|
|
180
|
+
var i = 0;
|
|
181
|
+
var length = keys.length;
|
|
182
|
+
while (i < length) {
|
|
183
|
+
key = keys[i++];
|
|
184
|
+
if (Object(obj) === obj && key in obj) {
|
|
185
|
+
obj = obj[key];
|
|
186
|
+
} else {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return obj;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const isGetSafe = function(obj, key) {
|
|
194
|
+
// Prevent prototype pollution
|
|
195
|
+
// https://snyk.io/vuln/SNYK-JS-JSON8MERGEPATCH-1038399
|
|
196
|
+
if (typeof key !== 'string' && typeof key !== 'number') {
|
|
197
|
+
key = String(key);
|
|
198
|
+
}
|
|
199
|
+
if (key === 'constructor' && typeof obj[key] === 'function') {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
if (key === '__proto__') {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const setByPath = function(obj, path, value, delimiter) {
|
|
209
|
+
|
|
210
|
+
const keys = Array.isArray(path) ? path : path.split(delimiter || '/');
|
|
211
|
+
const last = keys.length - 1;
|
|
212
|
+
let diver = obj;
|
|
213
|
+
let i = 0;
|
|
214
|
+
|
|
215
|
+
for (; i < last; i++) {
|
|
216
|
+
const key = keys[i];
|
|
217
|
+
if (!isGetSafe(diver, key)) return obj;
|
|
218
|
+
const value = diver[key];
|
|
219
|
+
// diver creates an empty object if there is no nested object under such a key.
|
|
220
|
+
// This means that one can populate an empty nested object with setByPath().
|
|
221
|
+
diver = value || (diver[key] = {});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
diver[keys[last]] = value;
|
|
225
|
+
|
|
226
|
+
return obj;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
export const unsetByPath = function(obj, path, delimiter) {
|
|
230
|
+
|
|
231
|
+
const keys = Array.isArray(path) ? path : path.split(delimiter || '/');
|
|
232
|
+
const last = keys.length - 1;
|
|
233
|
+
let diver = obj;
|
|
234
|
+
let i = 0;
|
|
235
|
+
|
|
236
|
+
for (; i < last; i++) {
|
|
237
|
+
const key = keys[i];
|
|
238
|
+
if (!isGetSafe(diver, key)) return obj;
|
|
239
|
+
const value = diver[key];
|
|
240
|
+
if (!value) return obj;
|
|
241
|
+
diver = value;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
delete diver[keys[last]];
|
|
245
|
+
|
|
246
|
+
return obj;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export const flattenObject = function(obj, delim, stop) {
|
|
250
|
+
|
|
251
|
+
delim = delim || '/';
|
|
252
|
+
var ret = {};
|
|
253
|
+
|
|
254
|
+
for (var key in obj) {
|
|
255
|
+
|
|
256
|
+
if (!obj.hasOwnProperty(key)) continue;
|
|
257
|
+
|
|
258
|
+
var shouldGoDeeper = typeof obj[key] === 'object';
|
|
259
|
+
if (shouldGoDeeper && stop && stop(obj[key])) {
|
|
260
|
+
shouldGoDeeper = false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (shouldGoDeeper) {
|
|
264
|
+
|
|
265
|
+
var flatObject = flattenObject(obj[key], delim, stop);
|
|
266
|
+
|
|
267
|
+
for (var flatKey in flatObject) {
|
|
268
|
+
if (!flatObject.hasOwnProperty(flatKey)) continue;
|
|
269
|
+
ret[key + delim + flatKey] = flatObject[flatKey];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
} else {
|
|
273
|
+
|
|
274
|
+
ret[key] = obj[key];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return ret;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const uuid = function() {
|
|
282
|
+
|
|
283
|
+
// credit: http://stackoverflow.com/posts/2117523/revisions
|
|
284
|
+
|
|
285
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
286
|
+
var r = (Math.random() * 16) | 0;
|
|
287
|
+
var v = (c === 'x') ? r : (r & 0x3 | 0x8);
|
|
288
|
+
return v.toString(16);
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Generates global unique id and stores it as a property of the object, if provided.
|
|
293
|
+
export const guid = function(obj) {
|
|
294
|
+
|
|
295
|
+
guid.id = guid.id || 1;
|
|
296
|
+
|
|
297
|
+
if (obj === undefined) {
|
|
298
|
+
return 'j_' + guid.id++;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
obj.id = (obj.id === undefined ? 'j_' + guid.id++ : obj.id);
|
|
302
|
+
return obj.id;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export const toKebabCase = function(string) {
|
|
306
|
+
|
|
307
|
+
return string.replace(/[A-Z]/g, '-$&').toLowerCase();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const normalizeEvent = function(evt) {
|
|
311
|
+
|
|
312
|
+
if (evt.normalized) return evt;
|
|
313
|
+
|
|
314
|
+
const { originalEvent, target } = evt;
|
|
315
|
+
|
|
316
|
+
// If the event is a touch event, normalize it to a mouse event.
|
|
317
|
+
const touch = originalEvent && originalEvent.changedTouches && originalEvent.changedTouches[0];
|
|
318
|
+
if (touch) {
|
|
319
|
+
for (let property in touch) {
|
|
320
|
+
// copy all the properties from the first touch that are not
|
|
321
|
+
// defined on TouchEvent (clientX, clientY, pageX, pageY, screenX, screenY, identifier, ...)
|
|
322
|
+
if (evt[property] === undefined) {
|
|
323
|
+
evt[property] = touch[property];
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// IE: evt.target could be set to SVGElementInstance for SVGUseElement
|
|
328
|
+
if (target) {
|
|
329
|
+
const useElement = target.correspondingUseElement;
|
|
330
|
+
if (useElement) evt.target = useElement;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
evt.normalized = true;
|
|
334
|
+
|
|
335
|
+
return evt;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
export const normalizeWheel = function(evt) {
|
|
339
|
+
// Sane values derived empirically
|
|
340
|
+
const PIXEL_STEP = 10;
|
|
341
|
+
const LINE_HEIGHT = 40;
|
|
342
|
+
const PAGE_HEIGHT = 800;
|
|
343
|
+
|
|
344
|
+
let sX = 0, sY = 0, pX = 0, pY = 0;
|
|
345
|
+
|
|
346
|
+
// Legacy
|
|
347
|
+
if ('detail' in evt) { sY = evt.detail; }
|
|
348
|
+
if ('wheelDelta' in evt) { sY = -evt.wheelDelta / 120; }
|
|
349
|
+
if ('wheelDeltaY' in evt) { sY = -evt.wheelDeltaY / 120; }
|
|
350
|
+
if ('wheelDeltaX' in evt) { sX = -evt.wheelDeltaX / 120; }
|
|
351
|
+
|
|
352
|
+
// side scrolling on FF with DOMMouseScroll
|
|
353
|
+
if ( 'axis' in evt && evt.axis === evt.HORIZONTAL_AXIS ) {
|
|
354
|
+
sX = sY;
|
|
355
|
+
sY = 0;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
pX = 'deltaX' in evt ? evt.deltaX : sX * PIXEL_STEP;
|
|
359
|
+
pY = 'deltaY' in evt ? evt.deltaY : sY * PIXEL_STEP;
|
|
360
|
+
|
|
361
|
+
if ((pX || pY) && evt.deltaMode) {
|
|
362
|
+
if (evt.deltaMode == 1) {
|
|
363
|
+
pX *= LINE_HEIGHT;
|
|
364
|
+
pY *= LINE_HEIGHT;
|
|
365
|
+
} else {
|
|
366
|
+
pX *= PAGE_HEIGHT;
|
|
367
|
+
pY *= PAGE_HEIGHT;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// macOS switches deltaX and deltaY automatically when scrolling with shift key, so this is needed in other cases
|
|
372
|
+
if (evt.deltaX === 0 && evt.deltaY !== 0 && evt.shiftKey) {
|
|
373
|
+
pX = pY;
|
|
374
|
+
pY = 0;
|
|
375
|
+
sX = sY;
|
|
376
|
+
sY = 0;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Fall-back if spin cannot be determined
|
|
380
|
+
if (pX && !sX) { sX = (pX < 1) ? -1 : 1; }
|
|
381
|
+
if (pY && !sY) { sY = (pY < 1) ? -1 : 1; }
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
spinX : sX,
|
|
385
|
+
spinY : sY,
|
|
386
|
+
deltaX : pX,
|
|
387
|
+
deltaY : pY,
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
export const cap = function(val, max) {
|
|
392
|
+
return val > max ? max : val < -max ? -max : val;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export const nextFrame = (function() {
|
|
396
|
+
|
|
397
|
+
var raf;
|
|
398
|
+
|
|
399
|
+
if (typeof window !== 'undefined') {
|
|
400
|
+
|
|
401
|
+
raf = window.requestAnimationFrame ||
|
|
402
|
+
window.webkitRequestAnimationFrame ||
|
|
403
|
+
window.mozRequestAnimationFrame ||
|
|
404
|
+
window.oRequestAnimationFrame ||
|
|
405
|
+
window.msRequestAnimationFrame;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!raf) {
|
|
409
|
+
|
|
410
|
+
var lastTime = 0;
|
|
411
|
+
|
|
412
|
+
raf = function(callback) {
|
|
413
|
+
|
|
414
|
+
var currTime = new Date().getTime();
|
|
415
|
+
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
|
416
|
+
var id = setTimeout(function() {
|
|
417
|
+
callback(currTime + timeToCall);
|
|
418
|
+
}, timeToCall);
|
|
419
|
+
|
|
420
|
+
lastTime = currTime + timeToCall;
|
|
421
|
+
|
|
422
|
+
return id;
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return function(callback, context, ...rest) {
|
|
427
|
+
return (context !== undefined)
|
|
428
|
+
? raf(callback.bind(context, ...rest))
|
|
429
|
+
: raf(callback);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
})();
|
|
433
|
+
|
|
434
|
+
export const cancelFrame = (function() {
|
|
435
|
+
|
|
436
|
+
var caf;
|
|
437
|
+
var client = typeof window != 'undefined';
|
|
438
|
+
|
|
439
|
+
if (client) {
|
|
440
|
+
|
|
441
|
+
caf = window.cancelAnimationFrame ||
|
|
442
|
+
window.webkitCancelAnimationFrame ||
|
|
443
|
+
window.webkitCancelRequestAnimationFrame ||
|
|
444
|
+
window.msCancelAnimationFrame ||
|
|
445
|
+
window.msCancelRequestAnimationFrame ||
|
|
446
|
+
window.oCancelAnimationFrame ||
|
|
447
|
+
window.oCancelRequestAnimationFrame ||
|
|
448
|
+
window.mozCancelAnimationFrame ||
|
|
449
|
+
window.mozCancelRequestAnimationFrame;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
caf = caf || clearTimeout;
|
|
453
|
+
|
|
454
|
+
return client ? caf.bind(window) : caf;
|
|
455
|
+
|
|
456
|
+
})();
|
|
457
|
+
|
|
458
|
+
export const isPercentage = function(val) {
|
|
459
|
+
|
|
460
|
+
return isString(val) && val.slice(-1) === '%';
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
export const parseCssNumeric = function(val, restrictUnits) {
|
|
464
|
+
|
|
465
|
+
function getUnit(validUnitExp) {
|
|
466
|
+
|
|
467
|
+
// one or more numbers, followed by
|
|
468
|
+
// any number of (
|
|
469
|
+
// `.`, followed by
|
|
470
|
+
// one or more numbers
|
|
471
|
+
// ), followed by
|
|
472
|
+
// `validUnitExp`, followed by
|
|
473
|
+
// end of string
|
|
474
|
+
var matches = new RegExp('(?:\\d+(?:\\.\\d+)*)(' + validUnitExp + ')$').exec(val);
|
|
475
|
+
|
|
476
|
+
if (!matches) return null;
|
|
477
|
+
return matches[1];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
var number = parseFloat(val);
|
|
481
|
+
|
|
482
|
+
// if `val` cannot be parsed as a number, return `null`
|
|
483
|
+
if (Number.isNaN(number)) return null;
|
|
484
|
+
|
|
485
|
+
// else: we know `output.value`
|
|
486
|
+
var output = {};
|
|
487
|
+
output.value = number;
|
|
488
|
+
|
|
489
|
+
// determine the unit
|
|
490
|
+
var validUnitExp;
|
|
491
|
+
if (restrictUnits == null) {
|
|
492
|
+
// no restriction
|
|
493
|
+
// accept any unit, as well as no unit
|
|
494
|
+
validUnitExp = '[A-Za-z]*';
|
|
495
|
+
|
|
496
|
+
} else if (Array.isArray(restrictUnits)) {
|
|
497
|
+
// if this is an empty array, top restriction - return `null`
|
|
498
|
+
if (restrictUnits.length === 0) return null;
|
|
499
|
+
|
|
500
|
+
// else: restriction - an array of valid unit strings
|
|
501
|
+
validUnitExp = restrictUnits.join('|');
|
|
502
|
+
|
|
503
|
+
} else if (isString(restrictUnits)) {
|
|
504
|
+
// restriction - a single valid unit string
|
|
505
|
+
validUnitExp = restrictUnits;
|
|
506
|
+
}
|
|
507
|
+
var unit = getUnit(validUnitExp);
|
|
508
|
+
|
|
509
|
+
// if we found no matches for `restrictUnits`, return `null`
|
|
510
|
+
if (unit === null) return null;
|
|
511
|
+
|
|
512
|
+
// else: we know the unit
|
|
513
|
+
output.unit = unit;
|
|
514
|
+
return output;
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const NO_SPACE = 0;
|
|
518
|
+
|
|
519
|
+
function splitWordWithEOL(word, eol) {
|
|
520
|
+
const eolWords = word.split(eol);
|
|
521
|
+
let n = 1;
|
|
522
|
+
for (let j = 0, jl = eolWords.length - 1; j < jl; j++) {
|
|
523
|
+
const replacement = [];
|
|
524
|
+
if (j > 0 || eolWords[0] !== '') replacement.push(NO_SPACE);
|
|
525
|
+
replacement.push(eol);
|
|
526
|
+
if (j < jl - 1 || eolWords[jl] !== '') replacement.push(NO_SPACE);
|
|
527
|
+
eolWords.splice(n, 0, ...replacement);
|
|
528
|
+
n += replacement.length + 1;
|
|
529
|
+
}
|
|
530
|
+
return eolWords.filter(word => word !== '');
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
function getLineHeight(heightValue, textElement) {
|
|
535
|
+
if (heightValue === null) {
|
|
536
|
+
// Default 1em lineHeight
|
|
537
|
+
return textElement.getBBox().height;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
switch (heightValue.unit) {
|
|
541
|
+
case 'em':
|
|
542
|
+
return textElement.getBBox().height * heightValue.value;
|
|
543
|
+
case 'px':
|
|
544
|
+
case '':
|
|
545
|
+
return heightValue.value;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export const breakText = function(text, size, styles = {}, opt = {}) {
|
|
550
|
+
|
|
551
|
+
var width = size.width;
|
|
552
|
+
var height = size.height;
|
|
553
|
+
|
|
554
|
+
var svgDocument = opt.svgDocument || V('svg').node;
|
|
555
|
+
var textSpan = V('tspan').node;
|
|
556
|
+
var textElement = V('text').attr(styles).append(textSpan).node;
|
|
557
|
+
var textNode = document.createTextNode('');
|
|
558
|
+
|
|
559
|
+
// Prevent flickering
|
|
560
|
+
textElement.style.opacity = 0;
|
|
561
|
+
// Prevent FF from throwing an uncaught exception when `getBBox()`
|
|
562
|
+
// called on element that is not in the render tree (is not measurable).
|
|
563
|
+
// <tspan>.getComputedTextLength() returns always 0 in this case.
|
|
564
|
+
// Note that the `textElement` resp. `textSpan` can become hidden
|
|
565
|
+
// when it's appended to the DOM and a `display: none` CSS stylesheet
|
|
566
|
+
// rule gets applied.
|
|
567
|
+
textElement.style.display = 'block';
|
|
568
|
+
textSpan.style.display = 'block';
|
|
569
|
+
|
|
570
|
+
textSpan.appendChild(textNode);
|
|
571
|
+
svgDocument.appendChild(textElement); // lgtm [js/xss-through-dom]
|
|
572
|
+
|
|
573
|
+
if (!opt.svgDocument) {
|
|
574
|
+
|
|
575
|
+
document.body.appendChild(svgDocument);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const preserveSpaces = opt.preserveSpaces;
|
|
579
|
+
const space = ' ';
|
|
580
|
+
const separator = (opt.separator || opt.separator === '') ? opt.separator : space;
|
|
581
|
+
// If separator is a RegExp, we use the space character to join words together again (not ideal)
|
|
582
|
+
const separatorChar = (typeof separator === 'string') ? separator : space;
|
|
583
|
+
var eol = opt.eol || '\n';
|
|
584
|
+
var hyphen = opt.hyphen ? new RegExp(opt.hyphen) : /[^\w\d\u00C0-\u1FFF\u2800-\uFFFD]/;
|
|
585
|
+
var maxLineCount = opt.maxLineCount;
|
|
586
|
+
if (!isNumber(maxLineCount)) maxLineCount = Infinity;
|
|
587
|
+
|
|
588
|
+
var words = text.split(separator);
|
|
589
|
+
var full = [];
|
|
590
|
+
var lines = [];
|
|
591
|
+
var p, h;
|
|
592
|
+
var lineHeight;
|
|
593
|
+
|
|
594
|
+
if (preserveSpaces) {
|
|
595
|
+
V(textSpan).attr('xml:space', 'preserve');
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
for (var i = 0, l = 0, len = words.length; i < len; i++) {
|
|
599
|
+
|
|
600
|
+
var word = words[i];
|
|
601
|
+
|
|
602
|
+
if (!word && !preserveSpaces) continue;
|
|
603
|
+
if (typeof word !== 'string') continue;
|
|
604
|
+
|
|
605
|
+
var isEol = false;
|
|
606
|
+
if (eol && word.indexOf(eol) >= 0) {
|
|
607
|
+
// word contains end-of-line character
|
|
608
|
+
if (word.length > 1) {
|
|
609
|
+
// separate word and continue cycle
|
|
610
|
+
const eolWords = splitWordWithEOL(words[i], eol);
|
|
611
|
+
words.splice(i, 1, ...eolWords);
|
|
612
|
+
i--;
|
|
613
|
+
len = words.length;
|
|
614
|
+
continue;
|
|
615
|
+
} else {
|
|
616
|
+
// creates a new line
|
|
617
|
+
if (preserveSpaces && typeof words[i - 1] === 'string' ) {
|
|
618
|
+
words.splice(i, NO_SPACE, '', NO_SPACE);
|
|
619
|
+
len += 2;
|
|
620
|
+
i--;
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
lines[++l] = (!preserveSpaces || typeof words[i + 1] === 'string') ? '' : undefined;
|
|
624
|
+
isEol = true;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (!isEol) {
|
|
629
|
+
|
|
630
|
+
let data;
|
|
631
|
+
if (preserveSpaces) {
|
|
632
|
+
data = lines[l] !== undefined ? lines[l] + separatorChar + word : word;
|
|
633
|
+
} else {
|
|
634
|
+
data = lines[l] ? lines[l] + separatorChar + word : word;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
textNode.data = data;
|
|
638
|
+
|
|
639
|
+
if (textSpan.getComputedTextLength() <= width) {
|
|
640
|
+
|
|
641
|
+
// the current line fits
|
|
642
|
+
lines[l] = data;
|
|
643
|
+
|
|
644
|
+
if (p || h) {
|
|
645
|
+
// We were partitioning. Put rest of the word onto next line
|
|
646
|
+
full[l++] = true;
|
|
647
|
+
|
|
648
|
+
// cancel partitioning and splitting by hyphens
|
|
649
|
+
p = 0;
|
|
650
|
+
h = 0;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
} else {
|
|
654
|
+
|
|
655
|
+
if (!lines[l] || p) {
|
|
656
|
+
|
|
657
|
+
var partition = !!p;
|
|
658
|
+
|
|
659
|
+
p = word.length - 1;
|
|
660
|
+
|
|
661
|
+
if (partition || !p) {
|
|
662
|
+
|
|
663
|
+
// word has only one character.
|
|
664
|
+
if (!p) {
|
|
665
|
+
|
|
666
|
+
if (!lines[l]) {
|
|
667
|
+
|
|
668
|
+
// we won't fit this text within our rect
|
|
669
|
+
lines = [];
|
|
670
|
+
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// partitioning didn't help on the non-empty line
|
|
675
|
+
// try again, but this time start with a new line
|
|
676
|
+
|
|
677
|
+
// cancel partitions created
|
|
678
|
+
words.splice(i, 2, word + words[i + 1]);
|
|
679
|
+
|
|
680
|
+
// adjust word length
|
|
681
|
+
len--;
|
|
682
|
+
|
|
683
|
+
full[l++] = true;
|
|
684
|
+
i--;
|
|
685
|
+
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// move last letter to the beginning of the next word
|
|
690
|
+
words[i] = word.substring(0, p);
|
|
691
|
+
const nextWord = words[i + 1];
|
|
692
|
+
words[i + 1] = word.substring(p) + (nextWord === undefined || nextWord === NO_SPACE ? '' : nextWord);
|
|
693
|
+
|
|
694
|
+
} else {
|
|
695
|
+
|
|
696
|
+
if (h) {
|
|
697
|
+
// cancel splitting and put the words together again
|
|
698
|
+
words.splice(i, 2, words[i] + words[i + 1]);
|
|
699
|
+
h = 0;
|
|
700
|
+
} else {
|
|
701
|
+
var hyphenIndex = word.search(hyphen);
|
|
702
|
+
if (hyphenIndex > -1 && hyphenIndex !== word.length - 1 && hyphenIndex !== 0) {
|
|
703
|
+
h = hyphenIndex + 1;
|
|
704
|
+
p = 0;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// We initiate partitioning or splitting
|
|
708
|
+
// split the long word into two words
|
|
709
|
+
words.splice(i, 1, word.substring(0, h || p), word.substring(h|| p));
|
|
710
|
+
// adjust words length
|
|
711
|
+
len++;
|
|
712
|
+
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (l && !full[l - 1]) {
|
|
716
|
+
// if the previous line is not full, try to fit max part of
|
|
717
|
+
// the current word there
|
|
718
|
+
l--;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (!preserveSpaces || lines[l] !== '') {
|
|
723
|
+
i--;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
l++;
|
|
730
|
+
i--;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
var lastL = null;
|
|
734
|
+
|
|
735
|
+
if (lines.length > maxLineCount) {
|
|
736
|
+
|
|
737
|
+
lastL = maxLineCount - 1;
|
|
738
|
+
|
|
739
|
+
} else if (height !== undefined) {
|
|
740
|
+
|
|
741
|
+
// if size.height is defined we have to check whether the height of the entire
|
|
742
|
+
// text exceeds the rect height
|
|
743
|
+
|
|
744
|
+
if (lineHeight === undefined && textNode.data !== '') {
|
|
745
|
+
|
|
746
|
+
// use the same defaults as in V.prototype.text
|
|
747
|
+
if (styles.lineHeight === 'auto') {
|
|
748
|
+
lineHeight = getLineHeight({ value: 1.5, unit: 'em' }, textElement);
|
|
749
|
+
} else {
|
|
750
|
+
const parsed = parseCssNumeric(styles.lineHeight, ['em', 'px', '']);
|
|
751
|
+
|
|
752
|
+
lineHeight = getLineHeight(parsed, textElement);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (lineHeight * lines.length > height) {
|
|
757
|
+
// remove overflowing lines
|
|
758
|
+
lastL = Math.floor(height / lineHeight) - 1;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (lastL !== null) {
|
|
763
|
+
|
|
764
|
+
lines.splice(lastL + 1);
|
|
765
|
+
|
|
766
|
+
// add ellipsis
|
|
767
|
+
var ellipsis = opt.ellipsis;
|
|
768
|
+
if (!ellipsis || lastL < 0) break;
|
|
769
|
+
if (typeof ellipsis !== 'string') ellipsis = '\u2026';
|
|
770
|
+
|
|
771
|
+
var lastLine = lines[lastL];
|
|
772
|
+
if (!lastLine && !isEol) break;
|
|
773
|
+
var k = lastLine.length;
|
|
774
|
+
var lastLineWithOmission, lastChar;
|
|
775
|
+
do {
|
|
776
|
+
lastChar = lastLine[k];
|
|
777
|
+
lastLineWithOmission = lastLine.substring(0, k);
|
|
778
|
+
if (!lastChar) {
|
|
779
|
+
lastLineWithOmission += separatorChar;
|
|
780
|
+
} else if (lastChar.match(separator)) {
|
|
781
|
+
lastLineWithOmission += lastChar;
|
|
782
|
+
}
|
|
783
|
+
lastLineWithOmission += ellipsis;
|
|
784
|
+
textNode.data = lastLineWithOmission;
|
|
785
|
+
if (textSpan.getComputedTextLength() <= width) {
|
|
786
|
+
lines[lastL] = lastLineWithOmission;
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
k--;
|
|
790
|
+
} while (k >= 0);
|
|
791
|
+
break;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (opt.svgDocument) {
|
|
796
|
+
|
|
797
|
+
// svg document was provided, remove the text element only
|
|
798
|
+
svgDocument.removeChild(textElement);
|
|
799
|
+
|
|
800
|
+
} else {
|
|
801
|
+
|
|
802
|
+
// clean svg document
|
|
803
|
+
document.body.removeChild(svgDocument);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return lines.join(eol);
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
// Sanitize HTML
|
|
810
|
+
// Based on https://gist.github.com/ufologist/5a0da51b2b9ef1b861c30254172ac3c9
|
|
811
|
+
// Parses a string into an array of DOM nodes.
|
|
812
|
+
// Then outputs it back as a string.
|
|
813
|
+
export const sanitizeHTML = function(html) {
|
|
814
|
+
|
|
815
|
+
// Ignores tags that are invalid inside a <div> tag (e.g. <body>, <head>)
|
|
816
|
+
const [outputEl] = $.parseHTML('<div>' + html + '</div>');
|
|
817
|
+
|
|
818
|
+
Array.from(outputEl.getElementsByTagName('*')).forEach(function(node) { // for all nodes
|
|
819
|
+
const names = node.getAttributeNames();
|
|
820
|
+
names.forEach(function(name) {
|
|
821
|
+
const value = node.getAttribute(name);
|
|
822
|
+
// Remove attribute names that start with "on" (e.g. onload, onerror...).
|
|
823
|
+
// Remove attribute values that start with "javascript:" pseudo protocol (e.g. `href="javascript:alert(1)"`).
|
|
824
|
+
if (name.startsWith('on') || value.startsWith('javascript:' || value.startsWith('data:') || value.startsWith('vbscript:'))) {
|
|
825
|
+
node.removeAttribute(name);
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
return outputEl.innerHTML;
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
// Download `blob` as file with `fileName`.
|
|
834
|
+
// Does not work in IE9.
|
|
835
|
+
export const downloadBlob = function(blob, fileName) {
|
|
836
|
+
|
|
837
|
+
if (window.navigator.msSaveBlob) { // requires IE 10+
|
|
838
|
+
// pulls up a save dialog
|
|
839
|
+
window.navigator.msSaveBlob(blob, fileName);
|
|
840
|
+
|
|
841
|
+
} else { // other browsers
|
|
842
|
+
// downloads directly in Chrome and Safari
|
|
843
|
+
|
|
844
|
+
// presents a save/open dialog in Firefox
|
|
845
|
+
// Firefox bug: `from` field in save dialog always shows `from:blob:`
|
|
846
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1053327
|
|
847
|
+
|
|
848
|
+
var url = window.URL.createObjectURL(blob);
|
|
849
|
+
var link = document.createElement('a');
|
|
850
|
+
|
|
851
|
+
link.href = url;
|
|
852
|
+
link.download = fileName;
|
|
853
|
+
document.body.appendChild(link);
|
|
854
|
+
|
|
855
|
+
link.click();
|
|
856
|
+
|
|
857
|
+
document.body.removeChild(link);
|
|
858
|
+
window.URL.revokeObjectURL(url); // mark the url for garbage collection
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
// Download `dataUri` as file with `fileName`.
|
|
863
|
+
// Does not work in IE9.
|
|
864
|
+
export const downloadDataUri = function(dataUri, fileName) {
|
|
865
|
+
|
|
866
|
+
const blob = dataUriToBlob(dataUri);
|
|
867
|
+
downloadBlob(blob, fileName);
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// Convert an uri-encoded data component (possibly also base64-encoded) to a blob.
|
|
871
|
+
export const dataUriToBlob = function(dataUri) {
|
|
872
|
+
|
|
873
|
+
// first, make sure there are no newlines in the data uri
|
|
874
|
+
dataUri = dataUri.replace(/\s/g, '');
|
|
875
|
+
dataUri = decodeURIComponent(dataUri);
|
|
876
|
+
|
|
877
|
+
var firstCommaIndex = dataUri.indexOf(','); // split dataUri as `dataTypeString`,`data`
|
|
878
|
+
|
|
879
|
+
var dataTypeString = dataUri.slice(0, firstCommaIndex); // e.g. 'data:image/jpeg;base64'
|
|
880
|
+
var mimeString = dataTypeString.split(':')[1].split(';')[0]; // e.g. 'image/jpeg'
|
|
881
|
+
|
|
882
|
+
var data = dataUri.slice(firstCommaIndex + 1);
|
|
883
|
+
var decodedString;
|
|
884
|
+
if (dataTypeString.indexOf('base64') >= 0) { // data may be encoded in base64
|
|
885
|
+
decodedString = atob(data); // decode data
|
|
886
|
+
} else {
|
|
887
|
+
// convert the decoded string to UTF-8
|
|
888
|
+
decodedString = unescape(encodeURIComponent(data));
|
|
889
|
+
}
|
|
890
|
+
// write the bytes of the string to a typed array
|
|
891
|
+
var ia = new Uint8Array(decodedString.length);
|
|
892
|
+
for (var i = 0; i < decodedString.length; i++) {
|
|
893
|
+
ia[i] = decodedString.charCodeAt(i);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return new Blob([ia], { type: mimeString }); // return the typed array as Blob
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// Read an image at `url` and return it as base64-encoded data uri.
|
|
900
|
+
// The mime type of the image is inferred from the `url` file extension.
|
|
901
|
+
// If data uri is provided as `url`, it is returned back unchanged.
|
|
902
|
+
// `callback` is a method with `err` as first argument and `dataUri` as second argument.
|
|
903
|
+
// Works with IE9.
|
|
904
|
+
export const imageToDataUri = function(url, callback) {
|
|
905
|
+
|
|
906
|
+
if (!url || url.substr(0, 'data:'.length) === 'data:') {
|
|
907
|
+
// No need to convert to data uri if it is already in data uri.
|
|
908
|
+
|
|
909
|
+
// This not only convenient but desired. For example,
|
|
910
|
+
// IE throws a security error if data:image/svg+xml is used to render
|
|
911
|
+
// an image to the canvas and an attempt is made to read out data uri.
|
|
912
|
+
// Now if our image is already in data uri, there is no need to render it to the canvas
|
|
913
|
+
// and so we can bypass this error.
|
|
914
|
+
|
|
915
|
+
// Keep the async nature of the function.
|
|
916
|
+
return setTimeout(function() {
|
|
917
|
+
callback(null, url);
|
|
918
|
+
}, 0);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// chrome, IE10+
|
|
922
|
+
var modernHandler = function(xhr, callback) {
|
|
923
|
+
|
|
924
|
+
if (xhr.status === 200) {
|
|
925
|
+
|
|
926
|
+
var reader = new FileReader();
|
|
927
|
+
|
|
928
|
+
reader.onload = function(evt) {
|
|
929
|
+
var dataUri = evt.target.result;
|
|
930
|
+
callback(null, dataUri);
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
reader.onerror = function() {
|
|
934
|
+
callback(new Error('Failed to load image ' + url));
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
reader.readAsDataURL(xhr.response);
|
|
938
|
+
} else {
|
|
939
|
+
callback(new Error('Failed to load image ' + url));
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
var legacyHandler = function(xhr, callback) {
|
|
944
|
+
|
|
945
|
+
var Uint8ToString = function(u8a) {
|
|
946
|
+
var CHUNK_SZ = 0x8000;
|
|
947
|
+
var c = [];
|
|
948
|
+
for (var i = 0; i < u8a.length; i += CHUNK_SZ) {
|
|
949
|
+
c.push(String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)));
|
|
950
|
+
}
|
|
951
|
+
return c.join('');
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
if (xhr.status === 200) {
|
|
955
|
+
|
|
956
|
+
var bytes = new Uint8Array(xhr.response);
|
|
957
|
+
|
|
958
|
+
var suffix = (url.split('.').pop()) || 'png';
|
|
959
|
+
var map = {
|
|
960
|
+
'svg': 'svg+xml'
|
|
961
|
+
};
|
|
962
|
+
var meta = 'data:image/' + (map[suffix] || suffix) + ';base64,';
|
|
963
|
+
var b64encoded = meta + btoa(Uint8ToString(bytes));
|
|
964
|
+
callback(null, b64encoded);
|
|
965
|
+
} else {
|
|
966
|
+
callback(new Error('Failed to load image ' + url));
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
var xhr = new XMLHttpRequest();
|
|
971
|
+
|
|
972
|
+
xhr.open('GET', url, true);
|
|
973
|
+
xhr.addEventListener('error', function() {
|
|
974
|
+
callback(new Error('Failed to load image ' + url));
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
xhr.responseType = window.FileReader ? 'blob' : 'arraybuffer';
|
|
978
|
+
|
|
979
|
+
xhr.addEventListener('load', function() {
|
|
980
|
+
if (window.FileReader) {
|
|
981
|
+
modernHandler(xhr, callback);
|
|
982
|
+
} else {
|
|
983
|
+
legacyHandler(xhr, callback);
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
xhr.send();
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
export const getElementBBox = function(el) {
|
|
991
|
+
|
|
992
|
+
var $el = $(el);
|
|
993
|
+
if ($el.length === 0) {
|
|
994
|
+
throw new Error('Element not found');
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
var element = $el[0];
|
|
998
|
+
var doc = element.ownerDocument;
|
|
999
|
+
var clientBBox = element.getBoundingClientRect();
|
|
1000
|
+
|
|
1001
|
+
var strokeWidthX = 0;
|
|
1002
|
+
var strokeWidthY = 0;
|
|
1003
|
+
|
|
1004
|
+
// Firefox correction
|
|
1005
|
+
if (element.ownerSVGElement) {
|
|
1006
|
+
|
|
1007
|
+
var vel = V(element);
|
|
1008
|
+
var bbox = vel.getBBox({ target: vel.svg() });
|
|
1009
|
+
|
|
1010
|
+
// if FF getBoundingClientRect includes stroke-width, getBBox doesn't.
|
|
1011
|
+
// To unify this across all browsers we need to adjust the final bBox with `stroke-width` value.
|
|
1012
|
+
strokeWidthX = (clientBBox.width - bbox.width);
|
|
1013
|
+
strokeWidthY = (clientBBox.height - bbox.height);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
return {
|
|
1017
|
+
x: clientBBox.left + window.pageXOffset - doc.documentElement.offsetLeft + strokeWidthX / 2,
|
|
1018
|
+
y: clientBBox.top + window.pageYOffset - doc.documentElement.offsetTop + strokeWidthY / 2,
|
|
1019
|
+
width: clientBBox.width - strokeWidthX,
|
|
1020
|
+
height: clientBBox.height - strokeWidthY
|
|
1021
|
+
};
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
|
|
1025
|
+
// Highly inspired by the jquery.sortElements plugin by Padolsey.
|
|
1026
|
+
// See http://james.padolsey.com/javascript/sorting-elements-with-jquery/.
|
|
1027
|
+
export const sortElements = function(elements, comparator) {
|
|
1028
|
+
|
|
1029
|
+
elements = $(elements).toArray();
|
|
1030
|
+
var placements = elements.map(function(sortElement) {
|
|
1031
|
+
|
|
1032
|
+
var parentNode = sortElement.parentNode;
|
|
1033
|
+
// Since the element itself will change position, we have
|
|
1034
|
+
// to have some way of storing it's original position in
|
|
1035
|
+
// the DOM. The easiest way is to have a 'flag' node:
|
|
1036
|
+
var nextSibling = parentNode.insertBefore(document.createTextNode(''), sortElement.nextSibling);
|
|
1037
|
+
|
|
1038
|
+
return function() {
|
|
1039
|
+
|
|
1040
|
+
if (parentNode === this) {
|
|
1041
|
+
throw new Error('You can\'t sort elements if any one is a descendant of another.');
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Insert before flag:
|
|
1045
|
+
parentNode.insertBefore(this, nextSibling);
|
|
1046
|
+
// Remove flag:
|
|
1047
|
+
parentNode.removeChild(nextSibling);
|
|
1048
|
+
};
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
elements.sort(comparator);
|
|
1052
|
+
for (var i = 0; i < placements.length; i++) {
|
|
1053
|
+
placements[i].call(elements[i]);
|
|
1054
|
+
}
|
|
1055
|
+
return elements;
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
// Sets attributes on the given element and its descendants based on the selector.
|
|
1059
|
+
// `attrs` object: { [SELECTOR1]: { attrs1 }, [SELECTOR2]: { attrs2}, ... } e.g. { 'input': { color : 'red' }}
|
|
1060
|
+
export const setAttributesBySelector = function(element, attrs) {
|
|
1061
|
+
|
|
1062
|
+
var $element = $(element);
|
|
1063
|
+
|
|
1064
|
+
forIn(attrs, function(attrs, selector) {
|
|
1065
|
+
var $elements = $element.find(selector).addBack().filter(selector);
|
|
1066
|
+
// Make a special case for setting classes.
|
|
1067
|
+
// We do not want to overwrite any existing class.
|
|
1068
|
+
if (has(attrs, 'class')) {
|
|
1069
|
+
$elements.addClass(attrs['class']);
|
|
1070
|
+
attrs = omit(attrs, 'class');
|
|
1071
|
+
}
|
|
1072
|
+
$elements.attr(attrs);
|
|
1073
|
+
});
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1076
|
+
// Return a new object with all four sides (top, right, bottom, left) in it.
|
|
1077
|
+
// Value of each side is taken from the given argument (either number or object).
|
|
1078
|
+
// Default value for a side is 0.
|
|
1079
|
+
// Examples:
|
|
1080
|
+
// normalizeSides(5) --> { top: 5, right: 5, bottom: 5, left: 5 }
|
|
1081
|
+
// normalizeSides({ horizontal: 5 }) --> { top: 0, right: 5, bottom: 0, left: 5 }
|
|
1082
|
+
// normalizeSides({ left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 }
|
|
1083
|
+
// normalizeSides({ horizontal: 10, left: 5 }) --> { top: 0, right: 10, bottom: 0, left: 5 }
|
|
1084
|
+
// normalizeSides({ horizontal: 0, left: 5 }) --> { top: 0, right: 0, bottom: 0, left: 5 }
|
|
1085
|
+
export const normalizeSides = function(box) {
|
|
1086
|
+
|
|
1087
|
+
if (Object(box) !== box) { // `box` is not an object
|
|
1088
|
+
var val = 0; // `val` left as 0 if `box` cannot be understood as finite number
|
|
1089
|
+
if (isFinite(box)) val = +box; // actually also accepts string numbers (e.g. '100')
|
|
1090
|
+
|
|
1091
|
+
return { top: val, right: val, bottom: val, left: val };
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// `box` is an object
|
|
1095
|
+
var top, right, bottom, left;
|
|
1096
|
+
top = right = bottom = left = 0;
|
|
1097
|
+
|
|
1098
|
+
if (isFinite(box.vertical)) top = bottom = +box.vertical;
|
|
1099
|
+
if (isFinite(box.horizontal)) right = left = +box.horizontal;
|
|
1100
|
+
|
|
1101
|
+
if (isFinite(box.top)) top = +box.top; // overwrite vertical
|
|
1102
|
+
if (isFinite(box.right)) right = +box.right; // overwrite horizontal
|
|
1103
|
+
if (isFinite(box.bottom)) bottom = +box.bottom; // overwrite vertical
|
|
1104
|
+
if (isFinite(box.left)) left = +box.left; // overwrite horizontal
|
|
1105
|
+
|
|
1106
|
+
return { top: top, right: right, bottom: bottom, left: left };
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
export const timing = {
|
|
1110
|
+
|
|
1111
|
+
linear: function(t) {
|
|
1112
|
+
return t;
|
|
1113
|
+
},
|
|
1114
|
+
|
|
1115
|
+
quad: function(t) {
|
|
1116
|
+
return t * t;
|
|
1117
|
+
},
|
|
1118
|
+
|
|
1119
|
+
cubic: function(t) {
|
|
1120
|
+
return t * t * t;
|
|
1121
|
+
},
|
|
1122
|
+
|
|
1123
|
+
inout: function(t) {
|
|
1124
|
+
if (t <= 0) return 0;
|
|
1125
|
+
if (t >= 1) return 1;
|
|
1126
|
+
var t2 = t * t;
|
|
1127
|
+
var t3 = t2 * t;
|
|
1128
|
+
return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
|
|
1129
|
+
},
|
|
1130
|
+
|
|
1131
|
+
exponential: function(t) {
|
|
1132
|
+
return Math.pow(2, 10 * (t - 1));
|
|
1133
|
+
},
|
|
1134
|
+
|
|
1135
|
+
bounce: function(t) {
|
|
1136
|
+
for (var a = 0, b = 1; 1; a += b, b /= 2) {
|
|
1137
|
+
if (t >= (7 - 4 * a) / 11) {
|
|
1138
|
+
var q = (11 - 6 * a - 11 * t) / 4;
|
|
1139
|
+
return -q * q + b * b;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
|
|
1144
|
+
reverse: function(f) {
|
|
1145
|
+
return function(t) {
|
|
1146
|
+
return 1 - f(1 - t);
|
|
1147
|
+
};
|
|
1148
|
+
},
|
|
1149
|
+
|
|
1150
|
+
reflect: function(f) {
|
|
1151
|
+
return function(t) {
|
|
1152
|
+
return .5 * (t < .5 ? f(2 * t) : (2 - f(2 - 2 * t)));
|
|
1153
|
+
};
|
|
1154
|
+
},
|
|
1155
|
+
|
|
1156
|
+
clamp: function(f, n, x) {
|
|
1157
|
+
n = n || 0;
|
|
1158
|
+
x = x || 1;
|
|
1159
|
+
return function(t) {
|
|
1160
|
+
var r = f(t);
|
|
1161
|
+
return r < n ? n : r > x ? x : r;
|
|
1162
|
+
};
|
|
1163
|
+
},
|
|
1164
|
+
|
|
1165
|
+
back: function(s) {
|
|
1166
|
+
if (!s) s = 1.70158;
|
|
1167
|
+
return function(t) {
|
|
1168
|
+
return t * t * ((s + 1) * t - s);
|
|
1169
|
+
};
|
|
1170
|
+
},
|
|
1171
|
+
|
|
1172
|
+
elastic: function(x) {
|
|
1173
|
+
if (!x) x = 1.5;
|
|
1174
|
+
return function(t) {
|
|
1175
|
+
return Math.pow(2, 10 * (t - 1)) * Math.cos(20 * Math.PI * x / 3 * t);
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
export const interpolate = {
|
|
1181
|
+
|
|
1182
|
+
number: function(a, b) {
|
|
1183
|
+
var d = b - a;
|
|
1184
|
+
return function(t) {
|
|
1185
|
+
return a + d * t;
|
|
1186
|
+
};
|
|
1187
|
+
},
|
|
1188
|
+
|
|
1189
|
+
object: function(a, b) {
|
|
1190
|
+
var s = Object.keys(a);
|
|
1191
|
+
return function(t) {
|
|
1192
|
+
var i, p;
|
|
1193
|
+
var r = {};
|
|
1194
|
+
for (i = s.length - 1; i != -1; i--) {
|
|
1195
|
+
p = s[i];
|
|
1196
|
+
r[p] = a[p] + (b[p] - a[p]) * t;
|
|
1197
|
+
}
|
|
1198
|
+
return r;
|
|
1199
|
+
};
|
|
1200
|
+
},
|
|
1201
|
+
|
|
1202
|
+
hexColor: function(a, b) {
|
|
1203
|
+
|
|
1204
|
+
var ca = parseInt(a.slice(1), 16);
|
|
1205
|
+
var cb = parseInt(b.slice(1), 16);
|
|
1206
|
+
var ra = ca & 0x0000ff;
|
|
1207
|
+
var rd = (cb & 0x0000ff) - ra;
|
|
1208
|
+
var ga = ca & 0x00ff00;
|
|
1209
|
+
var gd = (cb & 0x00ff00) - ga;
|
|
1210
|
+
var ba = ca & 0xff0000;
|
|
1211
|
+
var bd = (cb & 0xff0000) - ba;
|
|
1212
|
+
|
|
1213
|
+
return function(t) {
|
|
1214
|
+
|
|
1215
|
+
var r = (ra + rd * t) & 0x000000ff;
|
|
1216
|
+
var g = (ga + gd * t) & 0x0000ff00;
|
|
1217
|
+
var b = (ba + bd * t) & 0x00ff0000;
|
|
1218
|
+
|
|
1219
|
+
return '#' + (1 << 24 | r | g | b).toString(16).slice(1);
|
|
1220
|
+
};
|
|
1221
|
+
},
|
|
1222
|
+
|
|
1223
|
+
unit: function(a, b) {
|
|
1224
|
+
|
|
1225
|
+
var r = /(-?[0-9]*.[0-9]*)(px|em|cm|mm|in|pt|pc|%)/;
|
|
1226
|
+
var ma = r.exec(a);
|
|
1227
|
+
var mb = r.exec(b);
|
|
1228
|
+
var p = mb[1].indexOf('.');
|
|
1229
|
+
var f = p > 0 ? mb[1].length - p - 1 : 0;
|
|
1230
|
+
a = +ma[1];
|
|
1231
|
+
var d = +mb[1] - a;
|
|
1232
|
+
var u = ma[2];
|
|
1233
|
+
|
|
1234
|
+
return function(t) {
|
|
1235
|
+
return (a + d * t).toFixed(f) + u;
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
// SVG filters.
|
|
1241
|
+
// (values in parentheses are default values)
|
|
1242
|
+
export const filter = {
|
|
1243
|
+
|
|
1244
|
+
// `color` ... outline color ('blue')
|
|
1245
|
+
// `width`... outline width (1)
|
|
1246
|
+
// `opacity` ... outline opacity (1)
|
|
1247
|
+
// `margin` ... gap between outline and the element (2)
|
|
1248
|
+
outline: function(args) {
|
|
1249
|
+
|
|
1250
|
+
var tpl = '<filter><feFlood flood-color="${color}" flood-opacity="${opacity}" result="colored"/><feMorphology in="SourceAlpha" result="morphedOuter" operator="dilate" radius="${outerRadius}" /><feMorphology in="SourceAlpha" result="morphedInner" operator="dilate" radius="${innerRadius}" /><feComposite result="morphedOuterColored" in="colored" in2="morphedOuter" operator="in"/><feComposite operator="xor" in="morphedOuterColored" in2="morphedInner" result="outline"/><feMerge><feMergeNode in="outline"/><feMergeNode in="SourceGraphic"/></feMerge></filter>';
|
|
1251
|
+
|
|
1252
|
+
var margin = Number.isFinite(args.margin) ? args.margin : 2;
|
|
1253
|
+
var width = Number.isFinite(args.width) ? args.width : 1;
|
|
1254
|
+
|
|
1255
|
+
return template(tpl)({
|
|
1256
|
+
color: args.color || 'blue',
|
|
1257
|
+
opacity: Number.isFinite(args.opacity) ? args.opacity : 1,
|
|
1258
|
+
outerRadius: margin + width,
|
|
1259
|
+
innerRadius: margin
|
|
1260
|
+
});
|
|
1261
|
+
},
|
|
1262
|
+
|
|
1263
|
+
// `color` ... color ('red')
|
|
1264
|
+
// `width`... width (1)
|
|
1265
|
+
// `blur` ... blur (0)
|
|
1266
|
+
// `opacity` ... opacity (1)
|
|
1267
|
+
highlight: function(args) {
|
|
1268
|
+
|
|
1269
|
+
var tpl = '<filter><feFlood flood-color="${color}" flood-opacity="${opacity}" result="colored"/><feMorphology result="morphed" in="SourceGraphic" operator="dilate" radius="${width}"/><feComposite result="composed" in="colored" in2="morphed" operator="in"/><feGaussianBlur result="blured" in="composed" stdDeviation="${blur}"/><feBlend in="SourceGraphic" in2="blured" mode="normal"/></filter>';
|
|
1270
|
+
|
|
1271
|
+
return template(tpl)({
|
|
1272
|
+
color: args.color || 'red',
|
|
1273
|
+
width: Number.isFinite(args.width) ? args.width : 1,
|
|
1274
|
+
blur: Number.isFinite(args.blur) ? args.blur : 0,
|
|
1275
|
+
opacity: Number.isFinite(args.opacity) ? args.opacity : 1
|
|
1276
|
+
});
|
|
1277
|
+
},
|
|
1278
|
+
|
|
1279
|
+
// `x` ... horizontal blur (2)
|
|
1280
|
+
// `y` ... vertical blur (optional)
|
|
1281
|
+
blur: function(args) {
|
|
1282
|
+
|
|
1283
|
+
var x = Number.isFinite(args.x) ? args.x : 2;
|
|
1284
|
+
|
|
1285
|
+
return template('<filter><feGaussianBlur stdDeviation="${stdDeviation}"/></filter>')({
|
|
1286
|
+
stdDeviation: Number.isFinite(args.y) ? [x, args.y] : x
|
|
1287
|
+
});
|
|
1288
|
+
},
|
|
1289
|
+
|
|
1290
|
+
// `dx` ... horizontal shift (0)
|
|
1291
|
+
// `dy` ... vertical shift (0)
|
|
1292
|
+
// `blur` ... blur (4)
|
|
1293
|
+
// `color` ... color ('black')
|
|
1294
|
+
// `opacity` ... opacity (1)
|
|
1295
|
+
dropShadow: function(args) {
|
|
1296
|
+
|
|
1297
|
+
var tpl = 'SVGFEDropShadowElement' in window
|
|
1298
|
+
? '<filter><feDropShadow stdDeviation="${blur}" dx="${dx}" dy="${dy}" flood-color="${color}" flood-opacity="${opacity}"/></filter>'
|
|
1299
|
+
: '<filter><feGaussianBlur in="SourceAlpha" stdDeviation="${blur}"/><feOffset dx="${dx}" dy="${dy}" result="offsetblur"/><feFlood flood-color="${color}"/><feComposite in2="offsetblur" operator="in"/><feComponentTransfer><feFuncA type="linear" slope="${opacity}"/></feComponentTransfer><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter>';
|
|
1300
|
+
|
|
1301
|
+
return template(tpl)({
|
|
1302
|
+
dx: args.dx || 0,
|
|
1303
|
+
dy: args.dy || 0,
|
|
1304
|
+
opacity: Number.isFinite(args.opacity) ? args.opacity : 1,
|
|
1305
|
+
color: args.color || 'black',
|
|
1306
|
+
blur: Number.isFinite(args.blur) ? args.blur : 4
|
|
1307
|
+
});
|
|
1308
|
+
},
|
|
1309
|
+
|
|
1310
|
+
// `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely grayscale. A value of 0 leaves the input unchanged.
|
|
1311
|
+
grayscale: function(args) {
|
|
1312
|
+
|
|
1313
|
+
var amount = Number.isFinite(args.amount) ? args.amount : 1;
|
|
1314
|
+
|
|
1315
|
+
return template('<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 0 ${d} ${e} ${f} 0 0 ${g} ${b} ${h} 0 0 0 0 0 1 0"/></filter>')({
|
|
1316
|
+
a: 0.2126 + 0.7874 * (1 - amount),
|
|
1317
|
+
b: 0.7152 - 0.7152 * (1 - amount),
|
|
1318
|
+
c: 0.0722 - 0.0722 * (1 - amount),
|
|
1319
|
+
d: 0.2126 - 0.2126 * (1 - amount),
|
|
1320
|
+
e: 0.7152 + 0.2848 * (1 - amount),
|
|
1321
|
+
f: 0.0722 - 0.0722 * (1 - amount),
|
|
1322
|
+
g: 0.2126 - 0.2126 * (1 - amount),
|
|
1323
|
+
h: 0.0722 + 0.9278 * (1 - amount)
|
|
1324
|
+
});
|
|
1325
|
+
},
|
|
1326
|
+
|
|
1327
|
+
// `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely sepia. A value of 0 leaves the input unchanged.
|
|
1328
|
+
sepia: function(args) {
|
|
1329
|
+
|
|
1330
|
+
var amount = Number.isFinite(args.amount) ? args.amount : 1;
|
|
1331
|
+
|
|
1332
|
+
return template('<filter><feColorMatrix type="matrix" values="${a} ${b} ${c} 0 0 ${d} ${e} ${f} 0 0 ${g} ${h} ${i} 0 0 0 0 0 1 0"/></filter>')({
|
|
1333
|
+
a: 0.393 + 0.607 * (1 - amount),
|
|
1334
|
+
b: 0.769 - 0.769 * (1 - amount),
|
|
1335
|
+
c: 0.189 - 0.189 * (1 - amount),
|
|
1336
|
+
d: 0.349 - 0.349 * (1 - amount),
|
|
1337
|
+
e: 0.686 + 0.314 * (1 - amount),
|
|
1338
|
+
f: 0.168 - 0.168 * (1 - amount),
|
|
1339
|
+
g: 0.272 - 0.272 * (1 - amount),
|
|
1340
|
+
h: 0.534 - 0.534 * (1 - amount),
|
|
1341
|
+
i: 0.131 + 0.869 * (1 - amount)
|
|
1342
|
+
});
|
|
1343
|
+
},
|
|
1344
|
+
|
|
1345
|
+
// `amount` ... the proportion of the conversion (1). A value of 0 is completely un-saturated. A value of 1 (default) leaves the input unchanged.
|
|
1346
|
+
saturate: function(args) {
|
|
1347
|
+
|
|
1348
|
+
var amount = Number.isFinite(args.amount) ? args.amount : 1;
|
|
1349
|
+
|
|
1350
|
+
return template('<filter><feColorMatrix type="saturate" values="${amount}"/></filter>')({
|
|
1351
|
+
amount: 1 - amount
|
|
1352
|
+
});
|
|
1353
|
+
},
|
|
1354
|
+
|
|
1355
|
+
// `angle` ... the number of degrees around the color circle the input samples will be adjusted (0).
|
|
1356
|
+
hueRotate: function(args) {
|
|
1357
|
+
|
|
1358
|
+
return template('<filter><feColorMatrix type="hueRotate" values="${angle}"/></filter>')({
|
|
1359
|
+
angle: args.angle || 0
|
|
1360
|
+
});
|
|
1361
|
+
},
|
|
1362
|
+
|
|
1363
|
+
// `amount` ... the proportion of the conversion (1). A value of 1 (default) is completely inverted. A value of 0 leaves the input unchanged.
|
|
1364
|
+
invert: function(args) {
|
|
1365
|
+
|
|
1366
|
+
var amount = Number.isFinite(args.amount) ? args.amount : 1;
|
|
1367
|
+
|
|
1368
|
+
return template('<filter><feComponentTransfer><feFuncR type="table" tableValues="${amount} ${amount2}"/><feFuncG type="table" tableValues="${amount} ${amount2}"/><feFuncB type="table" tableValues="${amount} ${amount2}"/></feComponentTransfer></filter>')({
|
|
1369
|
+
amount: amount,
|
|
1370
|
+
amount2: 1 - amount
|
|
1371
|
+
});
|
|
1372
|
+
},
|
|
1373
|
+
|
|
1374
|
+
// `amount` ... proportion of the conversion (1). A value of 0 will create an image that is completely black. A value of 1 (default) leaves the input unchanged.
|
|
1375
|
+
brightness: function(args) {
|
|
1376
|
+
|
|
1377
|
+
return template('<filter><feComponentTransfer><feFuncR type="linear" slope="${amount}"/><feFuncG type="linear" slope="${amount}"/><feFuncB type="linear" slope="${amount}"/></feComponentTransfer></filter>')({
|
|
1378
|
+
amount: Number.isFinite(args.amount) ? args.amount : 1
|
|
1379
|
+
});
|
|
1380
|
+
},
|
|
1381
|
+
|
|
1382
|
+
// `amount` ... proportion of the conversion (1). A value of 0 will create an image that is completely black. A value of 1 (default) leaves the input unchanged.
|
|
1383
|
+
contrast: function(args) {
|
|
1384
|
+
|
|
1385
|
+
var amount = Number.isFinite(args.amount) ? args.amount : 1;
|
|
1386
|
+
|
|
1387
|
+
return template('<filter><feComponentTransfer><feFuncR type="linear" slope="${amount}" intercept="${amount2}"/><feFuncG type="linear" slope="${amount}" intercept="${amount2}"/><feFuncB type="linear" slope="${amount}" intercept="${amount2}"/></feComponentTransfer></filter>')({
|
|
1388
|
+
amount: amount,
|
|
1389
|
+
amount2: .5 - amount / 2
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
export const format = {
|
|
1395
|
+
|
|
1396
|
+
// Formatting numbers via the Python Format Specification Mini-language.
|
|
1397
|
+
// See http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language.
|
|
1398
|
+
// Heavilly inspired by the D3.js library implementation.
|
|
1399
|
+
number: function(specifier, value, locale) {
|
|
1400
|
+
|
|
1401
|
+
locale = locale || {
|
|
1402
|
+
|
|
1403
|
+
currency: ['$', ''],
|
|
1404
|
+
decimal: '.',
|
|
1405
|
+
thousands: ',',
|
|
1406
|
+
grouping: [3]
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
// See Python format specification mini-language: http://docs.python.org/release/3.1.3/library/string.html#format-specification-mini-language.
|
|
1410
|
+
// [[fill]align][sign][symbol][0][width][,][.precision][type]
|
|
1411
|
+
var re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
|
|
1412
|
+
|
|
1413
|
+
var match = re.exec(specifier);
|
|
1414
|
+
var fill = match[1] || ' ';
|
|
1415
|
+
var align = match[2] || '>';
|
|
1416
|
+
var sign = match[3] || '';
|
|
1417
|
+
var symbol = match[4] || '';
|
|
1418
|
+
var zfill = match[5];
|
|
1419
|
+
var width = +match[6];
|
|
1420
|
+
var comma = match[7];
|
|
1421
|
+
var precision = match[8];
|
|
1422
|
+
var type = match[9];
|
|
1423
|
+
var scale = 1;
|
|
1424
|
+
var prefix = '';
|
|
1425
|
+
var suffix = '';
|
|
1426
|
+
var integer = false;
|
|
1427
|
+
|
|
1428
|
+
if (precision) precision = +precision.substring(1);
|
|
1429
|
+
|
|
1430
|
+
if (zfill || fill === '0' && align === '=') {
|
|
1431
|
+
zfill = fill = '0';
|
|
1432
|
+
align = '=';
|
|
1433
|
+
if (comma) width -= Math.floor((width - 1) / 4);
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
switch (type) {
|
|
1437
|
+
case 'n':
|
|
1438
|
+
comma = true;
|
|
1439
|
+
type = 'g';
|
|
1440
|
+
break;
|
|
1441
|
+
case '%':
|
|
1442
|
+
scale = 100;
|
|
1443
|
+
suffix = '%';
|
|
1444
|
+
type = 'f';
|
|
1445
|
+
break;
|
|
1446
|
+
case 'p':
|
|
1447
|
+
scale = 100;
|
|
1448
|
+
suffix = '%';
|
|
1449
|
+
type = 'r';
|
|
1450
|
+
break;
|
|
1451
|
+
case 'b':
|
|
1452
|
+
case 'o':
|
|
1453
|
+
case 'x':
|
|
1454
|
+
case 'X':
|
|
1455
|
+
if (symbol === '#') prefix = '0' + type.toLowerCase();
|
|
1456
|
+
break;
|
|
1457
|
+
case 'c':
|
|
1458
|
+
case 'd':
|
|
1459
|
+
integer = true;
|
|
1460
|
+
precision = 0;
|
|
1461
|
+
break;
|
|
1462
|
+
case 's':
|
|
1463
|
+
scale = -1;
|
|
1464
|
+
type = 'r';
|
|
1465
|
+
break;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
if (symbol === '$') {
|
|
1469
|
+
prefix = locale.currency[0];
|
|
1470
|
+
suffix = locale.currency[1];
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// If no precision is specified for `'r'`, fallback to general notation.
|
|
1474
|
+
if (type == 'r' && !precision) type = 'g';
|
|
1475
|
+
|
|
1476
|
+
// Ensure that the requested precision is in the supported range.
|
|
1477
|
+
if (precision != null) {
|
|
1478
|
+
if (type == 'g') precision = Math.max(1, Math.min(21, precision));
|
|
1479
|
+
else if (type == 'e' || type == 'f') precision = Math.max(0, Math.min(20, precision));
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
var zcomma = zfill && comma;
|
|
1483
|
+
|
|
1484
|
+
// Return the empty string for floats formatted as ints.
|
|
1485
|
+
if (integer && (value % 1)) return '';
|
|
1486
|
+
|
|
1487
|
+
// Convert negative to positive, and record the sign prefix.
|
|
1488
|
+
var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, '-') : sign;
|
|
1489
|
+
|
|
1490
|
+
var fullSuffix = suffix;
|
|
1491
|
+
|
|
1492
|
+
// Apply the scale, computing it from the value's exponent for si format.
|
|
1493
|
+
// Preserve the existing suffix, if any, such as the currency symbol.
|
|
1494
|
+
if (scale < 0) {
|
|
1495
|
+
var unit = this.prefix(value, precision);
|
|
1496
|
+
value = unit.scale(value);
|
|
1497
|
+
fullSuffix = unit.symbol + suffix;
|
|
1498
|
+
} else {
|
|
1499
|
+
value *= scale;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// Convert to the desired precision.
|
|
1503
|
+
value = this.convert(type, value, precision);
|
|
1504
|
+
|
|
1505
|
+
// Break the value into the integer part (before) and decimal part (after).
|
|
1506
|
+
var i = value.lastIndexOf('.');
|
|
1507
|
+
var before = i < 0 ? value : value.substring(0, i);
|
|
1508
|
+
var after = i < 0 ? '' : locale.decimal + value.substring(i + 1);
|
|
1509
|
+
|
|
1510
|
+
function formatGroup(value) {
|
|
1511
|
+
|
|
1512
|
+
var i = value.length;
|
|
1513
|
+
var t = [];
|
|
1514
|
+
var j = 0;
|
|
1515
|
+
var g = locale.grouping[0];
|
|
1516
|
+
while (i > 0 && g > 0) {
|
|
1517
|
+
t.push(value.substring(i -= g, i + g));
|
|
1518
|
+
g = locale.grouping[j = (j + 1) % locale.grouping.length];
|
|
1519
|
+
}
|
|
1520
|
+
return t.reverse().join(locale.thousands);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// If the fill character is not `'0'`, grouping is applied before padding.
|
|
1524
|
+
if (!zfill && comma && locale.grouping) {
|
|
1525
|
+
|
|
1526
|
+
before = formatGroup(before);
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length);
|
|
1530
|
+
var padding = length < width ? new Array(length = width - length + 1).join(fill) : '';
|
|
1531
|
+
|
|
1532
|
+
// If the fill character is `'0'`, grouping is applied after padding.
|
|
1533
|
+
if (zcomma) before = formatGroup(padding + before);
|
|
1534
|
+
|
|
1535
|
+
// Apply prefix.
|
|
1536
|
+
negative += prefix;
|
|
1537
|
+
|
|
1538
|
+
// Rejoin integer and decimal parts.
|
|
1539
|
+
value = before + after;
|
|
1540
|
+
|
|
1541
|
+
return (align === '<' ? negative + value + padding
|
|
1542
|
+
: align === '>' ? padding + negative + value
|
|
1543
|
+
: align === '^' ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length)
|
|
1544
|
+
: negative + (zcomma ? value : padding + value)) + fullSuffix;
|
|
1545
|
+
},
|
|
1546
|
+
|
|
1547
|
+
// Formatting string via the Python Format string.
|
|
1548
|
+
// See https://docs.python.org/2/library/string.html#format-string-syntax)
|
|
1549
|
+
string: function(formatString, value) {
|
|
1550
|
+
|
|
1551
|
+
var fieldDelimiterIndex;
|
|
1552
|
+
var fieldDelimiter = '{';
|
|
1553
|
+
var endPlaceholder = false;
|
|
1554
|
+
var formattedStringArray = [];
|
|
1555
|
+
|
|
1556
|
+
while ((fieldDelimiterIndex = formatString.indexOf(fieldDelimiter)) !== -1) {
|
|
1557
|
+
|
|
1558
|
+
var pieceFormattedString, formatSpec, fieldName;
|
|
1559
|
+
|
|
1560
|
+
pieceFormattedString = formatString.slice(0, fieldDelimiterIndex);
|
|
1561
|
+
|
|
1562
|
+
if (endPlaceholder) {
|
|
1563
|
+
formatSpec = pieceFormattedString.split(':');
|
|
1564
|
+
fieldName = formatSpec.shift().split('.');
|
|
1565
|
+
pieceFormattedString = value;
|
|
1566
|
+
|
|
1567
|
+
for (var i = 0; i < fieldName.length; i++)
|
|
1568
|
+
pieceFormattedString = pieceFormattedString[fieldName[i]];
|
|
1569
|
+
|
|
1570
|
+
if (formatSpec.length)
|
|
1571
|
+
pieceFormattedString = this.number(formatSpec, pieceFormattedString);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
formattedStringArray.push(pieceFormattedString);
|
|
1575
|
+
|
|
1576
|
+
formatString = formatString.slice(fieldDelimiterIndex + 1);
|
|
1577
|
+
endPlaceholder = !endPlaceholder;
|
|
1578
|
+
fieldDelimiter = (endPlaceholder) ? '}' : '{';
|
|
1579
|
+
}
|
|
1580
|
+
formattedStringArray.push(formatString);
|
|
1581
|
+
|
|
1582
|
+
return formattedStringArray.join('');
|
|
1583
|
+
},
|
|
1584
|
+
|
|
1585
|
+
convert: function(type, value, precision) {
|
|
1586
|
+
|
|
1587
|
+
switch (type) {
|
|
1588
|
+
case 'b':
|
|
1589
|
+
return value.toString(2);
|
|
1590
|
+
case 'c':
|
|
1591
|
+
return String.fromCharCode(value);
|
|
1592
|
+
case 'o':
|
|
1593
|
+
return value.toString(8);
|
|
1594
|
+
case 'x':
|
|
1595
|
+
return value.toString(16);
|
|
1596
|
+
case 'X':
|
|
1597
|
+
return value.toString(16).toUpperCase();
|
|
1598
|
+
case 'g':
|
|
1599
|
+
return value.toPrecision(precision);
|
|
1600
|
+
case 'e':
|
|
1601
|
+
return value.toExponential(precision);
|
|
1602
|
+
case 'f':
|
|
1603
|
+
return value.toFixed(precision);
|
|
1604
|
+
case 'r':
|
|
1605
|
+
return (value = this.round(value, this.precision(value, precision))).toFixed(Math.max(0, Math.min(20, this.precision(value * (1 + 1e-15), precision))));
|
|
1606
|
+
default:
|
|
1607
|
+
return value + '';
|
|
1608
|
+
}
|
|
1609
|
+
},
|
|
1610
|
+
|
|
1611
|
+
round: function(value, precision) {
|
|
1612
|
+
|
|
1613
|
+
return precision
|
|
1614
|
+
? Math.round(value * (precision = Math.pow(10, precision))) / precision
|
|
1615
|
+
: Math.round(value);
|
|
1616
|
+
},
|
|
1617
|
+
|
|
1618
|
+
precision: function(value, precision) {
|
|
1619
|
+
|
|
1620
|
+
return precision - (value ? Math.ceil(Math.log(value) / Math.LN10) : 1);
|
|
1621
|
+
},
|
|
1622
|
+
|
|
1623
|
+
prefix: function(value, precision) {
|
|
1624
|
+
|
|
1625
|
+
var prefixes = ['y', 'z', 'a', 'f', 'p', 'n', 'µ', 'm', '', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'].map(function(d, i) {
|
|
1626
|
+
var k = Math.pow(10, Math.abs(8 - i) * 3);
|
|
1627
|
+
return {
|
|
1628
|
+
scale: i > 8 ? function(d) {
|
|
1629
|
+
return d / k;
|
|
1630
|
+
} : function(d) {
|
|
1631
|
+
return d * k;
|
|
1632
|
+
},
|
|
1633
|
+
symbol: d
|
|
1634
|
+
};
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
var i = 0;
|
|
1638
|
+
if (value) {
|
|
1639
|
+
if (value < 0) value *= -1;
|
|
1640
|
+
if (precision) value = this.round(value, this.precision(value, precision));
|
|
1641
|
+
i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
|
|
1642
|
+
i = Math.max(-24, Math.min(24, Math.floor((i <= 0 ? i + 1 : i - 1) / 3) * 3));
|
|
1643
|
+
}
|
|
1644
|
+
return prefixes[8 + i / 3];
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1647
|
+
|
|
1648
|
+
/*
|
|
1649
|
+
Pre-compile the HTML to be used as a template.
|
|
1650
|
+
*/
|
|
1651
|
+
export const template = function(html) {
|
|
1652
|
+
|
|
1653
|
+
/*
|
|
1654
|
+
Must support the variation in templating syntax found here:
|
|
1655
|
+
https://lodash.com/docs#template
|
|
1656
|
+
*/
|
|
1657
|
+
var regex = /<%= ([^ ]+) %>|\$\{ ?([^{} ]+) ?\}|\{\{([^{} ]+)\}\}/g;
|
|
1658
|
+
|
|
1659
|
+
return function(data) {
|
|
1660
|
+
|
|
1661
|
+
data = data || {};
|
|
1662
|
+
|
|
1663
|
+
return html.replace(regex, function(match) {
|
|
1664
|
+
|
|
1665
|
+
var args = Array.from(arguments);
|
|
1666
|
+
var attr = args.slice(1, 4).find(function(_attr) {
|
|
1667
|
+
return !!_attr;
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
var attrArray = attr.split('.');
|
|
1671
|
+
var value = data[attrArray.shift()];
|
|
1672
|
+
|
|
1673
|
+
while (value !== undefined && attrArray.length) {
|
|
1674
|
+
value = value[attrArray.shift()];
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
return value !== undefined ? value : '';
|
|
1678
|
+
});
|
|
1679
|
+
};
|
|
1680
|
+
};
|
|
1681
|
+
|
|
1682
|
+
/**
|
|
1683
|
+
* @param {Element} el Element, which content is intent to display in full-screen mode, 'window.top.document.body' is default.
|
|
1684
|
+
*/
|
|
1685
|
+
export const toggleFullScreen = function(el) {
|
|
1686
|
+
|
|
1687
|
+
var topDocument = window.top.document;
|
|
1688
|
+
el = el || topDocument.body;
|
|
1689
|
+
|
|
1690
|
+
function prefixedResult(el, prop) {
|
|
1691
|
+
|
|
1692
|
+
var prefixes = ['webkit', 'moz', 'ms', 'o', ''];
|
|
1693
|
+
for (var i = 0; i < prefixes.length; i++) {
|
|
1694
|
+
var prefix = prefixes[i];
|
|
1695
|
+
var propName = prefix ? (prefix + prop) : (prop.substr(0, 1).toLowerCase() + prop.substr(1));
|
|
1696
|
+
if (el[propName] !== undefined) {
|
|
1697
|
+
return isFunction(el[propName]) ? el[propName]() : el[propName];
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
if (prefixedResult(topDocument, 'FullscreenElement') || prefixedResult(topDocument, 'FullScreenElement')) {
|
|
1703
|
+
prefixedResult(topDocument, 'ExitFullscreen') || // Spec.
|
|
1704
|
+
prefixedResult(topDocument, 'CancelFullScreen'); // Firefox
|
|
1705
|
+
} else {
|
|
1706
|
+
prefixedResult(el, 'RequestFullscreen') || // Spec.
|
|
1707
|
+
prefixedResult(el, 'RequestFullScreen'); // Firefox
|
|
1708
|
+
}
|
|
1709
|
+
};
|
|
1710
|
+
|
|
1711
|
+
export {
|
|
1712
|
+
isBoolean,
|
|
1713
|
+
isObject,
|
|
1714
|
+
isNumber,
|
|
1715
|
+
isString,
|
|
1716
|
+
mixin,
|
|
1717
|
+
deepMixin,
|
|
1718
|
+
supplement,
|
|
1719
|
+
defaults,
|
|
1720
|
+
deepSupplement,
|
|
1721
|
+
defaultsDeep,
|
|
1722
|
+
assign,
|
|
1723
|
+
invoke,
|
|
1724
|
+
invokeProperty,
|
|
1725
|
+
sortedIndex,
|
|
1726
|
+
uniq,
|
|
1727
|
+
clone,
|
|
1728
|
+
cloneDeep,
|
|
1729
|
+
isEmpty,
|
|
1730
|
+
isEqual,
|
|
1731
|
+
isFunction,
|
|
1732
|
+
isPlainObject,
|
|
1733
|
+
toArray,
|
|
1734
|
+
debounce,
|
|
1735
|
+
groupBy,
|
|
1736
|
+
sortBy,
|
|
1737
|
+
flattenDeep,
|
|
1738
|
+
without,
|
|
1739
|
+
difference,
|
|
1740
|
+
intersection,
|
|
1741
|
+
union,
|
|
1742
|
+
has,
|
|
1743
|
+
result,
|
|
1744
|
+
omit,
|
|
1745
|
+
pick,
|
|
1746
|
+
bindAll,
|
|
1747
|
+
forIn,
|
|
1748
|
+
camelCase,
|
|
1749
|
+
uniqueId,
|
|
1750
|
+
merge
|
|
1751
|
+
};
|
|
1752
|
+
|
|
1753
|
+
export const noop = function() {
|
|
1754
|
+
};
|