lilact 0.18.0 → 0.20.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 (55) hide show
  1. package/README.md +1 -1
  2. package/dist/lilact.development.js +162 -37
  3. package/dist/lilact.development.js.map +3 -3
  4. package/dist/lilact.development.min.js +19 -19
  5. package/dist/lilact.development.min.js.map +3 -3
  6. package/dist/lilact.production.min.js +21 -21
  7. package/docs/assets/hierarchy.js +1 -1
  8. package/docs/assets/navigation.js +1 -1
  9. package/docs/assets/search.js +1 -1
  10. package/docs/classes/accessories.ErrorBoundary.html +8 -8
  11. package/docs/classes/accessories.Suspense.html +7 -7
  12. package/docs/classes/components.Component.html +11 -11
  13. package/docs/classes/components.HTMLComponent.html +11 -11
  14. package/docs/classes/components.RootComponent.html +11 -11
  15. package/docs/functions/components.createComponent.html +1 -1
  16. package/docs/functions/components.createPortal.html +11 -0
  17. package/docs/functions/components.createRoot.html +1 -1
  18. package/docs/functions/components.memo.html +1 -1
  19. package/docs/functions/components.render.html +1 -1
  20. package/docs/functions/misc.Fragment.html +1 -1
  21. package/docs/functions/misc.Portal.html +4 -0
  22. package/docs/functions/misc.deepEqual.html +1 -1
  23. package/docs/functions/misc.forwardRef.html +1 -1
  24. package/docs/functions/misc.getComponentByPointer.html +1 -1
  25. package/docs/functions/misc.isAsync.html +1 -1
  26. package/docs/functions/misc.isClass.html +1 -1
  27. package/docs/functions/misc.isEmpty.html +1 -1
  28. package/docs/functions/misc.isError.html +1 -1
  29. package/docs/functions/misc.isThenable.html +1 -1
  30. package/docs/functions/misc.shallowEqual.html +1 -1
  31. package/docs/functions/misc.toBool.html +1 -1
  32. package/docs/functions/timers.timeoutPromise.html +20 -12
  33. package/docs/hierarchy.html +1 -1
  34. package/docs/index.html +1 -1
  35. package/docs/modules/components.html +1 -1
  36. package/docs/modules/misc.html +1 -1
  37. package/docs/static/demos/portal.jsx +56 -0
  38. package/docs/static/index.html +6 -2
  39. package/docs/static/lilact.development.js +162 -37
  40. package/docs/static/lilact.development.js.map +3 -3
  41. package/docs/static/lilact.development.min.js +19 -19
  42. package/docs/static/lilact.development.min.js.map +3 -3
  43. package/docs/static/lilact.production.min.js +21 -21
  44. package/docs/variables/misc.Children.html +19 -10
  45. package/examples/demos/portal.jsx +56 -0
  46. package/examples/index.html +6 -2
  47. package/examples/lilact.development.js +162 -37
  48. package/examples/lilact.development.js.map +3 -3
  49. package/examples/lilact.development.min.js +19 -19
  50. package/examples/lilact.development.min.js.map +3 -3
  51. package/examples/lilact.production.min.js +21 -21
  52. package/package.json +1 -1
  53. package/src/components.jsx +46 -9
  54. package/src/lilact.jsx +1 -1
  55. package/src/misc.jsx +178 -43
@@ -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
  });
@@ -209,7 +209,6 @@ if(DEBUG) {
209
209
  this.element[COMPONENT] = this.component;
210
210
  }
211
211
 
