lilact 0.17.0 → 0.19.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.
Files changed (66) hide show
  1. package/dist/lilact.development.js +156 -118
  2. package/dist/lilact.development.js.map +3 -3
  3. package/dist/lilact.development.min.js +20 -20
  4. package/dist/lilact.development.min.js.map +3 -3
  5. package/dist/lilact.production.min.js +18 -18
  6. package/docs/assets/hierarchy.js +1 -1
  7. package/docs/assets/navigation.js +1 -1
  8. package/docs/assets/search.js +1 -1
  9. package/docs/classes/accessories.ErrorBoundary.html +8 -8
  10. package/docs/classes/accessories.Suspense.html +7 -7
  11. package/docs/classes/components.Component.html +11 -11
  12. package/docs/classes/components.HTMLComponent.html +11 -11
  13. package/docs/classes/components.RootComponent.html +11 -11
  14. package/docs/functions/components.createComponent.html +1 -1
  15. package/docs/functions/components.createPortal.html +11 -0
  16. package/docs/functions/components.createRoot.html +1 -1
  17. package/docs/functions/components.memo.html +7 -0
  18. package/docs/functions/components.render.html +1 -1
  19. package/docs/functions/hooks.startTransition.html +1 -1
  20. package/docs/functions/hooks.useActionState.html +1 -1
  21. package/docs/functions/hooks.useDebugValue.html +3 -3
  22. package/docs/functions/hooks.useDeferredValue.html +1 -1
  23. package/docs/functions/hooks.useEffect.html +4 -3
  24. package/docs/functions/hooks.useImperativeHandle.html +1 -1
  25. package/docs/functions/hooks.useInsertionEffect.html +4 -3
  26. package/docs/functions/hooks.useLayoutEffect.html +4 -3
  27. package/docs/functions/hooks.useMemo.html +4 -3
  28. package/docs/functions/hooks.useReducer.html +1 -1
  29. package/docs/functions/misc.Fragment.html +1 -1
  30. package/docs/functions/misc.Portal.html +4 -0
  31. package/docs/functions/misc.deepEqual.html +1 -1
  32. package/docs/functions/misc.forwardRef.html +1 -1
  33. package/docs/functions/misc.getComponentByPointer.html +1 -1
  34. package/docs/functions/misc.isAsync.html +1 -1
  35. package/docs/functions/misc.isClass.html +1 -1
  36. package/docs/functions/misc.isEmpty.html +1 -1
  37. package/docs/functions/misc.isError.html +1 -1
  38. package/docs/functions/misc.isThenable.html +1 -1
  39. package/docs/functions/misc.shallowEqual.html +3 -2
  40. package/docs/functions/misc.toBool.html +1 -1
  41. package/docs/functions/timers.timeoutPromise.html +5 -6
  42. package/docs/hierarchy.html +1 -1
  43. package/docs/modules/components.html +1 -1
  44. package/docs/modules/misc.html +1 -1
  45. package/docs/static/demos/memo.jsx +26 -0
  46. package/docs/static/demos/portal.jsx +56 -0
  47. package/docs/static/index.html +8 -3
  48. package/docs/static/lilact.development.js +156 -118
  49. package/docs/static/lilact.development.js.map +3 -3
  50. package/docs/static/lilact.development.min.js +20 -20
  51. package/docs/static/lilact.development.min.js.map +3 -3
  52. package/docs/static/lilact.production.min.js +18 -18
  53. package/docs/variables/misc.Children.html +2 -2
  54. package/examples/demos/memo.jsx +26 -0
  55. package/examples/demos/portal.jsx +56 -0
  56. package/examples/index.html +8 -3
  57. package/examples/lilact.development.js +156 -118
  58. package/examples/lilact.development.js.map +3 -3
  59. package/examples/lilact.development.min.js +20 -20
  60. package/examples/lilact.development.min.js.map +3 -3
  61. package/examples/lilact.production.min.js +18 -18
  62. package/package.json +1 -1
  63. package/src/components.jsx +101 -33
  64. package/src/hooks.jsx +10 -6
  65. package/src/lilact.jsx +1 -1
  66. package/src/misc.jsx +22 -4
