@alexgyver/component 1.4.0 → 1.4.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/Component.js +134 -52
- package/README.md +135 -18
- package/package.json +1 -1
package/Component.js
CHANGED
|
@@ -19,28 +19,10 @@ export class EL {
|
|
|
19
19
|
* @param {object} data параметры
|
|
20
20
|
* @param {Boolean} svg SVG
|
|
21
21
|
* @returns {Node}
|
|
22
|
-
* @params
|
|
23
|
-
* tag {string} - тег html элемента. Если 'svg' - включится режим SVG на детей
|
|
24
|
-
* context {object} - объект для параметра '$', прокидывается в детей
|
|
25
|
-
* parent - {Element} добавить созданный элемент к указанному элементу
|
|
26
|
-
* text {string} - добавить в textContent
|
|
27
|
-
* html {string} - добавить в innerHTML
|
|
28
|
-
* class {string | Array} - добавить в className
|
|
29
|
-
* style/style_r {string | object} - объект в виде { padding: '0px', ... } или строка css стилей. _r - заменить имеющиеся
|
|
30
|
-
* push {array} - добавить к указанному массиву
|
|
31
|
-
* $ {string} - создать переменную $имя в указанном объекте
|
|
32
|
-
* events {object} - добавить addEventListener'ы {event: e => {}}
|
|
33
|
-
* click, change, input, mousewheel - добавление события без events
|
|
34
|
-
* children/children_r - дети, массив {DOM, EL, object, html string}. _r - заменить имеющиеся. Без тега tag будет div
|
|
35
|
-
* child/child_r - ребенок {DOM, EL, object, html string}. _r - заменить имеющиеся. Без тега tag будет div
|
|
36
|
-
* attrs {object} - добавить аттрибуты (как setAttribute())
|
|
37
|
-
* props {object} - добавить свойства (как el[prop])
|
|
38
|
-
* also {function} - вызвать с текущим элементом: also(el) { console.log(el); }
|
|
39
|
-
* всё остальное будет добавлено как property
|
|
40
22
|
*/
|
|
41
23
|
static make(tag, data = {}, svg = false) {
|
|
42
|
-
if (!tag || typeof data !== 'object') return null;
|
|
43
24
|
if (data instanceof Node) return data;
|
|
25
|
+
if (!tag) tag = 'div';
|
|
44
26
|
if (tag == 'svg') svg = true;
|
|
45
27
|
return EL.config(svg ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag), data, svg);
|
|
46
28
|
}
|
|
@@ -61,57 +43,138 @@ export class EL {
|
|
|
61
43
|
el.forEach(e => EL.config(e, data, svg));
|
|
62
44
|
return null;
|
|
63
45
|
}
|
|
64
|
-
if (!(el instanceof Node) || (typeof data !== 'object'))
|
|
46
|
+
if (!(el instanceof Node) || (typeof data !== 'object')) {
|
|
47
|
+
return el;
|
|
48
|
+
}
|
|
65
49
|
|
|
66
50
|
let ctx = data.context;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
51
|
+
if (ctx === undefined) ctx = EL.context;
|
|
52
|
+
else EL.context = ctx;
|
|
53
|
+
|
|
54
|
+
const addChild = (obj) => {
|
|
55
|
+
if (obj) {
|
|
56
|
+
if (obj instanceof Node) el.appendChild(obj);
|
|
57
|
+
else if (obj instanceof EL) el.appendChild(obj.$root);
|
|
58
|
+
else if (typeof obj === 'string') el.insertAdjacentHTML('beforeend', obj);
|
|
59
|
+
else if (typeof obj === 'object') {
|
|
60
|
+
let cmp = EL.make(obj.tag, obj, svg || obj.tag == 'svg');
|
|
61
|
+
if (cmp) el.appendChild(cmp);
|
|
62
|
+
}
|
|
78
63
|
}
|
|
79
64
|
}
|
|
80
65
|
|
|
66
|
+
const getClasses = (cls) => {
|
|
67
|
+
if (Array.isArray(cls)) return cls;
|
|
68
|
+
if (typeof cls === 'string') return cls.split(/\s+/);
|
|
69
|
+
if (typeof cls === 'object') return Object.keys(cls).filter(k => cls[k]);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const mount = () => {
|
|
74
|
+
if (el.isConnected) {
|
|
75
|
+
el._mounted = true;
|
|
76
|
+
data.onMount(el);
|
|
77
|
+
} else requestAnimationFrame(mount);
|
|
78
|
+
}
|
|
79
|
+
|
|
81
80
|
for (const [key, val] of Object.entries(data)) {
|
|
82
81
|
switch (key) {
|
|
83
|
-
case 'text': el.textContent = (val == null) ? '' : (val
|
|
84
|
-
case 'html': el.innerHTML = (val == null) ? '' : (val
|
|
82
|
+
case 'text': el.textContent = (val == null) ? '' : String(val); continue; // == - null + undef
|
|
83
|
+
case 'html': el.innerHTML = (val == null) ? '' : String(val); continue;
|
|
85
84
|
case 'tag':
|
|
86
85
|
case 'context':
|
|
87
86
|
case 'get':
|
|
88
87
|
case 'also':
|
|
88
|
+
case 'onConfig':
|
|
89
|
+
case 'onMount':
|
|
89
90
|
continue;
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
+
|
|
93
|
+
if (val === undefined || val === null) continue;
|
|
92
94
|
|
|
93
95
|
switch (key) {
|
|
94
|
-
case '
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
case '
|
|
99
|
-
case '
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
case '
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
case 'push':
|
|
97
|
+
val.push(el);
|
|
98
|
+
break;
|
|
99
|
+
|
|
100
|
+
case '$':
|
|
101
|
+
case 'var':
|
|
102
|
+
if (ctx) ctx['$' + val] = el;
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'events':
|
|
106
|
+
for (let ev in val) el.addEventListener(ev, ctx ? val[ev].bind(ctx) : val[ev]);
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case 'click':
|
|
110
|
+
case 'change':
|
|
111
|
+
case 'input':
|
|
112
|
+
case 'mousewheel':
|
|
113
|
+
el.addEventListener(key, ctx ? val.bind(ctx) : val);
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'parent':
|
|
117
|
+
val.appendChild(el);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'attrs':
|
|
121
|
+
for (let attr in val) el.setAttribute(attr, val[attr]);
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
case 'props':
|
|
125
|
+
for (let prop in val) el[prop] = val[prop];
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case 'data':
|
|
129
|
+
for (let key in val) el.dataset[key] = val[key];
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'child_r':
|
|
133
|
+
EL.clear(el);
|
|
134
|
+
// fall
|
|
135
|
+
case 'child':
|
|
136
|
+
addChild(val);
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case 'children_r':
|
|
140
|
+
EL.clear(el);
|
|
141
|
+
// fall
|
|
142
|
+
case 'children':
|
|
143
|
+
for (let obj of val) addChild(obj);
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'animate': {
|
|
147
|
+
const { duration = 300, easing = 'ease', ...styles } = val;
|
|
148
|
+
el.style.transition = Object.keys(styles).map(st => `${st} ${duration}ms ${easing}`).join(', ');
|
|
149
|
+
requestAnimationFrame(() => { for (let st in styles) el.style[st] = styles[st]; });
|
|
150
|
+
} break;
|
|
151
|
+
|
|
152
|
+
case 'style_r':
|
|
153
|
+
el.style.cssText = '';
|
|
154
|
+
// fall
|
|
107
155
|
case 'style':
|
|
108
|
-
if (typeof val === 'string')
|
|
109
|
-
|
|
156
|
+
if (typeof val === 'string') {
|
|
157
|
+
el.style.cssText += val + ';';
|
|
158
|
+
} else for (let st in val) {
|
|
159
|
+
if (val[st]) el.style[st] = val[st];
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case 'class_r':
|
|
164
|
+
el.className = '';
|
|
165
|
+
// fall
|
|
166
|
+
case 'class':
|
|
167
|
+
getClasses(val).forEach(c => c && el.classList.add(c));
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
default: el[key] = val;
|
|
110
171
|
break;
|
|
111
|
-
default: el[key] = val; break;
|
|
112
172
|
}
|
|
113
173
|
}
|
|
114
|
-
|
|
174
|
+
|
|
175
|
+
if (data.also) ctx ? data.also.call(ctx, el) : data.also(el);
|
|
176
|
+
if (data.onMount && !el._mounted) mount();
|
|
177
|
+
if (data.onConfig) data.onConfig(el);
|
|
115
178
|
return el;
|
|
116
179
|
}
|
|
117
180
|
static configIn(ctx, el, data, svg = false) {
|
|
@@ -124,7 +187,7 @@ export class EL {
|
|
|
124
187
|
* @param {HTMLElement} el
|
|
125
188
|
*/
|
|
126
189
|
static clear(el) {
|
|
127
|
-
while (el.firstChild) el.removeChild(el.
|
|
190
|
+
while (el.firstChild) el.removeChild(el.firstChild);
|
|
128
191
|
}
|
|
129
192
|
|
|
130
193
|
/**
|
|
@@ -172,6 +235,25 @@ export class EL {
|
|
|
172
235
|
// legacy
|
|
173
236
|
export const Component = EL;
|
|
174
237
|
|
|
238
|
+
//#region State
|
|
239
|
+
export class State {
|
|
240
|
+
constructor(init = {}) {
|
|
241
|
+
this.data = init;
|
|
242
|
+
this.subs = new Set();
|
|
243
|
+
}
|
|
244
|
+
set(key, value) {
|
|
245
|
+
this.data[key] = value;
|
|
246
|
+
this.subs.forEach(fn => fn(this.data));
|
|
247
|
+
}
|
|
248
|
+
get(key) {
|
|
249
|
+
return this.data[key];
|
|
250
|
+
}
|
|
251
|
+
subscribe(fn) {
|
|
252
|
+
this.subs.add(fn);
|
|
253
|
+
return () => this.subs.delete(fn);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
175
257
|
//#region SVG
|
|
176
258
|
export class SVG {
|
|
177
259
|
static make = (tag, data) => EL.make(tag, data, true);
|
package/README.md
CHANGED
|
@@ -4,7 +4,36 @@
|
|
|
4
4
|
> npm i @alexgyver/component
|
|
5
5
|
|
|
6
6
|
## Дока
|
|
7
|
-
###
|
|
7
|
+
### EL
|
|
8
|
+
Параметры для конфига:
|
|
9
|
+
|
|
10
|
+
- `context` - объект, в контексте которого будут созданы некоторые параметры
|
|
11
|
+
- `$` - текст, добавляет созданный элемент в контекст с именем $значение
|
|
12
|
+
- `events` - объект с событиями вида `{eventName: handlerFunc}`, привязываются к контексту, если указан
|
|
13
|
+
- `click`, `change`, `input`, `mousewheel` - события, указывается обработчик. Короткая форма подключения этих событий
|
|
14
|
+
- `also` - функция, будет вызвана с созданным элементом, в контексте, если он указан
|
|
15
|
+
- `text` - текст, добавится в textContent, нулевые значения - очистить
|
|
16
|
+
- `html` - текст, добавится в innerHTML, нулевые значения - очистить
|
|
17
|
+
- `tag` - HTML тег (для child-объектов)
|
|
18
|
+
- `push` - массив, добавляет созданный элемент в указанный массив
|
|
19
|
+
- `parent` - Node элемент, к которому добавить созданный элемент как child
|
|
20
|
+
- `attrs` - объект с аттрибутами для установки через setAttribute
|
|
21
|
+
- `props` - объект со свойствами, будут добавлены просто как el[prop] = val
|
|
22
|
+
- `data` - объект с датасетами вида `{name: value}`, будут добавлены как аттрибуты data-name = value
|
|
23
|
+
- `child`, `child_r` - объект конфига, добавится как ребёнок к созданному элементу. Нужно указать tag, без указания будет div
|
|
24
|
+
- `children`, `children_r` - массив объектов конфига, добавятся как дети к созданному элементу. Нужно указать tag, без указания будет div
|
|
25
|
+
- `animate` - объект стилей `{styleName: value}`, может содержать параметры анимации duration и easing
|
|
26
|
+
- `style`, `style_r` - стили. Принимает:
|
|
27
|
+
- Строки CSS стилей
|
|
28
|
+
- Объект вида `{styleName: value}`
|
|
29
|
+
- `class`, `class_r` - установка классов в classList, версия с `_r` - заменить классы. Принимает:
|
|
30
|
+
- Строки вида `newClass active`
|
|
31
|
+
- Массивы вида `['newClass', 'active']`, причём можно по условию: `['newClass', isActive && 'active']`
|
|
32
|
+
- Объекты вида `{newClass: true, active: false}` - true значение добавляет класс
|
|
33
|
+
- `onConfig` - функция, вызовется при настройке через функцию config
|
|
34
|
+
- `onMount` - функция, вызовется при присоединении к DOM
|
|
35
|
+
- Другие значения будут добавлены как свойства
|
|
36
|
+
|
|
8
37
|
```js
|
|
9
38
|
/**
|
|
10
39
|
* Создать компонент и поместить его в переменную $root
|
|
@@ -18,22 +47,6 @@ EL(tag, data = {}, svg = false);
|
|
|
18
47
|
* @param {string} tag html tag элемента
|
|
19
48
|
* @param {object} data параметры
|
|
20
49
|
* @returns {Node}
|
|
21
|
-
* tag {string} - тег html элемента. Если 'svg' - включится режим SVG на детей
|
|
22
|
-
* context {object} - контекст для параметра 'var' и вызовов, прокидывается в детей. Если указан явно как null - прерывает проброс
|
|
23
|
-
* parent - {Element} добавляет компонент к указанному элементу
|
|
24
|
-
* text {string} - добавить в textContent
|
|
25
|
-
* html {string} - добавить в innerHTML
|
|
26
|
-
* class {string | Array} - добавить в className
|
|
27
|
-
* style {string | object} - объект в виде { padding: '0px', ... } или строка css стилей
|
|
28
|
-
* push {array} - добавить к указанному массиву
|
|
29
|
-
* var | $ {string} - создаёт переменную $имя в указанном контексте
|
|
30
|
-
* events {object} - добавляет addEventListener'ы {event: e => {}}
|
|
31
|
-
* children/children_r - массив {DOM, EL, object, html string}. _r - заменить имеющиеся. Без тега tag будет div
|
|
32
|
-
* child/child_r - {DOM, EL, object, html string}. _r - заменить имеющиеся. Без тега tag будет div
|
|
33
|
-
* attrs {object} - добавить аттрибуты (через setAttribute)
|
|
34
|
-
* props {object} - добавить свойства (как el[prop])
|
|
35
|
-
* also {function} - вызвать с текущим компонентом: also(el) { console.log(el); }
|
|
36
|
-
* всё остальное будет добавлено как property
|
|
37
50
|
*/
|
|
38
51
|
EL.make(tag, data = {});
|
|
39
52
|
|
|
@@ -118,8 +131,9 @@ StyledComponent(tag, data = {}, style = null, id = null, ext = false);
|
|
|
118
131
|
StyledComponent.make(tag, data = {}, style = null, id = null, ext = false);
|
|
119
132
|
```
|
|
120
133
|
|
|
121
|
-
##
|
|
134
|
+
## Примеры
|
|
122
135
|
Создаст контейнер с двумя вложенными блоками текста и прикрепит к body
|
|
136
|
+
|
|
123
137
|
```js
|
|
124
138
|
EL.make('div', {
|
|
125
139
|
parent: document.body,
|
|
@@ -143,6 +157,46 @@ EL.make('div', {
|
|
|
143
157
|
});
|
|
144
158
|
```
|
|
145
159
|
|
|
160
|
+
Простой элемент с текстом и классами
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
const box = new EL('div', {
|
|
164
|
+
text: 'Привет, мир!',
|
|
165
|
+
class: ['box', 'highlight'],
|
|
166
|
+
style: { padding: '10px', color: 'white', backgroundColor: 'blue' },
|
|
167
|
+
click() { console.log('Box кликнут!'); },
|
|
168
|
+
$: 'myBox' // создаёт ctx.$myBox
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
document.body.appendChild(box.$root);
|
|
172
|
+
console.log(box.$root); // сам DOM-элемент
|
|
173
|
+
console.log(EL.context.$myBox); // доступ через контекст
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Полная замена классов и стилей
|
|
177
|
+
|
|
178
|
+
```js
|
|
179
|
+
EL.config(someElement, {
|
|
180
|
+
text: 'Обновлённый блок',
|
|
181
|
+
class_r: ['newClass', 'active'], // полностью заменяет классы
|
|
182
|
+
style_r: { color: 'red', fontWeight: 'bold' } // сброс и новые стили
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Вложенные дети и массив children
|
|
187
|
+
|
|
188
|
+
```cpp
|
|
189
|
+
EL.make('div', {
|
|
190
|
+
parent: document.body,
|
|
191
|
+
class: 'parent',
|
|
192
|
+
children: [
|
|
193
|
+
{ tag: 'p', text: 'Первый ребёнок', class: 'child' },
|
|
194
|
+
{ tag: 'p', text: 'Второй ребёнок', class: ['child', 'second'] },
|
|
195
|
+
'Просто текстовый узел'
|
|
196
|
+
]
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
146
200
|
Гораздо интереснее использовать в классе и передавать контекст. Параметр `$` создаст переменную с элементом с указанным именем + префикс `$`:
|
|
147
201
|
|
|
148
202
|
```js
|
|
@@ -166,6 +220,25 @@ let btn = new Button('kek');
|
|
|
166
220
|
btn.$button; // элемент кнопки
|
|
167
221
|
```
|
|
168
222
|
|
|
223
|
+
Использование контекста для $ и событий
|
|
224
|
+
|
|
225
|
+
```js
|
|
226
|
+
class MyComponent extends EL {
|
|
227
|
+
constructor() {
|
|
228
|
+
super('div', {
|
|
229
|
+
class: 'comp',
|
|
230
|
+
$: 'root',
|
|
231
|
+
children: [
|
|
232
|
+
{ tag: 'button', text: 'Нажми меня', click() { console.log(this.$root); } }
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const comp = new MyComponent();
|
|
239
|
+
document.body.appendChild(comp.$root);
|
|
240
|
+
```
|
|
241
|
+
|
|
169
242
|
Некоторые трюки
|
|
170
243
|
|
|
171
244
|
```js
|
|
@@ -194,3 +267,47 @@ EL.make('div', {
|
|
|
194
267
|
class: ['some', 'class', foo && 'plus_me'], // добавить plus_me если foo - true
|
|
195
268
|
});
|
|
196
269
|
```
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
Создание SVG
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
const svg = SVG.svg({ width: 200, height: 200 }, {
|
|
276
|
+
children: [
|
|
277
|
+
SVG.rect(10, 10, 50, 50, 5, 5, { fill: 'green' }),
|
|
278
|
+
SVG.circle(100, 100, 40, { fill: 'red' }),
|
|
279
|
+
SVG.line(0, 0, 200, 200, { stroke: 'blue', 'stroke-width': 2 })
|
|
280
|
+
]
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Shadow DOM со стилями
|
|
285
|
+
|
|
286
|
+
```js
|
|
287
|
+
const shadowHost = EL.makeShadow('div', {
|
|
288
|
+
child: { tag: 'p', text: 'Текст внутри Shadow DOM', class: 'shadow-text' }
|
|
289
|
+
}, `
|
|
290
|
+
.shadow-text { color: purple; font-weight: bold; }
|
|
291
|
+
`);
|
|
292
|
+
|
|
293
|
+
document.body.appendChild(shadowHost);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
Прочее
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
const state = new State({ count: 0 });
|
|
301
|
+
|
|
302
|
+
let d = EL.make('div', {
|
|
303
|
+
text: state.get('count'),
|
|
304
|
+
also: el => state.subscribe(d => el.textContent = d.count),
|
|
305
|
+
style: { width: '100px', height: '50px', background: 'red' },
|
|
306
|
+
animate: { width: '200px', background: 'blue', duration: 500 },
|
|
307
|
+
onConfig: el => console.log('update'),
|
|
308
|
+
onMount: el => console.log('mount'),
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
setInterval(() => state.set('count', state.get('count') + 1), 1000);
|
|
312
|
+
setTimeout(() => { document.body.appendChild(d) }, 2000);
|
|
313
|
+
```
|