212
-
213
212
  if(next_props.ref) {
214
213
  if(typeof(next_props.ref)==='function') {
215
214
  next_props.ref(this.element || this.component);
@@ -266,6 +265,12 @@ if(DEBUG) {
266
265
  }
267
266
  }
268
267
 
268
+
269
+ if( this?.portal ) {
270
+ this.element = this.portal;
271
+ }
272
+
273
+
269
274
  if(this.outlet?.constructor?.name!=='Array') {
270
275
  this.outlet = [this.outlet];
271
276
  }
@@ -341,7 +346,7 @@ if(DEBUG) {
341
346
  this.component.componentWillUnmount();
342
347
  }
343
348
 
344
- if(this?.element?.parentElement) {
349
+ if(this?.element?.parentElement && !this.portal) {
345
350
  this.element.parentElement.removeChild( this.element );
346
351
  }
347
352
 
@@ -538,6 +543,8 @@ if(DEBUG) {
538
543
 
539
544
  appendElement(core)
540
545
  {
546
+ if(core.portal) return;
547
+
541
548
  this.scanZombies(core.container, core.element);
542
549
 
543
550
  if(core?.element.parentNode===null) {
@@ -643,9 +650,14 @@ function constructFunc(core, parent) // returns {text} or component, and not com
643
650
  let entity = core.entity;
644
651
  let memoized = false;
645
652
 
646
- if(typeof(entity)==='object' && entity[MEMOIZED]) {
647
- memoized = true;
648
- entity = entity[MEMOIZED];
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
+ }
649
661
  }
650
662
 
651
663
  if(typeof(entity)==='string') {
@@ -1131,8 +1143,6 @@ export function createComponent(entity, props={}, ...children)
1131
1143
  }
1132
1144
  }
1133
1145
 
1134
- //if(entity===null) return children; // <> style fragment
1135
-
1136
1146
  props.key = generateComponentKey(entity, props);
1137
1147
  props.children = children;
1138
1148
 
@@ -1179,6 +1189,27 @@ export function createRoot(element)
1179
1189
  }
1180
1190
  }
1181
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
+
1182
1213
  /**
1183
1214
  * Renders a component into a target DOM element.
1184
1215
  * If the component maintains internal state, this typically mounts it (or updates the existing tree) under `element`.
@@ -1209,7 +1240,13 @@ export function render(component, element)
1209
1240
 
1210
1241
  export function memo(component)
1211
1242
  {
1212
- return { [MEMOIZED]: component }
1243
+ if(typeof(component)==='object') {
1244
+ component[MEMOIZED] = true;
1245
+ }
1246
+ else {
1247
+ component = { component, [MEMOIZED]: true };
1248
+ }
1249
+ return component;
1213
1250
  }
1214
1251
 
1215
1252
 
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.18",
95
+ VERSION: "beta.20",
96
96
 
97
97
  // Configuration
98
98
 
package/src/misc.jsx CHANGED
@@ -95,59 +95,194 @@ 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
 
101
104
  /**
102
- * Children namespace for utilities that operate on `props.children`. `Children` is deprecated
103
- * and not recommended by React documentation itself. But `only` and `toArray` are used
104
- * extensively everywhere, so I included them.
105
- *
106
- * @property only - Filters to the single child (or returns null/throws based on count).
107
- * @property toArray - Converts children to a flat array.
108
- */
109
- export const Children = {
110
-
111
- /**
112
- * Returns the only child from a children collection.
105
+ * Portal helper/utility.
113
106
  *
114
- * @param children - The children to read.
115
- * @returns The single child (or null/exception based on the number of children).
107
+ * @param view - The DOM node that will receive the portal content.
108
+ * @param children - The nodes to put in the portal element.
116
109
  */
117
- only(children) {
118
- children = [...children];
119
- let i=0;
120
- while(i<children.length) {
121
- if(children[i]?.constructor?.name==='Array') {
122
- children.splice(i, 1, ...children[i]);
123
- i--;
124
- }
125
- else if(children[i]===null || children[i]===undefined) {
126
- children.splice(i, 1);
127
- i--;
128
- }
129
- if(i>1) {
130
- throw new Error("No child or child is not the only one");
131
- }
132
- i++;
133
- }
134
- if(children.length===1) return children[0];
110
+ export function Portal({children, view})
111
+ {
112
+ this[CORE].portal = view;
135
113
 
136
- },
114
+ return children;
115
+ };
116
+ Portal.displayName = "Portal";
137
117
 
138
118
  /**
139
- * Converts component children into a flat array.
119
+ * Children namespace for utilities that operate on `props.children`.
140
120
  *
141
- * @param children - The children to convert.
142
- * @returns An array representation of the children.
121
+ * - Flattens nested arrays recursively.
122
+ * - Omits `null` and `undefined` items (common React-like behavior).
143
123
  */
144
- toArray(children) {
145
- if(children) {
146
- if(children?.constructor?.name==='Array') return [...children];
147
- return [children];
148
- }
149
- return [];
150
- }
124
+ export const Children = {
125
+ /**
126
+ * @param {any} x
127
+ * @returns {boolean}
128
+ */
129
+ _isNil(x) {
130
+ return x === null || x === undefined;
131
+ },
132
+
133
+ /**
134
+ * Recursively flattens nested arrays into `out`, omitting null/undefined.
135
+ * @param {Array<any>} out
136
+ * @param {any} input
137
+ */
138
+ _flattenInto(out, input) {
139
+ if (this._isNil(input)) return;
140
+
141
+ if (Array.isArray(input)) {
142
+ for (const v of input) this._flattenInto(out, v);
143
+ return;
144
+ }
145
+
146
+ out.push(input);
147
+ },
148
+
149
+ /**
150
+ * Converts an iterable children collection into a flat array,
151
+ * omitting `null`/`undefined`.
152
+ *
153
+ * @param {Iterable<any>} children
154
+ * @returns {Array<any>}
155
+ */
156
+ toArray(children) {
157
+ const out = [];
158
+ // Per your rule, children is always iterable (often []), but keep it robust anyway.
159
+ if (!children) return out;
160
+
161
+ for (const item of children) {
162
+ this._flattenInto(out, item);
163
+ }
164
+ return out;
165
+ },
166
+
167
+ /**
168
+ * Returns the number of non-null/undefined children (after flattening).
169
+ * @param {Iterable<any>} children
170
+ * @returns {number}
171
+ */
172
+ count(children) {
173
+ return this.toArray(children).length;
174
+ },
175
+
176
+ /**
177
+ * Returns the single child from a children collection (after flattening & omitting nil),
178
+ * or throws if the remaining count is not exactly 1.
179
+ *
180
+ * @param {Iterable<any>} children
181
+ * @returns {any}
182
+ */
183
+ only(children) {
184
+ const arr = this.toArray(children);
185
+ if (arr.length !== 1) {
186
+ throw new Error(
187
+ arr.length === 0
188
+ ? "Expected exactly one child, but received none."
189
+ : "Expected exactly one child, but received more than one."
190
+ );
191
+ }
192
+ return arr[0];
193
+ },
194
+
195
+ /**
196
+ * Maps over children (after flattening & omitting nil).
197
+ *
198
+ * @param {Iterable<any>} children
199
+ * @param {(child:any, index:number)=>any} fn
200
+ * @returns {Array<any>}
201
+ */
202
+ map(children, fn) {
203
+ const arr = this.toArray(children);
204
+ const out = [];
205
+ for (let i = 0; i < arr.length; i++) out.push(fn(arr[i], i));
206
+ return out;
207
+ },
208
+
209
+ /**
210
+ * Iterates over children (after flattening & omitting nil).
211
+ * @param {Iterable<any>} children
212
+ * @param {(child:any, index:number)=>void} fn
213
+ */
214
+ forEach(children, fn) {
215
+ const arr = this.toArray(children);
216
+ for (let i = 0; i < arr.length; i++) fn(arr[i], i);
217
+ },
218
+
219
+ /**
220
+ * Finds the first child for which predicate returns true.
221
+ *
222
+ * @param {Iterable<any>} children
223
+ * @param {(child:any, index:number)=>boolean} predicate
224
+ * @returns {any|undefined}
225
+ */
226
+ find(children, predicate) {
227
+ const arr = this.toArray(children);
228
+ for (let i = 0; i < arr.length; i++) {
229
+ if (predicate(arr[i], i)) return arr[i];
230
+ }
231
+ return undefined;
232
+ },
233
+
234
+ /**
235
+ * Finds exactly one matching child.
236
+ * Throws if matched count is not exactly 1.
237
+ *
238
+ * @param {Iterable<any>} children
239
+ * @param {(child:any, index:number)=>boolean} predicate
240
+ * @returns {any}
241
+ */
242
+ pickOne(children, predicate) {
243
+ const arr = this.toArray(children);
244
+
245
+ let found;
246
+ let matches = 0;
247
+
248
+ for (let i = 0; i < arr.length; i++) {
249
+ if (predicate(arr[i], i)) {
250
+ matches++;
251
+ found = arr[i];
252
+ if (matches > 1) break;
253
+ }
254
+ }
255
+
256
+ if (matches !== 1) {
257
+ throw new Error(
258
+ matches === 0
259
+ ? "pickOne expected exactly one matching child, but matched none."
260
+ : "pickOne expected exactly one matching child, but matched multiple."
261
+ );
262
+ }
263
+
264
+ return found;
265
+ },
266
+
267
+ /**
268
+ * Returns the first non-nil child (after flattening), or undefined.
269
+ * @param {Iterable<any>} children
270
+ * @returns {any|undefined}
271
+ */
272
+ first(children) {
273
+ const arr = this.toArray(children);
274
+ return arr[0];
275
+ },
276
+
277
+ /**
278
+ * Returns the last non-nil child (after flattening), or undefined.
279
+ * @param {Iterable<any>} children
280
+ * @returns {any|undefined}
281
+ */
282
+ last(children) {
283
+ const arr = this.toArray(children);
284
+ return arr.length ? arr[arr.length - 1] : undefined;
285
+ }
151
286
  };
152
287
 
153
288