~repos /atoms-element

#js

git clone https://pyrossh.dev/repos/atoms-element.git

A simple web component library for defining your custom elements. It works on both client and server.


42ef01f2 Peter John

4 years ago
improve element
element.js CHANGED
@@ -240,6 +240,15 @@ const validator = (type, validate) => (innerType) => {
240
240
  common.__default = fnOrValue;
241
241
  return common;
242
242
  };
243
+ common.compute = (...args) => {
244
+ const fn = args[args.length - 1];
245
+ const deps = args.slice(0, args.length - 1);
246
+ common.__compute = {
247
+ fn,
248
+ deps,
249
+ };
250
+ return common;
251
+ };
243
252
  return common;
244
253
  };
245
254
 
@@ -266,15 +275,6 @@ export const array = validator('array', (innerType, context, data) => {
266
275
  }
267
276
  });
268
277
 
269
- // const depsChanged = (prev, next) => prev == null || next.some((f, i) => !Object.is(f, prev[i]));
270
- // useEffect(handler, deps) {
271
- // const index = this.hooks.currentCursor++;
272
- // if (!deps || depsChanged(this.hooks.deps[index], deps)) {
273
- // this.hooks.deps[index] = deps || [];
274
- // this.hooks.effects[index] = handler;
275
- // }
276
- // }
277
-
278
278
  const normalizeCss = `html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}`;
279
279
  const fifo = (q) => q.shift();
280
280
  const filo = (q) => q.pop();
