yandel 1.1.2 → 1.2.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/README.md CHANGED
@@ -199,10 +199,10 @@ function AppNotifications() {
199
199
 
200
200
  #### Future node
201
201
 
202
- Future nodes are `templates` (functions) and will be created at render time (as `components` and `text`). This applies for all type of functions that return a `ValidTemplateReturn`; also the generic tags like `div`, `span`, etc. are allowed
202
+ Future nodes are `templates` (functions) used as a child and will be created at render time (as `components` and `text`). This applies for all type of functions that return a `ValidTemplateReturn`; also the generic tags like `div`, `span`, etc. are allowed
203
203
 
204
204
  ```ts
205
- import { div, main, nav } from "yandel"
205
+ import { div, main, nav } from "yandel";
206
206
 
207
207
  function MyGridOfPhotos () {
208
208
  return div(...);
@@ -566,7 +566,7 @@ function UserConsumerWithError() {
566
566
 
567
567
  #### Future nodes
568
568
 
569
- As explained in the section of [future nodes](#future-node), `components`, `text` and `future nodes` are created at render time, and if your consumer is a function, like in the next example:
569
+ As explained in the section of [future nodes](#future-node), `components`, `text` and `future nodes` are created at render time. You have to understand how they are being created:
570
570
 
571
571
  ```ts
572
572
  function UserConsumer() {
@@ -580,7 +580,8 @@ class App extends Component {
580
580
  }
581
581
  ```
582
582
 
583
- `UserConsumer` will be created before the `UserProvider` is rendered and provides, leading to an undefined context.
583
+ When a component is created, its `render` method will be called at render time. In this example, `UserConsumer` will be called before the `UserProvider` `render`
584
+ method at render time, so the context will be undefined.
584
585
 
585
586
  ```ts
586
587
  public render(): ValidTemplateReturn {
@@ -591,11 +592,11 @@ public render(): ValidTemplateReturn {
591
592
  }
592
593
  ```
593
594
 
594
- Now, `UserConsumer` will be able to consume. Don't forget that nodes outside the provider component
595
+ Now, `UserConsumer` will be able to consume, because It will be created at render time right after the `UserProvider` `render` method, so it will be accesible.
595
596
 
596
597
  ### Ref
597
598
 
598
- The `ref` prop allows you to have access to the element's reference:
599
+ The `ref` prop allows you to have access to the tag's reference:
599
600
 
600
601
  ```ts
601
602
  import { Component, ElementRef, Stores, input } from "yandel";
@@ -635,7 +636,7 @@ class WithKey extends Component {
635
636
 
636
637
  ## Usage
637
638
 
638
- Define your own templates and components. Then, use `createRoot.render` to render your UI.
639
+ Define your UI: components, templates, nodes... then, use `createRoot.render` to start rendering and done!
639
640
 
640
641
  ```ts
641
642
  import {
package/dist/node.d.ts CHANGED
@@ -93,10 +93,7 @@ declare class TagNode<T extends HTMLTags | HTMLSVGTags = HTMLTags> extends Node
93
93
  key: symbol | string | number | undefined;
94
94
  index_length: number;
95
95
  constructor(tag: T, props?: AnyProps<T>, children?: ValidNodeChild[]);
96
- private _apply_dom_props;
97
96
  private _diff_dom_props;
98
- private _clear_dom_props;
99
- private _clear_dom_events;
100
97
  get dom(): AnyElement<T> | undefined;
101
98
  get props(): AnyProps<T> | undefined;
102
99
  get children(): ValidNodeChild[] | undefined;
@@ -105,7 +102,7 @@ declare class TagNode<T extends HTMLTags | HTMLSVGTags = HTMLTags> extends Node
105
102
  clearChildren(): void;
106
103
  clearDom(): void;
107
104
  delete(): void;
108
- switch(t: TagNode<HTMLTags>): boolean;
105
+ switch(t: TagNode): boolean;
109
106
  }
110
107
  declare class ParentNode<T extends HTMLElement = HTMLElement> extends Node implements SwitchableNode<ParentNode> {
111
108
  static is(t: any): t is ParentNode;
package/dist/node.js CHANGED
@@ -76,7 +76,7 @@ class Stores {
76
76
  this.#_stores = s;
77
77
  }
78
78
  }
79
- class ComponentUpdateQueue {
79
+ class UpdateQueue {
80
80
  static _update_scheduled = false;
81
81
  static _queue = new Set();
82
82
  static processNodeQueue() {
@@ -118,16 +118,14 @@ class ComponentUpdateQueue {
118
118
  if (!this._update_scheduled)
119
119
  this.notifyQueue();
120
120
  }
121
- }
122
- function queueNodesDeletion(ns) {
123
- queueMicrotask(() => {
124
- for (const c of ns) {
125
- if (c instanceof Node)
126
- c.delete();
127
- else
128
- console.warn("Unknown child");
129
- }
130
- });
121
+ static queueNodesDeletion(ns) {
122
+ queueMicrotask(() => {
123
+ for (const c of ns) {
124
+ if (c && c instanceof Node)
125
+ c.delete();
126
+ }
127
+ });
128
+ }
131
129
  }
132
130
  const ComponentInternalKey = Symbol("_$ci$_");
133
131
  function _c_el(t) {
@@ -225,21 +223,10 @@ class ContentNode extends Node {
225
223
  super.delete();
226
224
  }
227
225
  }
228
- function create_template(t) {
229
- try {
230
- return t();
231
- }
232
- catch (error) {
233
- throw new Error(`Template create error: ${error}`);
234
- }
235
- }
236
- function _get_node_from_future(t) {
237
- const n = create_template(t);
238
- if (_is_fn(n))
239
- return _get_node_from_future(n);
240
- return n;
226
+ function _create_html_tag(tag) {
227
+ return SVG_TAGS.has(tag) ? _c_s(tag) : _c_el(tag);
241
228
  }
242
- function _apply_dom_prop(dom, props, prop) {
229
+ function _apply_html_prop(dom, props, prop) {
243
230
  const v = props[prop];
244
231
  if (v == null)
245
232
  return;
@@ -287,7 +274,7 @@ function _apply_dom_prop(dom, props, prop) {
287
274
  else
288
275
  dom.setAttribute(prop, v);
289
276
  }
290
- function _remove_dom_prop(dom, props, prop) {
277
+ function _remove_html_prop(dom, props, prop) {
291
278
  if (/^on[a-z]/.test(prop)) {
292
279
  const ev_name = prop.slice(2);
293
280
  dom.removeEventListener(ev_name, props[prop]);
@@ -295,11 +282,21 @@ function _remove_dom_prop(dom, props, prop) {
295
282
  }
296
283
  if (prop === "ref" && props.ref instanceof Stores) {
297
284
  props.ref.stores = undefined;
298
- return;
299
285
  }
300
286
  const v = props[prop];
301
- if (v == null)
287
+ if (v == null) {
288
+ if (prop === "style")
289
+ return;
290
+ if (prop in dom) {
291
+ try {
292
+ dom[prop] = undefined;
293
+ }
294
+ catch (error) {
295
+ console.error("Remove prop error:", error);
296
+ }
297
+ }
302
298
  return;
299
+ }
303
300
  if (prop === "style" && typeof v === "object") {
304
301
  try {
305
302
  Object.entries(v).forEach(([cssProp]) => {
@@ -323,15 +320,15 @@ function _remove_dom_prop(dom, props, prop) {
323
320
  switch (t) {
324
321
  case "boolean": {
325
322
  dom[prop] = false;
326
- return;
323
+ break;
327
324
  }
328
325
  case "string": {
329
326
  dom[prop] = "";
330
- return;
327
+ break;
331
328
  }
332
329
  case "object": {
333
- dom[prop] = null;
334
- return;
330
+ dom[prop] = undefined;
331
+ break;
335
332
  }
336
333
  }
337
334
  }
@@ -339,6 +336,69 @@ function _remove_dom_prop(dom, props, prop) {
339
336
  }
340
337
  dom.removeAttribute(prop);
341
338
  }
339
+ function _get_props_diff(a, b) {
340
+ const _to_remove = {};
341
+ const _to_apply = {};
342
+ for (const key in a) {
343
+ if (!b.hasOwnProperty(key)) {
344
+ _to_remove[key] = a[key];
345
+ }
346
+ }
347
+ for (const key in b) {
348
+ if (b[key] == null) {
349
+ _to_remove[key] = b[key];
350
+ }
351
+ else {
352
+ if (b[key] === a[key])
353
+ continue;
354
+ _to_apply[key] = b[key];
355
+ }
356
+ }
357
+ return {
358
+ apply: _to_apply,
359
+ remove: _to_remove,
360
+ };
361
+ }
362
+ // function _diff_html_props<T extends HTMLTags | HTMLSVGTags>(
363
+ // dom: AnyElement<T>,
364
+ // a: AnyProps<T>,
365
+ // b: AnyProps<T>
366
+ // ) {
367
+ // for (const prop of Object.keys(a)) {
368
+ // if (prop.startsWith("on")) {
369
+ // _remove_html_prop(dom, a, prop);
370
+ // if (prop in b) _apply_html_prop(dom, b, prop);
371
+ // } else if (!(prop in b)) {
372
+ // _remove_html_prop(dom, a, prop);
373
+ // } else _apply_html_prop(dom, b, prop);
374
+ // }
375
+ // }
376
+ function _clear_events(dom, props) {
377
+ for (const prop of Object.keys(props)) {
378
+ if (prop.startsWith("on")) {
379
+ if (typeof props[prop] !== "function")
380
+ continue;
381
+ const ev_name = prop.slice(2);
382
+ dom.removeEventListener(ev_name, props[prop]);
383
+ delete props[prop];
384
+ }
385
+ }
386
+ }
387
+ function _apply_props(dom, props) {
388
+ for (const prop of Object.keys(props)) {
389
+ _apply_html_prop(dom, props, prop);
390
+ }
391
+ }
392
+ function _clear_props(dom, props) {
393
+ for (const prop of Object.keys(props)) {
394
+ _remove_html_prop(dom, props, prop);
395
+ }
396
+ }
397
+ function _remove_style(dom, styles) {
398
+ for (const style of Object.keys(styles)) {
399
+ dom.style[style] = "";
400
+ }
401
+ }
342
402
  class TagNode extends Node {
343
403
  static is(t) {
344
404
  return t instanceof TagNode;
@@ -359,51 +419,26 @@ class TagNode extends Node {
359
419
  this._children = children;
360
420
  this.needs_update = true;
361
421
  }
362
- _apply_dom_props() {
363
- if (!this._props || !this._dom)
364
- return;
365
- for (const prop in this._props) {
366
- _apply_dom_prop(this._dom, this._props, prop);
367
- }
368
- }
369
422
  _diff_dom_props(nprops) {
370
- if (!this._props) {
371
- this._props = nprops;
372
- this._apply_dom_props();
373
- return;
374
- }
375
- if (this._dom)
376
- for (const prop in this._props) {
377
- if (prop.startsWith("on")) {
378
- _remove_dom_prop(this._dom, this._props, prop);
379
- if (prop in nprops)
380
- _apply_dom_prop(this._dom, nprops, prop);
381
- }
382
- else if (!(prop in nprops)) {
383
- _remove_dom_prop(this._dom, this._props, prop);
423
+ if (this._dom) {
424
+ if (this._props) {
425
+ if (this._props.style) {
426
+ _remove_style(this._dom, this._props.style);
427
+ delete this._props.style;
384
428
  }
385
- else
386
- _apply_dom_prop(this._dom, nprops, prop);
387
- }
388
- this._props = nprops;
389
- }
390
- _clear_dom_props() {
391
- if (this._props && this._dom)
392
- for (const prop in this._props) {
393
- _remove_dom_prop(this._dom, this._props, prop);
429
+ _clear_events(this._dom, this._props);
430
+ const diff = _get_props_diff(this._props, nprops);
431
+ _clear_props(this._dom, diff.remove);
432
+ _apply_props(this._dom, diff.apply);
433
+ this._props = nprops;
394
434
  }
395
- }
396
- _clear_dom_events() {
397
- if (this._props && this._dom)
398
- for (const prop in this._props) {
399
- const v = Reflect.get(this._props, prop);
400
- if (!v)
401
- continue;
402
- if (prop.startsWith("on") && typeof v === "function") {
403
- const ev_name = prop.slice(2);
404
- this._dom.removeEventListener(ev_name, Reflect.get(this._props, prop));
405
- }
435
+ else {
436
+ this._props = nprops;
437
+ _apply_props(this._dom, this._props);
438
+ return;
406
439
  }
440
+ }
441
+ this._props = nprops;
407
442
  }
408
443
  get dom() {
409
444
  return this._dom;
@@ -423,12 +458,16 @@ class TagNode extends Node {
423
458
  if (this._deleted)
424
459
  throw new Error("Attempt to create a deleted node");
425
460
  if (this._dom) {
426
- this._apply_dom_props();
427
- this.needs_update = true;
428
- return true;
461
+ if (this._props) {
462
+ _apply_props(this._dom, this._props);
463
+ this.needs_update = true;
464
+ return true;
465
+ }
466
+ return false;
429
467
  }
430
- this._dom = (SVG_TAGS.has(this.tag) ? _c_s(this.tag) : _c_el(this.tag));
431
- this._apply_dom_props();
468
+ this._dom = _create_html_tag(this.tag);
469
+ if (this._props)
470
+ _apply_props(this._dom, this._props);
432
471
  this.needs_update = false;
433
472
  return true;
434
473
  }
@@ -436,7 +475,7 @@ class TagNode extends Node {
436
475
  if (this._deleted)
437
476
  return;
438
477
  if (this._children?.length) {
439
- queueNodesDeletion(this._children);
478
+ UpdateQueue.queueNodesDeletion(this._children);
440
479
  delete this._children;
441
480
  }
442
481
  }
@@ -446,7 +485,8 @@ class TagNode extends Node {
446
485
  return;
447
486
  }
448
487
  this.needs_update = true;
449
- this._clear_dom_events();
488
+ if (this._props)
489
+ _clear_events(this._dom, this._props);
450
490
  this._dom.remove();
451
491
  delete this._dom;
452
492
  }
@@ -464,10 +504,20 @@ class TagNode extends Node {
464
504
  this.key = t.props?.key ?? t.key;
465
505
  if (this.tag === t.tag) {
466
506
  // Tag is the same
467
- if (t.props)
468
- this._diff_dom_props(t.props);
469
- else if (this._props)
470
- this._clear_dom_props();
507
+ if (this._dom) {
508
+ if (this._props) {
509
+ if (t.props)
510
+ this._diff_dom_props(t.props);
511
+ else
512
+ _clear_props(this._dom, this._props);
513
+ }
514
+ else if (t.props) {
515
+ this._props = t.props;
516
+ _apply_props(this._dom, this._props);
517
+ }
518
+ }
519
+ else if (t.props)
520
+ this._props = t.props;
471
521
  }
472
522
  else {
473
523
  // Tag has changed
@@ -515,7 +565,7 @@ class ParentNode extends Node {
515
565
  clearChildren() {
516
566
  this.child_length = 0;
517
567
  if (this._children?.length) {
518
- queueNodesDeletion(this._children);
568
+ UpdateQueue.queueNodesDeletion(this._children);
519
569
  delete this._children;
520
570
  }
521
571
  }
@@ -542,6 +592,20 @@ class ParentNode extends Node {
542
592
  return this.needs_update;
543
593
  }
544
594
  }
595
+ function _create_template(t) {
596
+ try {
597
+ return t();
598
+ }
599
+ catch (error) {
600
+ throw new Error(`Template create error: ${error}`);
601
+ }
602
+ }
603
+ function _get_node_from_future(t) {
604
+ const n = _create_template(t);
605
+ if (_is_fn(n))
606
+ return _get_node_from_future(n);
607
+ return n;
608
+ }
545
609
  class ComponentNode extends Node {
546
610
  static is(t) {
547
611
  return t instanceof ComponentNode;
@@ -563,7 +627,7 @@ class ComponentNode extends Node {
563
627
  throw new Error("Template has been deleted");
564
628
  nextInternalTick(this._template);
565
629
  try {
566
- const child = create_template(this._template.render.bind(this._template));
630
+ const child = _create_template(this._template.render.bind(this._template));
567
631
  if (child == null) {
568
632
  if (this.hasChildren) {
569
633
  this.clearChildren();
@@ -603,7 +667,7 @@ class ComponentNode extends Node {
603
667
  return;
604
668
  this.child_length = 0;
605
669
  if (this._children?.length) {
606
- queueNodesDeletion(this._children);
670
+ UpdateQueue.queueNodesDeletion(this._children);
607
671
  delete this._children;
608
672
  }
609
673
  }
@@ -619,10 +683,8 @@ class ComponentNode extends Node {
619
683
  if (t.hasChildren) {
620
684
  this.needs_update = diffChildren(this._children, t.children);
621
685
  }
622
- else {
623
- this.needs_update = true;
624
- this.clearChildren();
625
- }
686
+ this.needs_update = true;
687
+ this.clearChildren();
626
688
  }
627
689
  else if (t.hasChildren)
628
690
  this._children = t.children;
@@ -686,7 +748,7 @@ class ComponentInternals {
686
748
  : s;
687
749
  Promise.resolve().then(() => {
688
750
  if (this.#_n)
689
- ComponentUpdateQueue.queueNodeUpdate(this.#_n);
751
+ UpdateQueue.queueNodeUpdate(this.#_n);
690
752
  });
691
753
  }
692
754
  addEffect(e) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "yandel",
3
3
  "description": "A reactive lightweight TS frontend framework",
4
- "version": "1.1.2",
4
+ "version": "1.2.0",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "files": [