sculp-js 0.0.1

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/lib/es/dom.js ADDED
@@ -0,0 +1,143 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ import { arrayEach } from './array.js';
8
+ import { easingFunctional } from './easing.js';
9
+ import { objectEach, objectAssign } from './object.js';
10
+ import { stringKebabCase } from './string.js';
11
+ import { isObject } from './type.js';
12
+
13
+ /**
14
+ * 判断元素是否包含某个样式名
15
+ * @param {HTMLElement} el
16
+ * @param {string} className
17
+ * @returns {boolean}
18
+ */
19
+ const hasClass = (el, className) => {
20
+ if (className.indexOf(' ') !== -1)
21
+ throw new Error('className should not contain space.');
22
+ return el.classList.contains(className);
23
+ };
24
+ const eachClassName = (classNames, func) => {
25
+ const classNameList = classNames.split(/\s+/g);
26
+ classNameList.forEach(func);
27
+ };
28
+ /**
29
+ * 给元素增加样式名
30
+ * @param {HTMLElement} el
31
+ * @param {string} classNames
32
+ */
33
+ const addClass = (el, classNames) => {
34
+ eachClassName(classNames, className => el.classList.add(className));
35
+ };
36
+ /**
37
+ * 给元素移除样式名
38
+ * @param {HTMLElement} el
39
+ * @param {string} classNames
40
+ */
41
+ const removeClass = (el, classNames) => {
42
+ eachClassName(classNames, className => el.classList.remove(className));
43
+ };
44
+ /**
45
+ * 设置元素样式
46
+ * @param {HTMLElement} el
47
+ * @param {string | Style} key
48
+ * @param {string} val
49
+ */
50
+ const setStyle = (el, key, val) => {
51
+ if (isObject(key)) {
52
+ objectEach(key, (val1, key1) => {
53
+ setStyle(el, key1, val1);
54
+ });
55
+ }
56
+ else {
57
+ el.style.setProperty(stringKebabCase(key), val);
58
+ }
59
+ };
60
+ /**
61
+ * 获取元素样式
62
+ * @param {HTMLElement} el
63
+ * @param {string} key
64
+ * @returns {string}
65
+ */
66
+ const getStyle = (el, key) => getComputedStyle(el).getPropertyValue(key);
67
+ async function smoothScroll(options) {
68
+ return new Promise(resolve => {
69
+ const defaults = {
70
+ el: document,
71
+ to: 0,
72
+ duration: 567,
73
+ easing: 'ease'
74
+ };
75
+ const { el, to, duration, easing } = objectAssign(defaults, options);
76
+ const htmlEl = document.documentElement;
77
+ const bodyEl = document.body;
78
+ const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
79
+ const els = globalMode ? [htmlEl, bodyEl] : [el];
80
+ const query = () => {
81
+ let value = 0;
82
+ arrayEach(els, el => {
83
+ if ('scrollTop' in el) {
84
+ value = el.scrollTop;
85
+ return false;
86
+ }
87
+ });
88
+ return value;
89
+ };
90
+ const update = (val) => {
91
+ els.forEach(el => {
92
+ if ('scrollTop' in el) {
93
+ el.scrollTop = val;
94
+ }
95
+ });
96
+ };
97
+ let startTime;
98
+ const startValue = query();
99
+ const length = to - startValue;
100
+ const easingFn = easingFunctional(easing);
101
+ const render = () => {
102
+ const now = performance.now();
103
+ const passingTime = startTime ? now - startTime : 0;
104
+ const t = passingTime / duration;
105
+ const p = easingFn(t);
106
+ if (!startTime)
107
+ startTime = now;
108
+ update(startValue + length * p);
109
+ if (t >= 1)
110
+ resolve();
111
+ else
112
+ requestAnimationFrame(render);
113
+ };
114
+ render();
115
+ });
116
+ }
117
+ const domReadyCallbacks = [];
118
+ const eventType = 'DOMContentLoaded';
119
+ const listener = () => {
120
+ domReadyCallbacks.forEach(callback => callback());
121
+ domReadyCallbacks.length = 0;
122
+ document.removeEventListener(eventType, listener);
123
+ };
124
+ document.addEventListener(eventType, listener);
125
+ let readied = false;
126
+ function isDomReady() {
127
+ if (readied)
128
+ return true;
129
+ readied = ['complete', 'loaded', 'interactive'].indexOf(document.readyState) !== -1;
130
+ return readied;
131
+ }
132
+ function onDomReady(callback) {
133
+ // document readied
134
+ if (isDomReady()) {
135
+ setTimeout(callback, 0);
136
+ }
137
+ // listen document to ready
138
+ else {
139
+ domReadyCallbacks.push(callback);
140
+ }
141
+ }
142
+
143
+ export { addClass, getStyle, hasClass, isDomReady, onDomReady, removeClass, setStyle, smoothScroll };
@@ -0,0 +1,77 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ import { urlSetParams } from './url.js';
8
+
9
+ /**
10
+ * 通过打开新窗口的方式下载
11
+ * @param {string} url
12
+ * @param {LooseParams} params
13
+ */
14
+ const downloadURL = (url, params) => {
15
+ window.open(params ? urlSetParams(url, params) : url);
16
+ };
17
+ /**
18
+ * 通过 A 链接的方式下载
19
+ * @param {string} href
20
+ * @param {string} filename
21
+ */
22
+ const downloadHref = (href, filename) => {
23
+ const eleLink = document.createElement('a');
24
+ eleLink.download = filename;
25
+ eleLink.style.display = 'none';
26
+ eleLink.href = href;
27
+ document.body.appendChild(eleLink);
28
+ eleLink.click();
29
+ setTimeout(() => document.body.removeChild(eleLink));
30
+ };
31
+ /**
32
+ * 将大文件对象通过 A 链接的方式下载
33
+ * @param {Blob} blob
34
+ * @param {string} filename
35
+ */
36
+ const downloadBlob = (blob, filename) => {
37
+ const objURL = URL.createObjectURL(blob);
38
+ downloadHref(objURL, filename);
39
+ setTimeout(() => URL.revokeObjectURL(objURL));
40
+ };
41
+ /**
42
+ * 将指定数据格式通过 A 链接的方式下载
43
+ * @param {AnyObject | AnyObject[]} data
44
+ * @param {FileType} fileType 支持 json/csv/xls/xlsx 四种格式
45
+ * @param {string} filename
46
+ * @param {string[]} [headers]
47
+ */
48
+ const downloadData = (data, fileType, filename, headers) => {
49
+ filename = filename.replace(`.${fileType}`, '') + `.${fileType}`;
50
+ if (fileType === 'json') {
51
+ const blob = new Blob([JSON.stringify(data, null, 4)]);
52
+ downloadBlob(blob, filename);
53
+ }
54
+ else {
55
+ // xlsx实际生成的也为csv,仅后缀名名不同
56
+ if (!headers || !headers.length)
57
+ throw new Error('未传入表头数据');
58
+ if (!Array.isArray(data))
59
+ throw new Error('data error! expected array!');
60
+ const headerStr = headers.join(',') + '\n';
61
+ let bodyStr = '';
62
+ data.forEach(row => {
63
+ // \t防止数字被科学计数法显示
64
+ bodyStr += Object.values(row).join(',\t') + ',\n';
65
+ });
66
+ const MIMETypes = {
67
+ csv: 'text/csv',
68
+ xls: 'application/vnd.ms-excel',
69
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
70
+ };
71
+ // encodeURIComponent解决中文乱码
72
+ const href = 'data:' + MIMETypes[fileType] + ';charset=utf-8,\ufeff' + encodeURIComponent(headerStr + bodyStr);
73
+ downloadHref(href, filename);
74
+ }
75
+ };
76
+
77
+ export { downloadBlob, downloadData, downloadHref, downloadURL };
@@ -0,0 +1,38 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ import bezier from 'bezier-easing';
8
+ import { isArray } from './type.js';
9
+
10
+ // @ref https://cubic-bezier.com/
11
+ const easingDefines = {
12
+ linear: [0, 0, 1, 1],
13
+ ease: [0.25, 0.1, 0.25, 1],
14
+ 'ease-in': [0.42, 0, 1, 1],
15
+ 'ease-out': [0, 0, 0.58, 1],
16
+ 'ease-in-out': [0.42, 0, 0.58, 1]
17
+ };
18
+ /**
19
+ * 缓冲函数化,用于 js 计算缓冲进度
20
+ * @param {EasingNameOrDefine} [name=linear]
21
+ * @returns {EasingFunction}
22
+ */
23
+ function easingFunctional(name) {
24
+ let fn;
25
+ if (isArray(name)) {
26
+ fn = bezier(...name);
27
+ }
28
+ else {
29
+ const define = easingDefines[name];
30
+ if (!define) {
31
+ throw new Error(`${name} 缓冲函数未定义`);
32
+ }
33
+ fn = bezier(...define);
34
+ }
35
+ return (input) => fn(Math.max(0, Math.min(input, 1)));
36
+ }
37
+
38
+ export { easingFunctional };
package/lib/es/file.js ADDED
@@ -0,0 +1,28 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ /**
8
+ * 选择本地文件
9
+ * @param {function} changeCb 选择文件回调
10
+ * @return {*}
11
+ */
12
+ function chooseLocalFile({ accept }, changeCb) {
13
+ const inputObj = document.createElement('input');
14
+ inputObj.setAttribute('id', String(Date.now()));
15
+ inputObj.setAttribute('type', 'file');
16
+ inputObj.setAttribute('style', 'visibility:hidden');
17
+ inputObj.setAttribute('accept', accept);
18
+ document.body.appendChild(inputObj);
19
+ inputObj.click();
20
+ // @ts-ignore
21
+ inputObj.onchange = (e) => {
22
+ changeCb(e.target.files);
23
+ setTimeout(() => document.body.removeChild(inputObj));
24
+ };
25
+ return inputObj;
26
+ }
27
+
28
+ export { chooseLocalFile };
@@ -0,0 +1,35 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ import * as array from './array.js';
8
+ export { array };
9
+ import * as clipboard from './clipboard.js';
10
+ export { clipboard };
11
+ import * as cookie from './cookie.js';
12
+ export { cookie };
13
+ import * as date from './date.js';
14
+ export { date };
15
+ import * as dom from './dom.js';
16
+ export { dom };
17
+ import * as download from './download.js';
18
+ export { download };
19
+ import * as object from './object.js';
20
+ export { object };
21
+ import * as path from './path.js';
22
+ export { path };
23
+ import * as qs from './qs.js';
24
+ export { qs };
25
+ import * as string from './string.js';
26
+ export { string };
27
+ import * as type from './type.js';
28
+ export { type };
29
+ import * as url from './url.js';
30
+ export { url };
31
+ import * as async from './async.js';
32
+ export { async };
33
+ import * as file from './file.js';
34
+ export { file };
35
+ export { genCanvasWM } from './watermark.js';
@@ -0,0 +1,228 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ import typeIs, { isObject, isUndefined, isArray } from './type.js';
8
+
9
+ /**
10
+ * 判断对象是否为纯对象
11
+ * @param {object} obj
12
+ * @returns {boolean}
13
+ */
14
+ const isPlainObject = (obj) => {
15
+ if (!isObject(obj))
16
+ return false;
17
+ const proto = Object.getPrototypeOf(obj);
18
+ // 对象无原型
19
+ if (!proto)
20
+ return true;
21
+ // 是否对象直接实例
22
+ return proto === Object.prototype;
23
+ };
24
+ /**
25
+ * 判断对象内是否有该静态属性
26
+ * @param {object} obj
27
+ * @param {string} key
28
+ * @returns {boolean}
29
+ */
30
+ const objectHas = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
31
+ /**
32
+ * 遍历对象,返回 false 中断遍历
33
+ * @param {O} obj
34
+ * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
35
+ */
36
+ const objectEach = (obj, iterator) => {
37
+ for (const key in obj) {
38
+ if (!objectHas(obj, key))
39
+ continue;
40
+ if (iterator(obj[key], key) === false)
41
+ break;
42
+ }
43
+ };
44
+ /**
45
+ * 异步遍历对象,返回 false 中断遍历
46
+ * @param {O} obj
47
+ * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
48
+ */
49
+ async function objectEachAsync(obj, iterator) {
50
+ for (const key in obj) {
51
+ if (!objectHas(obj, key))
52
+ continue;
53
+ if ((await iterator(obj[key], key)) === false)
54
+ break;
55
+ }
56
+ }
57
+ /**
58
+ * 对象映射
59
+ * @param {O} obj
60
+ * @param {(val: O[keyof O], key: Extract<keyof O, string>) => any} iterator
61
+ * @returns {Record<Extract<keyof O, string>, T>}
62
+ */
63
+ function objectMap(obj, iterator) {
64
+ const obj2 = {};
65
+ for (const key in obj) {
66
+ if (!objectHas(obj, key))
67
+ continue;
68
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
69
+ obj2[key] = iterator(obj[key], key);
70
+ }
71
+ return obj2;
72
+ }
73
+ /**
74
+ * 对象提取
75
+ * @param {O} obj
76
+ * @param {K} keys
77
+ * @returns {Pick<O, ArrayElements<K>>}
78
+ */
79
+ function objectPick(obj, keys) {
80
+ const obj2 = {};
81
+ objectEach(obj, (v, k) => {
82
+ if (keys.includes(k)) {
83
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
84
+ // @ts-ignore
85
+ obj2[k] = v;
86
+ }
87
+ });
88
+ return obj2;
89
+ }
90
+ /**
91
+ * 对象祛除
92
+ * @param {O} obj
93
+ * @param {K} keys
94
+ * @returns {Pick<O, ArrayElements<K>>}
95
+ */
96
+ function objectOmit(obj, keys) {
97
+ const obj2 = {};
98
+ objectEach(obj, (v, k) => {
99
+ if (!keys.includes(k)) {
100
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
101
+ // @ts-ignore
102
+ obj2[k] = v;
103
+ }
104
+ });
105
+ return obj2;
106
+ }
107
+ const merge = (map, source, target) => {
108
+ if (isUndefined(target))
109
+ return source;
110
+ const sourceType = typeIs(source);
111
+ const targetType = typeIs(target);
112
+ if (sourceType !== targetType) {
113
+ if (isArray(target))
114
+ return merge(map, [], target);
115
+ if (isObject(target))
116
+ return merge(map, {}, target);
117
+ return target;
118
+ }
119
+ // 朴素对象
120
+ if (isPlainObject(target)) {
121
+ const exist = map.get(target);
122
+ if (exist)
123
+ return exist;
124
+ map.set(target, source);
125
+ objectEach(target, (val, key) => {
126
+ source[key] = merge(map, source[key], val);
127
+ });
128
+ return source;
129
+ }
130
+ // 数组
131
+ else if (isArray(target)) {
132
+ const exist = map.get(target);
133
+ if (exist)
134
+ return exist;
135
+ map.set(target, source);
136
+ target.forEach((val, index) => {
137
+ source[index] = merge(map, source[index], val);
138
+ });
139
+ return source;
140
+ }
141
+ return target;
142
+ };
143
+ /**
144
+ * 对象合并,返回原始对象
145
+ * @param {ObjectAssignItem} source
146
+ * @param {ObjectAssignItem | undefined} targets
147
+ * @returns {R}
148
+ */
149
+ const objectAssign = (source, ...targets) => {
150
+ const map = new Map();
151
+ for (let i = 0; i < targets.length; i++) {
152
+ const target = targets[i];
153
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
154
+ // @ts-ignore
155
+ source = merge(map, source, target);
156
+ }
157
+ map.clear();
158
+ return source;
159
+ };
160
+ /**
161
+ * 对象填充
162
+ * @param {Partial<R>} source
163
+ * @param {Partial<R>} target
164
+ * @param {(s: Partial<R>, t: Partial<R>, key: keyof R) => boolean} fillable
165
+ * @returns {R}
166
+ */
167
+ const objectFill = (source, target, fillable) => {
168
+ const _fillable = fillable || ((source, target, key) => source[key] === undefined);
169
+ objectEach(target, (val, key) => {
170
+ if (_fillable(source, target, key)) {
171
+ source[key] = val;
172
+ }
173
+ });
174
+ return source;
175
+ };
176
+ function objectGet(obj, path, strict = false) {
177
+ path = path.replace(/\[(\w+)\]/g, '.$1');
178
+ path = path.replace(/^\./, '');
179
+ const keyArr = path.split('.');
180
+ let tempObj = obj;
181
+ let i = 0;
182
+ for (let len = keyArr.length; i < len - 1; ++i) {
183
+ const key = keyArr[i];
184
+ if (key in tempObj) {
185
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
186
+ tempObj = tempObj[key];
187
+ }
188
+ else {
189
+ tempObj = undefined;
190
+ if (strict) {
191
+ throw new Error('[berry/js-utils/object] objectGet path 路径不正确');
192
+ }
193
+ break;
194
+ }
195
+ }
196
+ return {
197
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
198
+ p: tempObj,
199
+ k: tempObj ? keyArr[i] : undefined,
200
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment
201
+ v: tempObj ? tempObj[keyArr[i]] : undefined
202
+ };
203
+ }
204
+ /**
205
+ * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
206
+ * @param {AnyObject | AnyArray} obj
207
+ * @param {WeakMap} map
208
+ * @return {AnyObject | AnyArray}
209
+ */
210
+ function cloneDeep(obj, map = new WeakMap()) {
211
+ if (obj instanceof Date)
212
+ return new Date(obj);
213
+ if (obj instanceof RegExp)
214
+ return new RegExp(obj);
215
+ if (map.has(obj)) {
216
+ return map.get(obj);
217
+ }
218
+ const allDesc = Object.getOwnPropertyDescriptors(obj);
219
+ const cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
220
+ map.set(obj, cloneObj);
221
+ for (const key of Reflect.ownKeys(obj)) {
222
+ const value = obj[key];
223
+ cloneObj[key] = value instanceof Object && typeof value !== 'function' ? cloneDeep(value, map) : value;
224
+ }
225
+ return cloneObj;
226
+ }
227
+
228
+ export { cloneDeep, isPlainObject, objectAssign, objectEach, objectEachAsync, objectFill, objectGet, objectHas, objectMap, objectAssign as objectMerge, objectOmit, objectPick };
package/lib/es/path.js ADDED
@@ -0,0 +1,63 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ /**
8
+ * 标准化路径
9
+ * @param {string} path
10
+ * @returns {string}
11
+ */
12
+ const pathNormalize = (path) => {
13
+ const slicees = path
14
+ .replace(/\\/g, '/')
15
+ .replace(/\/{2,}/g, '/')
16
+ .replace(/\.{3,}/g, '..')
17
+ .replace(/\/\.\//g, '/')
18
+ .split('/')
19
+ .map(point => point.trim());
20
+ const isCurrentSlice = (slice) => slice === '.';
21
+ const isParentSlice = (slice) => slice === '..';
22
+ const points = [];
23
+ let inPoints = false;
24
+ const push = (point) => {
25
+ points.push(point);
26
+ };
27
+ const back = () => {
28
+ if (points.length === 0)
29
+ return;
30
+ const lastSlice = points[points.length - 1];
31
+ if (isParentSlice(lastSlice)) {
32
+ points.push('..');
33
+ }
34
+ else {
35
+ points.pop();
36
+ }
37
+ };
38
+ slicees.forEach(slice => {
39
+ const isCurrent = isCurrentSlice(slice);
40
+ const isParent = isParentSlice(slice);
41
+ // 未进入实际路径
42
+ if (!inPoints) {
43
+ push(slice);
44
+ inPoints = !isCurrent && !isParent;
45
+ return;
46
+ }
47
+ if (isCurrent)
48
+ return;
49
+ if (isParent)
50
+ return back();
51
+ push(slice);
52
+ });
53
+ return points.join('/');
54
+ };
55
+ /**
56
+ * 路径合并
57
+ * @param {string} from
58
+ * @param {string} to
59
+ * @returns {string}
60
+ */
61
+ const pathJoin = (from, ...to) => pathNormalize([from, ...to].join('/'));
62
+
63
+ export { pathJoin, pathNormalize };
package/lib/es/qs.js ADDED
@@ -0,0 +1,68 @@
1
+ /*!
2
+ * sculp-js v0.0.1
3
+ * (c) 2023-2023 chandq
4
+ * Released under the MIT License.
5
+ */
6
+
7
+ import { objectEach } from './object.js';
8
+ import { isUndefined, isArray, isString, isNumber, isBoolean, isDate } from './type.js';
9
+
10
+ /**
11
+ * 解析查询参数,内部使用的是浏览器内置的 URLSearchParams 进行处理
12
+ * @param {string} queryString
13
+ * @returns {Params}
14
+ */
15
+ const qsParse = (queryString) => {
16
+ const params = new URLSearchParams(queryString);
17
+ const result = {};
18
+ for (const [key, val] of params.entries()) {
19
+ if (isUndefined(result[key])) {
20
+ result[key] = val;
21
+ continue;
22
+ }
23
+ if (isArray(result[key])) {
24
+ continue;
25
+ }
26
+ result[key] = params.getAll(key);
27
+ }
28
+ return result;
29
+ };
30
+ const defaultReplacer = (val) => {
31
+ if (isString(val))
32
+ return val;
33
+ if (isNumber(val))
34
+ return String(val);
35
+ if (isBoolean(val))
36
+ return val ? 'true' : 'false';
37
+ if (isDate(val))
38
+ return val.toISOString();
39
+ return null;
40
+ };
41
+ /**
42
+ * 字符化查询对象,内部使用的是浏览器内置的 URLSearchParams 进行处理
43
+ * @param {LooseParams} query
44
+ * @param {Replacer} replacer
45
+ * @returns {string}
46
+ */
47
+ const qsStringify = (query, replacer = defaultReplacer) => {
48
+ const params = new URLSearchParams();
49
+ objectEach(query, (val, key) => {
50
+ if (isArray(val)) {
51
+ val.forEach(i => {
52
+ const replaced = replacer(i);
53
+ if (replaced === null)
54
+ return;
55
+ params.append(key.toString(), replaced);
56
+ });
57
+ }
58
+ else {
59
+ const replaced = replacer(val);
60
+ if (replaced === null)
61
+ return;
62
+ params.set(key.toString(), replaced);
63
+ }
64
+ });
65
+ return params.toString();
66
+ };
67
+
68
+ export { qsParse, qsStringify };