@@ -320,7 +320,6 @@ export default class AtomsElement extends BaseElement {
320
320
  this._dirty = false;
321
321
  this._connected = false;
322
322
  this._state = {};
323
- this._effects = {};
324
323
  this.ssrAttributes = ssrAttributes;
325
324
  this.config = isBrowser ? window.config : global.config;
326
325
  this.location = isBrowser ? window.location : global.location;
@@ -335,10 +334,6 @@ export default class AtomsElement extends BaseElement {
335
334
  }
336
335
  disconnectedCallback() {
337
336
  this._connected = false;
338
- // let cleanup;
339
- // while ((cleanup = this.hooks.cleanup.shift())) {
340
- // cleanup();
341
- // }
342
337
  }
343
338
 
344
339
  attributeChangedCallback(key, oldValue, newValue) {
@@ -360,7 +355,6 @@ export default class AtomsElement extends BaseElement {
360
355
  return;
361
356
  }
362
357
  this.renderTemplate();
363
- this.enqueueEffects();
364
358
  this._dirty = false;
365
359
  }
366
360
 
@@ -374,23 +368,10 @@ export default class AtomsElement extends BaseElement {
374
368
  q.push(this) === 1 && run();
375
369
  }
376
370
 
377
- enqueueEffects() {
378
- this.batch(task, filo, () => this._flushEffects());
379
- }
380
-
381
371
  enqueueUpdate() {
382
372
  this.batch(microtask, fifo, () => this._performUpdate());
383
373
  }
384
374
 
385
- _flushEffects() {
386
- Object.keys(this.constructor.effects).forEach((key) => {
387
- const effect = this.constructor.effects[key];
388
- // if (!effect.deps || depsChanged(, effect.deps)) {
389
- // }
390
- effect.callback(this.attrs, this.state);
391
- });
392
- }
393
-
394
375
  get attrs() {
395
376
  return Object.keys(this.constructor.attrTypes).reduceRight((acc, key) => {
396
377
  const attrType = this.constructor.attrTypes[key];
@@ -409,14 +390,30 @@ export default class AtomsElement extends BaseElement {
409
390
  this._state[key] = typeof stateType.__default === 'function' ? stateType.__default(this.attrs, this._state) : stateType.__default;
410
391
  }
411
392
  acc[key] = this._state[key];
412
- acc[`set${key[0].toUpperCase()}${key.slice(1)}`] = (v) => () => {
393
+ acc[`set${key[0].toUpperCase()}${key.slice(1)}`] = (v) => {
413
- this._state[key] = v;
394
+ // TODO: check type on set
395
+ this._state[key] = typeof v === 'function' ? v(this._state[key]) : v;
414
396
  this.update();
415
397
  };
416
398
  return acc;
417
399
  }, {});
418
400
  }
419
401
 
402
+ get computed() {
403
+ return Object.keys(this.constructor.computedTypes).reduceRight((acc, key) => {
404
+ const type = this.constructor.computedTypes[key];
405
+ const state = this.state;
406
+ const values = type.__compute.deps.reduce((acc, key) => {
407
+ if (typeof state[key] !== undefined) {
408
+ acc.push(state[key]);
409
+ }
410
+ return acc;
411
+ }, []);
412
+ acc[key] = type.__compute.fn(...values);
413
+ return acc;
414
+ }, {});
415
+ }
416
+
420
417
  renderTemplate() {
421
418
  const template = this.render();
422
419
  const result = render(template, this);
element.test.js CHANGED
@@ -250,13 +250,12 @@ test('AtomsElement', async () => {
250
250
  count: number().required().default(0),
251
251
  };
252
252
 
253
- static effects = {
253
+ static computedTypes = {
254
- logEffect: {
254
+ sum: number()
255
+ .required()
255
- callback: (attrs, state) => {
256
+ .compute('count', (count) => {
256
- console.log(attrs, state);
257
+ return count + 10;
257
- },
258
+ }),
258
- deps: ['attrs.perPage', 'state.count'],
259
- },
260
259
  };
261
260
 
262
261
  static styles = css`
@@ -271,10 +270,12 @@ test('AtomsElement', async () => {
271
270
  address: { street },
272
271
  } = this.attrs;
273
272
  const { count, setCount } = this.state;
273
+ const { sum } = this.computed;
274
274
  return html`
275
275
  <div perPage=${perPage}>
276
276
  <p>street: ${street}</p>
277
277
  <p>count: ${count}</p>
278
+ <p>sum: ${sum}</p>
278
279
  ${this.renderItem()}
279
280
  </div>
280
281
  `;
@@ -295,6 +296,7 @@ test('AtomsElement', async () => {
295
296
  <div perPage="1">
296
297
  <p>street: 123</p>
297
298
  <p>count: 0</p>
299
+ <p>sum: 10</p>
298
300
  <div><p>render item 1</p></div>
299
301
  </div>
300
302
 
example/app-counter.js CHANGED
@@ -16,6 +16,14 @@ class Counter extends AtomsElement {
16
16
  .default((attrs) => attrs.meta?.start || 0),
17
17
  };
18
18
 
19
+ static computedTypes = {
20
+ sum: number()
21
+ .required()
22
+ .compute('count', (count) => {
23
+ return count + 10;
24
+ }),
25
+ };
26
+
19
27
  static styles = css`
20
28
  .container {
21
29
  }
@@ -24,6 +32,7 @@ class Counter extends AtomsElement {
24
32
  render() {
25
33
  const { name } = this.attrs;
26
34
  const { count, setCount } = this.state;
35
+ const { sum } = this.computed;
27
36
 
28
37
  return html`
29
38
  <div>
@@ -32,6 +41,7 @@ class Counter extends AtomsElement {
32
41
  <button @click=${() => setCount((v) => v - 1)}>-</button>
33
42
  <div class="mx-20">
34
43
  <h1 class="text-1xl">${count}</h1>
44
+ <h1 class="text-1xl">${sum}</h1>
35
45
  </div>
36
46
  <button @click=${() => setCount((v) => v + 1)}>+</button>
37
47
  </div>
example/index.html ADDED
@@ -0,0 +1,10 @@
1
+ <html>
2
+ <body>
3
+ <div>
4
+ <app-counter name="1"></app-counter>
5
+ </div>
6
+ <script type="module">
7
+ import './app-counter.js';
8
+ </script>
9
+ </body>
10
+ </html>
example/index.js CHANGED
@@ -7,6 +7,10 @@ class CounterPage extends Page {
7
7
  return '/counter';
8
8
  }
9
9
 
10
+ datapaths() {
11
+ return '/data/items/**';
12
+ }
13
+
10
14
  styles() {
11
15
  return css``;
12
16
  }
package.json CHANGED
@@ -33,6 +33,7 @@
33
33
  "node": ">=14.0.0"
34
34
  },
35
35
  "scripts": {
36
+ "example": "npx serve ./",
36
37
  "test": "NODE_OPTIONS=--experimental-vm-modules jest"
37
38
  },
38
39
  "devDependencies": {