@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.
Files changed (3) hide show
  1. package/Component.js +134 -52
  2. package/README.md +135 -18
  3. 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')) return el;
46
+ if (!(el instanceof Node) || (typeof data !== 'object')) {
47
+ return el;
48
+ }
65
49
 
66
50
  let ctx = data.context;
67
- EL.context = (ctx === null) ? null : (ctx ? ctx : EL.context);
68
- ctx = EL.context;
69
-
70
- let addChild = (obj) => {
71
- if (!obj) return;
72
- if (obj instanceof Node) el.appendChild(obj);
73
- else if (obj instanceof EL) el.appendChild(obj.$root);
74
- else if (typeof obj === 'string') el.innerHTML += obj;
75
- else if (typeof obj === 'object') {
76
- let cmp = EL.make(obj.tag ?? 'div', obj, svg || obj.tag == 'svg');
77
- if (cmp) el.appendChild(cmp);
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 + ''); continue;
84
- case 'html': el.innerHTML = (val == null) ? '' : (val + ''); continue;
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
- if (!val) continue;
92
+
93
+ if (val === undefined || val === null) continue;
92
94
 
93
95
  switch (key) {
94
- case 'class': (Array.isArray(val) ? val : val.split(' ')).map(c => c && el.classList.add(c)); break;
95
- case 'push': val.push(el); break;
96
- case '$': case 'var': if (ctx) ctx['$' + val] = el; break;
97
- case 'events': for (let ev in val) el.addEventListener(ev, ctx ? val[ev].bind(ctx) : val[ev]); break;
98
- case 'click': case 'change': case 'input': case 'mousewheel': el.addEventListener(key, ctx ? val.bind(ctx) : val); break;
99
- case 'parent': val.appendChild(el); break;
100
- case 'attrs': for (let attr in val) el.setAttribute(attr, val[attr]); break;
101
- case 'props': for (let prop in val) el[prop] = val[prop]; break;
102
- case 'child_r': EL.clear(el); // fall
103
- case 'child': addChild(val); break;
104
- case 'children_r': EL.clear(el); // fall
105
- case 'children': for (let obj of val) addChild(obj); break;
106
- case 'style_r': el.style.cssText = ''; // fall
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') el.style.cssText += val + ';';
109
- else for (let st in val) if (val[st]) el.style[st] = val[st];
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
- if (data.also && ctx) data.also.call(ctx, el);
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.lastChild);
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
- ### Component
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
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexgyver/component",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Simple HTML&SVG element builder",
5
5
  "main": "./Component.js",
6
6
  "module": "./Component.js",