@@ -97,7 +97,7 @@ class ComponentCache
97
97
  if(ex.cleanup) {
98
98
  ex.cleanup();
99
99
  }
100
- else if(ex.element) {
100
+ else if(ex.element && !ex.portal) {
101
101
  ex.element.parentElement.removeChild(ex.element);
102
102
  }
103
103
  });
@@ -162,19 +162,23 @@ class ComponentCore
162
162
  }
163
163
 
164
164
  /*
165
- //let do_rerender = true;
166
-
167
- // if(this.outlet && this.entity[MEMOIZED]) {
168
- // if(shallowEqual(this.props, next_props)) do_rerender=false;
169
- // delete this.entity[MEMOIZED];
170
- // }
171
-
172
- if(do_rerender) {
173
165
  */
174
166
  // TODO: should componentDidUpdate be called after arranging/appending the outlet or before?
175
167
  apply(next_props = this.props, next_state = this.next_state || this.state)
176
168
  {
177
169
 
170
+ let do_rerender = true;
171
+
172
+ if(this.outlet && this?.[MEMOIZED]) {
173
+
174
+ if( shallowEqual(this.props, next_props, "children") &&
175
+ shallowEqual(this.props?.children, next_props?.children) ) {
176
+ do_rerender=false;
177
+ }
178
+ }
179
+
180
+ if(do_rerender) {
181
+
178
182
  if(DEBUG) {
179
183
 
180
184
  if(this.entity?.propTypes) {
@@ -205,7 +209,6 @@ if(DEBUG) {
205
209
  this.element[COMPONENT] = this.component;
206
210
  }
207
211
 
208
-
209
212
  if(next_props.ref) {
210
213
  if(typeof(next_props.ref)==='function') {
211
214
  next_props.ref(this.element || this.component);
@@ -262,6 +265,12 @@ if(DEBUG) {
262
265
  }
263
266
  }
264
267
 
268
+
269
+ if( this?.portal ) {
270
+ this.element = this.portal;
271
+ }
272
+
273
+
265
274
  if(this.outlet?.constructor?.name!=='Array') {
266
275
  this.outlet = [this.outlet];
267
276
  }
@@ -308,13 +317,14 @@ if(DEBUG) {
308
317
  if(this.cache) this.cache.commit();
309
318
 
310
319
 
311
- if(this.element) this.arrangeOutlet();
320
+ if(this.element) this.arrangeOutlet();
312
321
 
313
- if(this.component.componentDidUpdate) {
314
- this.component.componentDidUpdate(prev_props, prev_state, this.last_snapshot);
315
- }
322
+ if(this.component.componentDidUpdate) {
323
+ this.component.componentDidUpdate(prev_props, prev_state, this.last_snapshot);
324
+ }
316
325
 
317
- if(this.last_snapshot) delete this.last_snapshot;
326
+ if(this.last_snapshot) delete this.last_snapshot;
327
+ }
318
328
 
319
329
  }
320
330
 
@@ -336,7 +346,7 @@ if(DEBUG) {
336
346
  this.component.componentWillUnmount();
337
347
  }
338
348
 
339
- if(this?.element?.parentElement) {
349
+ if(this?.element?.parentElement && !this.portal) {
340
350
  this.element.parentElement.removeChild( this.element );
341
351
  }
342
352
 
@@ -533,6 +543,8 @@ if(DEBUG) {
533
543
 
534
544
  appendElement(core)
535
545
  {
546
+ if(core.portal) return;
547
+
536
548
  this.scanZombies(core.container, core.element);
537
549
 
538
550
  if(core?.element.parentNode===null) {
@@ -635,17 +647,30 @@ function constructFunc(core, parent) // returns {text} or component, and not com
635
647
  // do nothing...
636
648
  }
637
649
  else {
638
- if(typeof(core.entity)==='string') {
639
- comp = new HTMLComponent(core.entity, core.props);
650
+ let entity = core.entity;
651
+ let memoized = false;
652
+
653
+ if(typeof(entity)==='object') {
654
+ if( entity[MEMOIZED] ) {
655
+ memoized = entity[MEMOIZED];
656
+ entity = entity.component;
657
+ }
658
+ else {
659
+ throw new Error("Invalid component.");
660
+ }
661
+ }
662
+
663
+ if(typeof(entity)==='string') {
664
+ comp = new HTMLComponent(entity, core.props);
640
665
  }
641
666
  else {
642
667
 
643
- if( isClass(core.entity) ) {
644
- if(core.entity?.defaultProps) {
645
- core.props = { ...core.entity.defaultProps, ...core.props };
668
+ if( isClass(entity) ) {
669
+ if(entity?.defaultProps) {
670
+ core.props = { ...entity.defaultProps, ...core.props };
646
671
  }
647
672
 
648
- comp = new core.entity(core.props);
673
+ comp = new entity(core.props);
649
674
 
650
675
  const desc = Object.getOwnPropertyDescriptor(comp, "state");
651
676
  if(desc) {
@@ -667,35 +692,35 @@ function constructFunc(core, parent) // returns {text} or component, and not com
667
692
  }
668
693
  }
669
694
  }
670
- else if(typeof(core.entity)==='function') {
695
+ else if(typeof(entity)==='function') {
671
696
 
672
- if(core.entity?.defaultProps) {
673
- core.props = { ...core.entity.defaultProps, ...core.props };
697
+ if(entity?.defaultProps) {
698
+ core.props = { ...entity.defaultProps, ...core.props };
674
699
  }
675
700
 
676
701
  comp = new Component(core.props);
677
702
 
678
703
  // the binding is not necessary and is not according to the specs,
679
704
  // probably not even recommended! but helpful.
680
- comp.render = core.entity.bind(comp);
705
+ comp.render = entity.bind(comp);
681
706
  comp[CORE].hooks = [];
682
707
  comp[CORE].hook_index = 0;
683
708
  }
684
709
  else {
685
- throw new Error("Invalid entity for createComponent.");
710
+ throw new Error("Error in constructing component.");
686
711
  }
687
712
 
688
- comp[CORE].entity = core.entity;
713
+ comp[CORE].entity = entity;
689
714
 
690
715
  if(core.container) {
691
716
  comp[CORE].container = core.container;
692
717
  }
693
718
  }
694
719
 
720
+ if(memoized) comp[CORE][MEMOIZED] = true;
695
721
  }
696
722
 
697
723
  if(parent instanceof ComponentCore) comp[CORE].parent = parent;
698
-
699
724
  return comp;
700
725
  }
701
726
 
@@ -1089,8 +1114,12 @@ export class RootComponent extends HTMLComponent
1089
1114
 
1090
1115
  export function createComponent(entity, props={}, ...children)
1091
1116
  {
1092
- if(typeof(entity)!=='string' && typeof(entity)!=='function') {
1093
- throw new Error("Invalid entity for createComponent.");
1117
+
1118
+ if (typeof(entity)!=='string' && typeof(entity)!=='function' )
1119
+ {
1120
+ if(typeof(entity)!=='object' || !entity[MEMOIZED]) {
1121
+ throw new Error("Invalid entity for createComponent.");
1122
+ }
1094
1123
  }
1095
1124
 
1096
1125
  for(let i=0; i<children.length; i++) {
@@ -1114,8 +1143,6 @@ export function createComponent(entity, props={}, ...children)
1114
1143
  }
1115
1144
  }
1116
1145
 
1117
- //if(entity===null) return children; // <> style fragment
1118
-
1119
1146
  props.key = generateComponentKey(entity, props);
1120
1147
  props.children = children;
1121
1148
 
@@ -1162,6 +1189,27 @@ export function createRoot(element)
1162
1189
  }
1163
1190
  }
1164
1191
 
1192
+ /**
1193
+ * Creates a portal — a way to render children into a DOM node
1194
+ * that exists outside the current component hierarchy.
1195
+ *
1196
+ * Note: In React, events from portals propagate according to the React tree rather
1197
+ * than the DOM tree. For example, if you click inside a portal, and the portal is
1198
+ * wrapped in `<div onClick>`, that onClick handler will fire. Lilact doesn't act
1199
+ * this way. In Lilact, event propagation follows DOM tree, while the ownership
1200
+ * follow the JSX structure.
1201
+ *
1202
+ * @param {Component} children - The component(s) to render into the portal.
1203
+ * @param {Element} element - The DOM node that will receive the portal content.
1204
+ * @returns {Component} A portal object that can be rendered.
1205
+ */
1206
+
1207
+ export function createPortal(children, element)
1208
+ {
1209
+ return <Lilact.Portal view={element}>{children}</Lilact.Portal>;
1210
+ }
1211
+
1212
+
1165
1213
  /**
1166
1214
  * Renders a component into a target DOM element.
1167
1215
  * If the component maintains internal state, this typically mounts it (or updates the existing tree) under `element`.
@@ -1181,6 +1229,26 @@ export function render(component, element)
1181
1229
  return createRoot(element).render(component);
1182
1230
  }
1183
1231
 
1232
+ /**
1233
+ * Memoizes the given component. The memoized component will only rerender if its props or children are modified.
1234
+ * Unlike React, Lilact memo also works on class components.
1235
+ *
1236
+ * @param {Object} component - Component instance to render.
1237
+ *
1238
+ * @returns {component} - Memoized component.
1239
+ */
1240
+
1241
+ export function memo(component)
1242
+ {
1243
+ if(typeof(component)==='object') {
1244
+ component[MEMOIZED] = true;
1245
+ }
1246
+ else {
1247
+ component = { component, [MEMOIZED]: true };
1248
+ }
1249
+ return component;
1250
+ }
1251
+
1184
1252
 
1185
1253
  /** @ignore */
1186
1254
  export const createElement = createComponent;
package/src/hooks.jsx CHANGED
@@ -269,7 +269,8 @@ export function useRef(initialValue = null)
269
269
  * Runs an effect synchronously after all DOM mutations but before the browser paints.
270
270
  *
271
271
  * @param {Function} effect - Effect callback.
272
- * @param deps - Optional dependency list (or an object for shallow comparison) used to determine when to re-run the effect.
272
+ * @param {Array<any>} [deps] Optional dependency list (or an object for shallow comparison)
273
+ * used to determine when to re-run the effect.
273
274
  * @returns {void}
274
275
  */
275
276
  export function useLayoutEffect(effect, deps=undefined)
@@ -299,7 +300,8 @@ export function useLayoutEffect(effect, deps=undefined)
299
300
  * Runs a side effect after render commits.
300
301
  *
301
302
  * @param effect - Callback invoked.
302
- * @param deps - Optional dependency list (or an object for shallow comparison) used to determine when to re-run the effect.
303
+ * @param {Array<any>} [deps] Optional dependency list (or an object for shallow comparison)
304
+ * used to determine when to re-run the effect.
303
305
  * @returns {void}
304
306
  */
305
307
  export function useEffect(effect, deps=undefined)
@@ -330,7 +332,8 @@ export function useEffect(effect, deps=undefined)
330
332
  * Executes a side effect after the DOM updates have been committed.
331
333
  *
332
334
  * @param effect - Callback invoked after render commit.
333
- * @param deps - Optional dependency list (or an object for shallow comparison) used to determine when to re-run the effect.
335
+ * @param {Array<any>} [deps] Optional dependency list (or an object for shallow comparison)
336
+ * used to determine when to re-run the effect.
334
337
  * @returns void
335
338
  */
336
339
  export function useInsertionEffect(effect, deps=undefined)
@@ -361,7 +364,8 @@ export function useInsertionEffect(effect, deps=undefined)
361
364
  * Memoizes a computed value until dependencies change.
362
365
  *
363
366
  * @param {Function} factory - Function that creates the value.
364
- * @param deps - Optional dependency list (or an object for shallow comparison) used to determine when to re-run the effect.
367
+ * @param {Array<any>} [deps] Optional dependency list (or an object for shallow comparison)
368
+ * used to determine when to recompute the value.
365
369
  * @returns {any} Memoized value.
366
370
  */
367
371
  export function useMemo(factory,deps=undefined)
@@ -525,8 +529,8 @@ export function useImperativeHandle(ref, factory, deps=undefined)
525
529
 
526
530
 
527
531
  /**
528
- * A hook for debug purposes. At the moment it only logs the output, but it can be overrided
529
- * for each development environment.
532
+ * A hook for debug purposes. By default it only logs the output to console, but it can be overrided
533
+ * for the development environment.
530
534
  *
531
535
  * @param {any} val
532
536
  * The debug value.
package/src/lilact.jsx CHANGED
@@ -92,7 +92,7 @@ export {transpileJSX, transpilerConfig} from "./jsx";
92
92
  export const Lilact =
93
93
  {
94
94
 
95
- VERSION: "beta.17",
95
+ VERSION: "beta.19",
96
96
 
97
97
  // Configuration
98
98
 
package/src/misc.jsx CHANGED
@@ -95,9 +95,26 @@ export const findDOMNode = (component)=>{
95
95
  *
96
96
  * @param children - The nodes to group without adding an extra DOM element.
97
97
  */
98
- export const Fragment = function ({children}) { return children };
98
+ export function Fragment({children})
99
+ {
100
+ return children;
101
+ };
99
102
  Fragment.displayName = "Fragment";
100
103
 
104
+ /**
105
+ * Portal helper/utility.
106
+ *
107
+ * @param view - The DOM node that will receive the portal content.
108
+ * @param children - The nodes to put in the portal element.
109
+ */
110
+ export function Portal({children, view})
111
+ {
112
+ this[CORE].portal = view;
113
+
114
+ return children;
115
+ };
116
+ Portal.displayName = "Portal";
117
+
101
118
  /**
102
119
  * Children namespace for utilities that operate on `props.children`. `Children` is deprecated
103
120
  * and not recommended by React documentation itself. But `only` and `toArray` are used
@@ -220,9 +237,10 @@ export function isEmpty(value) {
220
237
  *
221
238
  * @param source - First object to compare.
222
239
  * @param target - Second object to compare.
240
+ * @param ignore - specific property/index to be ignored in comparison.
223
241
  * @returns True if shallowly equal; otherwise false.
224
242
  */
225
- export const shallowEqual = (source, target) => {
243
+ export const shallowEqual = (source, target, ignore) => {
226
244
  if (typeOf(source) !== typeOf(target)) {
227
245
  return false;
228
246
  }
@@ -231,9 +249,9 @@ export const shallowEqual = (source, target) => {
231
249
  if (source.length !== target.length) {
232
250
  return false;
233
251
  }
234
- return source.every((el, index) => el === target[index]);
252
+ return source.every((el, index) => el === target[index] || index===ignore );
235
253
  } else if (typeOf(source) === "object") {
236
- return Object.keys(source).every((key) => source[key] === target[key]);
254
+ return Object.keys(source).every((key) => source[key] === target[key] || key===ignore );
237
255
  } else if (typeOf(source) === "date") {
238
256
  return source.getTime() === target.getTime();
239
257
  }