@alexgyver/component 1.5.0 → 2.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/Component.min.js +1 -1
- package/Component.min.js.gz +0 -0
- package/Component.pico.min.js +1 -0
- package/Component.pico.min.js.gz +0 -0
- package/Component.tiny.min.js +1 -0
- package/Component.tiny.min.js.gz +0 -0
- package/README.md +651 -259
- package/package.json +16 -9
- package/src/Component.js +15 -0
- package/src/EL.js +371 -0
- package/src/SVG.js +18 -0
- package/src/Sheet.js +73 -0
- package/src/State.js +47 -0
- package/src/utils.js +94 -0
- package/test/index.html +12 -0
- package/test/script.js +434 -148
- package/webpack.config.js +29 -10
- package/webpack.dev.config.js +32 -0
- package/Component.js +0 -388
- package/test/example.html +0 -15
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alexgyver/component",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Simple HTML&SVG element builder",
|
|
5
|
-
"main": "./Component.js",
|
|
6
|
-
"module": "./Component.js",
|
|
7
|
-
"types": "./Component.js",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Simple reactive HTML&SVG element builder",
|
|
5
|
+
"main": "./src/Component.js",
|
|
6
|
+
"module": "./src/Component.js",
|
|
7
|
+
"types": "./src/Component.js",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "webpack --config ./webpack.config.js"
|
|
9
|
+
"build": "webpack --config ./webpack.config.js",
|
|
10
|
+
"dev": "webpack serve --config ./webpack.dev.config.js & webpack --config ./webpack.dev.config.js"
|
|
10
11
|
},
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
@@ -15,7 +16,13 @@
|
|
|
15
16
|
"author": "AlexGyver <alex@alexgyver.ru>",
|
|
16
17
|
"license": "MIT",
|
|
17
18
|
"devDependencies": {
|
|
18
|
-
"webpack": "^5.105.
|
|
19
|
-
"webpack-cli": "^6.0.1"
|
|
19
|
+
"webpack": "^5.105.2",
|
|
20
|
+
"webpack-cli": "^6.0.1",
|
|
21
|
+
"webpack-dev-server": "^5.2.3",
|
|
22
|
+
"compression-webpack-plugin": "^11.1.0",
|
|
23
|
+
"ifdef-loader": "^2.3.2",
|
|
24
|
+
"css-loader": "^7.1.3",
|
|
25
|
+
"style-loader": "^4.0.0",
|
|
26
|
+
"mini-css-extract-plugin": "^2.10.0"
|
|
20
27
|
}
|
|
21
|
-
}
|
|
28
|
+
}
|
package/src/Component.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EL } from "./EL";
|
|
2
|
+
export { EL };
|
|
3
|
+
|
|
4
|
+
/// #if !PICO_COMPONENT
|
|
5
|
+
import { SVG } from "./SVG";
|
|
6
|
+
export { SVG };
|
|
7
|
+
|
|
8
|
+
import { addCSS, removeCSS, watchMount, watchResize } from "./utils";
|
|
9
|
+
export { addCSS, removeCSS, watchMount, watchResize };
|
|
10
|
+
/// #endif
|
|
11
|
+
|
|
12
|
+
/// #if !TINY_COMPONENT
|
|
13
|
+
import { State, useState } from "./State";
|
|
14
|
+
export { State, useState };
|
|
15
|
+
/// #endif
|
package/src/EL.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/// #if !TINY_COMPONENT
|
|
2
|
+
import { watchMount, watchResize } from "./utils";
|
|
3
|
+
/// #endif
|
|
4
|
+
|
|
5
|
+
export class EL {
|
|
6
|
+
// Создать элемент и поместить его в переменную $root
|
|
7
|
+
constructor(tag, cfg = {}, svg = false) {
|
|
8
|
+
this.$root = EL.make(tag, { ...cfg, ctx: this }, svg);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#region ## make
|
|
12
|
+
|
|
13
|
+
// Создать элемент
|
|
14
|
+
static make(tag, cfg = {}, svg = false) {
|
|
15
|
+
tag ??= cfg.tag;
|
|
16
|
+
|
|
17
|
+
let el = (tag == 'svg' || svg)
|
|
18
|
+
? document.createElementNS("http://www.w3.org/2000/svg", tag ?? 'svg')
|
|
19
|
+
: document.createElement(tag ?? 'div');
|
|
20
|
+
|
|
21
|
+
EL.update(el, cfg);
|
|
22
|
+
|
|
23
|
+
/// #if !TINY_COMPONENT
|
|
24
|
+
for (let k of EL_METHODS) {
|
|
25
|
+
el[k] = (...a) => EL[k](el, ...a);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
CALLBACKS.forEach(fn => {
|
|
29
|
+
if (fn in cfg) el['_' + fn] = cfg[fn].bind(el.ctx, { el, ctx: el.ctx });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (el._onResize) watchResize(el, el._onResize);
|
|
33
|
+
/// #endif
|
|
34
|
+
|
|
35
|
+
return el;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#region ## update
|
|
39
|
+
|
|
40
|
+
// Обновить элемент
|
|
41
|
+
static update(el, cfg) {
|
|
42
|
+
if (!is.node(el) || !is.obj(cfg)) return el;
|
|
43
|
+
|
|
44
|
+
el.ctx = cfg.ctx ?? cfg.context ?? el.ctx;
|
|
45
|
+
|
|
46
|
+
for (let [param, val] of Object.entries(cfg)) _update(el, param, val);
|
|
47
|
+
|
|
48
|
+
/// #if !TINY_COMPONENT
|
|
49
|
+
if (el._onUpdate) el._onUpdate();
|
|
50
|
+
/// #endif
|
|
51
|
+
|
|
52
|
+
if (cfg.also) cfg.also.call(el.ctx, { el, ctx: el.ctx });
|
|
53
|
+
|
|
54
|
+
return el;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//#region ## lifecycle
|
|
58
|
+
|
|
59
|
+
// Подключить к родителю, null - отключить, вернёт Promise
|
|
60
|
+
static mount(el, parent, waitRender = false, tries = 100) {
|
|
61
|
+
if (el) {
|
|
62
|
+
/// #if !TINY_COMPONENT
|
|
63
|
+
if (parent == null) {
|
|
64
|
+
if (el.parentNode) el.parentNode.removeChild(el);
|
|
65
|
+
} else {
|
|
66
|
+
/// #endif
|
|
67
|
+
if (el.parentNode !== parent) parent.appendChild(el);
|
|
68
|
+
/// #if !TINY_COMPONENT
|
|
69
|
+
if (tries) return watchMount(el, waitRender, tries);
|
|
70
|
+
}
|
|
71
|
+
/// #endif
|
|
72
|
+
}
|
|
73
|
+
return Promise.resolve(el);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Удалить всех детей
|
|
77
|
+
static clear(el, recursive = true) {
|
|
78
|
+
if (!el) return;
|
|
79
|
+
while (el.firstChild) {
|
|
80
|
+
/// #if !TINY_COMPONENT
|
|
81
|
+
if (recursive) EL.clear(el.firstChild, true);
|
|
82
|
+
/// #endif
|
|
83
|
+
EL.remove(el.firstChild, false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Удалить элемент
|
|
88
|
+
static remove(el, recursive = true) {
|
|
89
|
+
if (!el) return;
|
|
90
|
+
/// #if !TINY_COMPONENT
|
|
91
|
+
if (recursive) EL.clear(el, true);
|
|
92
|
+
EL.release(el);
|
|
93
|
+
EL.unbind(el);
|
|
94
|
+
if (el._onDestroy) el._onDestroy();
|
|
95
|
+
CALLBACKS.forEach(fn => el['_' + fn] = null);
|
|
96
|
+
/// #endif
|
|
97
|
+
if (el.parentNode) el.parentNode.removeChild(el); // remove
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// #if !TINY_COMPONENT
|
|
101
|
+
|
|
102
|
+
// Заменить ребёнка old на нового el, old удалить, у el запустить монтаж с вызовом обработчиков. Вернёт el
|
|
103
|
+
static replace(old, el, keepContext = false) {
|
|
104
|
+
if (old) {
|
|
105
|
+
if (old.parentNode) old.parentNode.replaceChild(el, old);
|
|
106
|
+
if (keepContext) el.ctx = old.ctx;
|
|
107
|
+
watchMount(el);
|
|
108
|
+
old.remove();
|
|
109
|
+
}
|
|
110
|
+
return el;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Отключить on-обработчики
|
|
114
|
+
static release(el) {
|
|
115
|
+
if (el && el._events) {
|
|
116
|
+
el._events.forEach(({ ev, fn, options }) => el.removeEventListener(ev, fn, options));
|
|
117
|
+
el._events = [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Отключить state-бинды
|
|
122
|
+
static unbind(el) {
|
|
123
|
+
if (el && el._unsub) {
|
|
124
|
+
Object.values(el._unsub).forEach(f => f());
|
|
125
|
+
el._unsub = {};
|
|
126
|
+
// el._unsub.forEach(f => f());
|
|
127
|
+
// el._unsub = [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// #endif
|
|
132
|
+
|
|
133
|
+
//#region ## shadow
|
|
134
|
+
|
|
135
|
+
/// #if !TINY_COMPONENT
|
|
136
|
+
|
|
137
|
+
// Создать теневой элемент от указанного тега/Node host, дети подключатся к нему в shadowRoot, стили запишутся в $style
|
|
138
|
+
static makeShadow(host, cfg = {}, css = '') {
|
|
139
|
+
if (!host || !is.obj(cfg)) return null;
|
|
140
|
+
|
|
141
|
+
if (!is.node(host)) host = document.createElement(host);
|
|
142
|
+
host.attachShadow({ mode: 'open' });
|
|
143
|
+
|
|
144
|
+
EL.update(host.shadowRoot, {
|
|
145
|
+
ctx: cfg.context ?? cfg.ctx,
|
|
146
|
+
child: [{ tag: 'style', $: 'style', text: css }, cfg.child, cfg.children],
|
|
147
|
+
});
|
|
148
|
+
delete cfg.child;
|
|
149
|
+
delete cfg.children;
|
|
150
|
+
return EL.update(host, cfg);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/// #endif
|
|
154
|
+
|
|
155
|
+
//#region ## template
|
|
156
|
+
|
|
157
|
+
/// #if !TINY_COMPONENT
|
|
158
|
+
|
|
159
|
+
// Определить глобальный шаблон, fn - функция, возвращающая cfg-конфиг
|
|
160
|
+
static setTemplate(name, tag, fn) {
|
|
161
|
+
EL.templates.set(name, (...args) => EL.make(tag, fn(...args)));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Вызвать шаблон
|
|
165
|
+
static useTemplate(name, ...args) {
|
|
166
|
+
const t = EL.templates.get(name);
|
|
167
|
+
return t ? t(...args) : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static templates = new Map();
|
|
171
|
+
|
|
172
|
+
/// #endif
|
|
173
|
+
|
|
174
|
+
// legacy
|
|
175
|
+
static config = EL.update;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#region ## private
|
|
179
|
+
|
|
180
|
+
/// #if !TINY_COMPONENT
|
|
181
|
+
const EL_METHODS = ['update', 'mount', 'clear', 'remove'];
|
|
182
|
+
const CALLBACKS = ['onMount', 'onRender', 'onUpdate', 'onResize', 'onDestroy'];
|
|
183
|
+
/// #endif
|
|
184
|
+
|
|
185
|
+
const SKIP_PARAM = new Set(['tag', 'get', 'also', 'context', 'ctx',
|
|
186
|
+
/// #if !TINY_COMPONENT
|
|
187
|
+
...CALLBACKS
|
|
188
|
+
/// #endif
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
const PARAM_ALIAS = {
|
|
192
|
+
children: 'child',
|
|
193
|
+
children_r: 'child_r',
|
|
194
|
+
text: 'textContent',
|
|
195
|
+
html: 'innerHTML',
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const PARAM_UPD = {
|
|
199
|
+
push(el, val) {
|
|
200
|
+
val.push(el);
|
|
201
|
+
},
|
|
202
|
+
$(el, val) {
|
|
203
|
+
if (el.ctx) el.ctx['$' + val] = el;
|
|
204
|
+
},
|
|
205
|
+
attrs(el, val) {
|
|
206
|
+
_applyP('attrs', el, val, (k, v) => el.setAttribute(k, v), k => el.removeAttribute(k));
|
|
207
|
+
},
|
|
208
|
+
data(el, val) {
|
|
209
|
+
_applyP('data', el, val, (k, v) => el.dataset[k] = v, k => delete el.dataset[k]);
|
|
210
|
+
},
|
|
211
|
+
props(el, val) {
|
|
212
|
+
for (let p in val) {
|
|
213
|
+
let v = _makeVal(el, p, null, val[p]);
|
|
214
|
+
el[p] = (
|
|
215
|
+
['min', 'max', 'step', 'selectedIndex'].includes(p)
|
|
216
|
+
|| (p == 'value' && ['number', 'range'].includes(el.type)))
|
|
217
|
+
? ((v == null || v === '' || Number.isNaN(v)) ? '' : +v)
|
|
218
|
+
: (v ?? '');
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
transition(el, val) {
|
|
222
|
+
const { duration = 300, easing = 'ease', delay = 0, onEnd = null, ...styles } = val;
|
|
223
|
+
el.style.transition = Object.keys(styles).map(st => `${st} ${duration}ms ${easing} ${delay}ms`).join(', ');
|
|
224
|
+
if (onEnd) {
|
|
225
|
+
PARAM_UPD.events(el, {
|
|
226
|
+
transitionend: { handler: onEnd, once: true }
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
requestAnimationFrame(() => PARAM_UPD.style(el, styles));
|
|
230
|
+
},
|
|
231
|
+
events(el, val) {
|
|
232
|
+
for (let ev in val) {
|
|
233
|
+
let h = val[ev];
|
|
234
|
+
let options = {};
|
|
235
|
+
|
|
236
|
+
if (is.obj(h)) {
|
|
237
|
+
options = h;
|
|
238
|
+
h = h.handler;
|
|
239
|
+
if (!h) continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const fn = (evt) => h.call(el.ctx, Object.assign(evt, { el, ctx: el.ctx }));
|
|
243
|
+
el.addEventListener(ev, fn, options);
|
|
244
|
+
/// #if !TINY_COMPONENT
|
|
245
|
+
(el._events ??= []).push({ ev, fn, options });
|
|
246
|
+
/// #endif
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
events_r(el, val) {
|
|
250
|
+
/// #if !TINY_COMPONENT
|
|
251
|
+
EL.release(el);
|
|
252
|
+
/// #endif
|
|
253
|
+
PARAM_UPD.events(el, val);
|
|
254
|
+
},
|
|
255
|
+
child(el, val) {
|
|
256
|
+
_addChild(el, val);
|
|
257
|
+
},
|
|
258
|
+
child_r(el, val) {
|
|
259
|
+
EL.clear(el);
|
|
260
|
+
_addChild(el, val);
|
|
261
|
+
},
|
|
262
|
+
style(el, val) {
|
|
263
|
+
_applyObj('style', el, val,
|
|
264
|
+
r => el.style.cssText += r + ';',
|
|
265
|
+
(k, v) => {
|
|
266
|
+
(k.startsWith('--')) ? el.style.setProperty(k, v) : el.style[k] = v;
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
},
|
|
270
|
+
style_r(el, val) {
|
|
271
|
+
el.style.cssText = '';
|
|
272
|
+
PARAM_UPD.style(el, val);
|
|
273
|
+
},
|
|
274
|
+
class(el, val) {
|
|
275
|
+
if (is.arr(val)) val = Object.fromEntries(val.filter(Boolean).map(c => [c, true]));
|
|
276
|
+
_applyObj('class', el, val,
|
|
277
|
+
r => r.split(/\s+/).forEach(c => c && el.classList.add(c)),
|
|
278
|
+
(k, v) => v ? el.classList.add(k) : el.classList.remove(k)
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
class_r(el, val) {
|
|
282
|
+
el.className = '';
|
|
283
|
+
PARAM_UPD.class(el, val);
|
|
284
|
+
},
|
|
285
|
+
parent(el, val) {
|
|
286
|
+
EL.mount(el, val, false, (el._onMount || el._onRender) ? 100 : 0);
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
function _applyP(param, el, val, set, del) {
|
|
291
|
+
for (let p in val) {
|
|
292
|
+
let v = _makeVal(el, param, p, val[p]);
|
|
293
|
+
(v == null) ? del(p) : set(p, String(v));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function _applyObj(param, el, val, raw, add) {
|
|
298
|
+
if (is.str(val)) {
|
|
299
|
+
raw(val);
|
|
300
|
+
} else {
|
|
301
|
+
for (let k in val) {
|
|
302
|
+
if (k == '_raw') PARAM_UPD[param](el, val[k]);
|
|
303
|
+
else add(k, _makeVal(el, param, k, val[k]));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function _addChild(el, obj) {
|
|
309
|
+
if (!obj) return;
|
|
310
|
+
else if (is.str(obj)) el.insertAdjacentHTML('beforeend', obj);
|
|
311
|
+
else if (is.arr(obj)) obj.forEach(o => _addChild(el, o));
|
|
312
|
+
else if (is.node(obj)) EL.mount(obj, el);
|
|
313
|
+
else if (obj instanceof EL) EL.mount(obj.$root, el);
|
|
314
|
+
else if (is.obj(obj)) EL.make(null, { ctx: el.ctx, ...obj, parent: el }, el instanceof SVGElement);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function _makeVal(el, param, sub, val) {
|
|
318
|
+
/// #if !TINY_COMPONENT
|
|
319
|
+
if (is.func(val)) {
|
|
320
|
+
return val.call(el.ctx, { el, ctx: el.ctx });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (is.arr(val) && is.state(val[0])) {
|
|
324
|
+
return val.map(v => _makeVal(el, param, sub, v))[0];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (is.state(val)) {
|
|
328
|
+
const fn = (name, value) => {
|
|
329
|
+
value = val.map({ el, ctx: el.ctx, name, value });
|
|
330
|
+
_update(el, param, sub ? { [sub]: value } : value);
|
|
331
|
+
if (el._onUpdate) el._onUpdate();
|
|
332
|
+
}
|
|
333
|
+
const key = param + '.' + sub + '.' + val.name;
|
|
334
|
+
if (!el._unsub) el._unsub = {};
|
|
335
|
+
if (key in el._unsub) el._unsub[key]();
|
|
336
|
+
el._unsub[key] = val._state.subscribe(val.name, fn);
|
|
337
|
+
// (el._unsub ??= []).push(val._state.subscribe(val.name, fn));
|
|
338
|
+
return val.map({ el, ctx: el.ctx, name: val.name, value: val._state[val.name] });
|
|
339
|
+
}
|
|
340
|
+
/// #endif
|
|
341
|
+
|
|
342
|
+
return val;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function _update(el, param, val) {
|
|
346
|
+
if (SKIP_PARAM.has(param)) return;
|
|
347
|
+
|
|
348
|
+
param = PARAM_ALIAS[param] ?? param;
|
|
349
|
+
|
|
350
|
+
if (param.startsWith('on')) {
|
|
351
|
+
val = { [param.slice(2).toLowerCase()]: val };
|
|
352
|
+
param = 'events';
|
|
353
|
+
} else {
|
|
354
|
+
/// #if !TINY_COMPONENT
|
|
355
|
+
val = _makeVal(el, param, null, val);
|
|
356
|
+
/// #endif
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const fn = PARAM_UPD[param];
|
|
360
|
+
if (val != null && fn) fn(el, val);
|
|
361
|
+
else PARAM_UPD.props(el, { [param]: val });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const is = {
|
|
365
|
+
func: x => typeof x === 'function',
|
|
366
|
+
str: x => typeof x === 'string',
|
|
367
|
+
arr: x => Array.isArray(x),
|
|
368
|
+
node: x => x instanceof Node,
|
|
369
|
+
obj: x => x !== null && typeof x === 'object',
|
|
370
|
+
state: x => x && x._state,
|
|
371
|
+
}
|
package/src/SVG.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EL } from "./EL";
|
|
2
|
+
|
|
3
|
+
export class SVG {
|
|
4
|
+
static svg = (attrs = {}, cfg = {}) => ({ tag: 'svg', ...cfg, attrs });
|
|
5
|
+
static rect = (x, y, width, height, rx, ry, attrs = {}, cfg = {}) => ({ tag: 'rect', ...cfg, attrs: { ...attrs, x, y, width, height, rx, ry } });
|
|
6
|
+
static circle = (cx, cy, r, attrs = {}, cfg = {}) => ({ tag: 'circle', ...cfg, attrs: { ...attrs, cx, cy, r } });
|
|
7
|
+
static line = (x1, y1, x2, y2, attrs = {}, cfg = {}) => ({ tag: 'line', ...cfg, attrs: { ...attrs, x1, y1, x2, y2 } });
|
|
8
|
+
static polyline = (points, attrs = {}, cfg = {}) => ({ tag: 'polyline', ...cfg, attrs: { ...attrs, points } });
|
|
9
|
+
static polygon = (points, attrs = {}, cfg = {}) => ({ tag: 'polygon', ...cfg, attrs: { ...attrs, points } });
|
|
10
|
+
static path = (d, attrs = {}, cfg = {}) => ({ tag: 'path', ...cfg, attrs: { ...attrs, d } });
|
|
11
|
+
static text = (text, x, y, attrs = {}, cfg = {}) => ({ tag: 'text', ...cfg, text, attrs: { ...attrs, x, y } });
|
|
12
|
+
|
|
13
|
+
static make = (tag, cfg) => EL.make(tag, cfg, true);
|
|
14
|
+
static update = EL.update;
|
|
15
|
+
static config = EL.update; // legacy
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Object.getOwnPropertyNames(SVG).forEach(name => SVG['make_' + name] = (...args) => SVG.make(null, SVG[name](...args)));
|
package/src/Sheet.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { EL } from "./EL";
|
|
2
|
+
|
|
3
|
+
//#region Sheet
|
|
4
|
+
export class Sheet {
|
|
5
|
+
/**
|
|
6
|
+
* Добавить стиль с уникальным id в head. ext - стиль можно будет удалить по id
|
|
7
|
+
* @param {string|array} style стили в виде css строки
|
|
8
|
+
* @param {string|this} id уникальный id стиля. При передаче this будет именем класса
|
|
9
|
+
* @param {boolean} ext внешний стиль - может быть удалён по id
|
|
10
|
+
*/
|
|
11
|
+
static addStyle(style, id, ext = false) {
|
|
12
|
+
if (!style || !id) return;
|
|
13
|
+
if (typeof id === 'object') id = id.constructor.name;
|
|
14
|
+
|
|
15
|
+
if (!Sheet.#int.has(id) && !Sheet.#ext.has(id)) {
|
|
16
|
+
if (ext) {
|
|
17
|
+
let sheet = document.createElement('style');
|
|
18
|
+
document.head.appendChild(sheet);
|
|
19
|
+
sheet.textContent = style;
|
|
20
|
+
Sheet.#ext.set(id, sheet);
|
|
21
|
+
} else {
|
|
22
|
+
if (!Sheet.#sheet) Sheet.#sheet = document.head.appendChild(document.createElement('style'));
|
|
23
|
+
Sheet.#sheet.textContent += style + '\r\n';
|
|
24
|
+
Sheet.#int.add(id);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Удалить ext стиль по его id
|
|
31
|
+
* @param {string} id id стиля. При передаче this будет именем класса
|
|
32
|
+
*/
|
|
33
|
+
static removeStyle(id) {
|
|
34
|
+
if (typeof id === 'object') id = id.constructor.name;
|
|
35
|
+
if (Sheet.#ext.has(id)) {
|
|
36
|
+
Sheet.#ext.get(id).remove();
|
|
37
|
+
Sheet.#ext.delete(id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static #sheet = null;
|
|
42
|
+
static #int = new Set();
|
|
43
|
+
static #ext = new Map();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#region StyledComponent
|
|
47
|
+
export class StyledComponent extends EL {
|
|
48
|
+
/**
|
|
49
|
+
* Создать элемент и поместить его в переменную $root
|
|
50
|
+
* @param {string} tag html tag элемента
|
|
51
|
+
* @param {object} data параметры
|
|
52
|
+
* @param {string|array} style стили в виде css строки
|
|
53
|
+
* @param {string|this} id уникальный id стиля. При передаче this будет именем класса
|
|
54
|
+
* @param {boolean} ext внешний стиль - может быть удалён по id
|
|
55
|
+
*/
|
|
56
|
+
constructor(tag, data = {}, style = null, id = null, ext = false) {
|
|
57
|
+
super(tag, data);
|
|
58
|
+
Sheet.addStyle(style, id, ext);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Создать элемент
|
|
63
|
+
* @param {string} tag html tag элемента
|
|
64
|
+
* @param {object} data параметры
|
|
65
|
+
* @param {string|array} style стили в виде css строки
|
|
66
|
+
* @param {string|this} id уникальный id стиля. При передаче this будет именем класса
|
|
67
|
+
* @param {boolean} ext внешний стиль - может быть удалён по id
|
|
68
|
+
*/
|
|
69
|
+
static make(tag, data = {}, style = null, id = null, ext = false) {
|
|
70
|
+
Sheet.addStyle(style, id, ext);
|
|
71
|
+
return EL.make(tag, data);
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/State.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class State {
|
|
2
|
+
constructor(init = {}) {
|
|
3
|
+
this._data = {};
|
|
4
|
+
this._subs = new Map();
|
|
5
|
+
this.addStates(init);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// добавить состояния
|
|
9
|
+
addStates(obj) {
|
|
10
|
+
for (const name in obj) {
|
|
11
|
+
if (name in this || name in this._data) continue;
|
|
12
|
+
|
|
13
|
+
this._data[name] = obj[name];
|
|
14
|
+
|
|
15
|
+
Object.defineProperty(this, name, {
|
|
16
|
+
get: () => this._data[name],
|
|
17
|
+
set: (value) => {
|
|
18
|
+
if (Object.is(this._data[name], value)) return;
|
|
19
|
+
this._data[name] = value;
|
|
20
|
+
const subs = this._subs.get(name);
|
|
21
|
+
if (subs) subs.forEach(fn => fn(name, value));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// имеет состояние
|
|
28
|
+
hasState(name) {
|
|
29
|
+
return name in this._data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// подключить состояние
|
|
33
|
+
bind(name, map = (e) => e.value) {
|
|
34
|
+
return { _state: this, name, map };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// подписаться, функция вида fn(name, val)
|
|
38
|
+
subscribe(name, fn) {
|
|
39
|
+
if (!this._subs.has(name)) this._subs.set(name, new Set());
|
|
40
|
+
this._subs.get(name).add(fn);
|
|
41
|
+
return () => this._subs.get(name).delete(fn);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useState(init) {
|
|
46
|
+
return new State(init);
|
|
47
|
+
}
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region CSS
|
|
2
|
+
|
|
3
|
+
// Добавить стили, уникально. Без ID будет вычислен хэш
|
|
4
|
+
export function addCSS(css, id = '') {
|
|
5
|
+
if (!id) id = _hash(css);
|
|
6
|
+
if (cssMap.has(id)) return;
|
|
7
|
+
|
|
8
|
+
const style = document.createElement('style');
|
|
9
|
+
style.textContent = css;
|
|
10
|
+
document.head.appendChild(style);
|
|
11
|
+
cssMap.set(id, style);
|
|
12
|
+
return style;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Удалить стили. Без ID будет вычислен хэш
|
|
16
|
+
export function removeCSS(css, id = '') {
|
|
17
|
+
if (!id) id = _hash(css);
|
|
18
|
+
const style = cssMap.get(id);
|
|
19
|
+
if (!style) return;
|
|
20
|
+
|
|
21
|
+
cssMap.delete(id);
|
|
22
|
+
style.remove();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function _hash(str) {
|
|
26
|
+
let hash = 5381;
|
|
27
|
+
for (let i = 0; i < str.length; i++) {
|
|
28
|
+
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
|
29
|
+
hash |= 0;
|
|
30
|
+
}
|
|
31
|
+
return hash.toString(36);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cssMap = new Map(); // hash: styleElement
|
|
35
|
+
|
|
36
|
+
//#region watch
|
|
37
|
+
|
|
38
|
+
// проверить статус подключения в DOM и рендера
|
|
39
|
+
export function watchMount(el, waitRender = false, tries = 100) {
|
|
40
|
+
return new Promise(res => {
|
|
41
|
+
let mounted, rendered;
|
|
42
|
+
const check = () => {
|
|
43
|
+
if (el.isConnected) {
|
|
44
|
+
if (!el._mounted) {
|
|
45
|
+
el._mounted = true;
|
|
46
|
+
if (el._onMount) el._onMount();
|
|
47
|
+
}
|
|
48
|
+
if (!mounted) {
|
|
49
|
+
mounted = true;
|
|
50
|
+
if (!waitRender) res(el);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (el.clientWidth || el.clientHeight) {
|
|
54
|
+
if (!el._rendered) {
|
|
55
|
+
el._rendered = true;
|
|
56
|
+
if (el._onRender) el._onRender();
|
|
57
|
+
}
|
|
58
|
+
if (!rendered) {
|
|
59
|
+
rendered = true;
|
|
60
|
+
if (waitRender) res(el);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (tries--) {
|
|
66
|
+
requestAnimationFrame(check);
|
|
67
|
+
} else {
|
|
68
|
+
if ((waitRender && !rendered) || (!waitRender && !mounted)) res(null);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
check();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// следить за обновлениями размера элемента
|
|
77
|
+
export function watchResize(el, onResize) {
|
|
78
|
+
const ro = new ResizeObserver(entries => {
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
if (entry.target === el) onResize(entry);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
ro.observe(el);
|
|
85
|
+
|
|
86
|
+
const mo = new MutationObserver(() => {
|
|
87
|
+
if (!el.isConnected) {
|
|
88
|
+
ro.disconnect();
|
|
89
|
+
mo.disconnect();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
mo.observe(document.body, { childList: true, subtree: true });
|
|
94
|
+
}
|