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.
- package/README.md +1 -1
- package/dist/lilact.development.js +162 -37
- package/dist/lilact.development.js.map +3 -3
- package/dist/lilact.development.min.js +19 -19
- package/dist/lilact.development.min.js.map +3 -3
- package/dist/lilact.production.min.js +21 -21
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/accessories.ErrorBoundary.html +8 -8
- package/docs/classes/accessories.Suspense.html +7 -7
- package/docs/classes/components.Component.html +11 -11
- package/docs/classes/components.HTMLComponent.html +11 -11
- package/docs/classes/components.RootComponent.html +11 -11
- package/docs/functions/components.createComponent.html +1 -1
- package/docs/functions/components.createPortal.html +11 -0
- package/docs/functions/components.createRoot.html +1 -1
- package/docs/functions/components.memo.html +1 -1
- package/docs/functions/components.render.html +1 -1
- package/docs/functions/misc.Fragment.html +1 -1
- package/docs/functions/misc.Portal.html +4 -0
- package/docs/functions/misc.deepEqual.html +1 -1
- package/docs/functions/misc.forwardRef.html +1 -1
- package/docs/functions/misc.getComponentByPointer.html +1 -1
- package/docs/functions/misc.isAsync.html +1 -1
- package/docs/functions/misc.isClass.html +1 -1
- package/docs/functions/misc.isEmpty.html +1 -1
- package/docs/functions/misc.isError.html +1 -1
- package/docs/functions/misc.isThenable.html +1 -1
- package/docs/functions/misc.shallowEqual.html +1 -1
- package/docs/functions/misc.toBool.html +1 -1
- package/docs/functions/timers.timeoutPromise.html +20 -12
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +1 -1
- package/docs/modules/components.html +1 -1
- package/docs/modules/misc.html +1 -1
- package/docs/static/demos/portal.jsx +56 -0
- package/docs/static/index.html +6 -2
- package/docs/static/lilact.development.js +162 -37
- package/docs/static/lilact.development.js.map +3 -3
- package/docs/static/lilact.development.min.js +19 -19
- package/docs/static/lilact.development.min.js.map +3 -3
- package/docs/static/lilact.production.min.js +21 -21
- package/docs/variables/misc.Children.html +19 -10
- package/examples/demos/portal.jsx +56 -0
- package/examples/index.html +6 -2
- package/examples/lilact.development.js +162 -37
- package/examples/lilact.development.js.map +3 -3
- package/examples/lilact.development.min.js +19 -19
- package/examples/lilact.development.min.js.map +3 -3
- package/examples/lilact.production.min.js +21 -21
- package/package.json +1 -1
- package/src/components.jsx +46 -9
- package/src/lilact.jsx +1 -1
- package/src/misc.jsx +178 -43
package/src/components.jsx
CHANGED
|
@@ -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'
|
|
647
|
-
|
|
648
|
-
|
|
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
|
-
|
|
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
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
|
|
98
|
+
export function Fragment({children})
|
|
99
|
+
{
|
|
100
|
+
return children;
|
|
101
|
+
};
|
|
99
102
|
Fragment.displayName = "Fragment";
|
|
100
103
|
|
|
101
104
|
/**
|
|
102
|
-
*
|
|
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
|
|
115
|
-
* @
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
*
|
|
119
|
+
* Children namespace for utilities that operate on `props.children`.
|
|
140
120
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
121
|
+
* - Flattens nested arrays recursively.
|
|
122
|
+
* - Omits `null` and `undefined` items (common React-like behavior).
|
|
143
123
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|