lilact 0.10.2 → 0.11.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 +2 -1
- package/dist/lilact.development.js +185 -4
- package/dist/lilact.development.js.map +1 -1
- package/dist/lilact.development.min.js +1 -1
- package/dist/lilact.development.min.js.map +1 -1
- package/dist/lilact.production.min.js +1 -1
- package/dist/lilact.production.min.js.map +1 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/functions/transition.CSSTransition.html +3 -3
- package/docs/functions/transition.SwitchTransition.html +22 -0
- package/docs/functions/transition.TransitionGroup.html +1 -1
- package/docs/index.html +2 -1
- package/docs/modules/transition.html +1 -1
- package/docs/static/demos/switch-transition.jsx +38 -34
- package/docs/static/index.html +6 -5
- package/docs/static/lilact.development.js +185 -4
- package/docs/static/lilact.development.min.js +1 -1
- package/docs/static/lilact.production.min.js +1 -1
- package/package.json +7 -3
- package/root/demos/switch-transition.jsx +38 -34
- package/root/index.html +6 -5
- package/root/lilact.development.js +185 -4
- package/root/lilact.development.min.js +1 -1
- package/root/lilact.production.min.js +1 -1
- package/src/lilact.jsx +1 -1
- package/src/transition.jsx +180 -4
package/src/transition.jsx
CHANGED
|
@@ -37,9 +37,9 @@ const ENTERING = "entering";
|
|
|
37
37
|
const ENTERED = "entered";
|
|
38
38
|
const EXITING = "exiting";
|
|
39
39
|
|
|
40
|
-
import {setTimeout, clearTimeout} from "./timers.jsx"
|
|
41
|
-
import {Children} from "./misc.jsx"
|
|
42
|
-
import {useEffect} from "./hooks.jsx"
|
|
40
|
+
import { setTimeout, clearTimeout} from "./timers.jsx"
|
|
41
|
+
import { Children } from "./misc.jsx"
|
|
42
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "./hooks.jsx"
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Transition component that manages enter/exit lifecycle and calls callbacks based on state changes.
|
|
@@ -200,7 +200,7 @@ export function Transition({
|
|
|
200
200
|
*/
|
|
201
201
|
export function CSSTransition({
|
|
202
202
|
in: inProp,
|
|
203
|
-
timeout = defaultTransitionTimeout,
|
|
203
|
+
timeout = Lilact.defaultTransitionTimeout,
|
|
204
204
|
classNames = "fade",
|
|
205
205
|
mountOnEnter = false,
|
|
206
206
|
unmountOnExit = false,
|
|
@@ -244,6 +244,182 @@ export function CSSTransition({
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Switches between keyed children with `CSSTransition`.
|
|
250
|
+
* Ensures each child key gets its own `CSSTransition` instance to avoid state loss.
|
|
251
|
+
*
|
|
252
|
+
* @param {object} props
|
|
253
|
+
* @param {Array} props.children
|
|
254
|
+
* The set of children to switch between. Each child should have a stable `key`
|
|
255
|
+
* (or your `key` will fall back to its index).
|
|
256
|
+
* @param {*} props.activeKey
|
|
257
|
+
* The key of the currently active child.
|
|
258
|
+
* @param {"out-in" | "in-out"} [props.mode="out-in"]
|
|
259
|
+
* Controls sequencing when `activeKey` changes:
|
|
260
|
+
* - `"out-in"`: outgoing transitions out first, incoming transitions in after exit completes.
|
|
261
|
+
* - `"in-out"`: incoming transitions in first, outgoing transitions out after the incoming finishes entering
|
|
262
|
+
* (relies on `onEntered`).
|
|
263
|
+
* @param {number} [props.timeout=Lilact.defaultTransitionTimeout]
|
|
264
|
+
* Transition timeout passed to each `CSSTransition`.
|
|
265
|
+
* @param {string} [props.classNames="switch"]
|
|
266
|
+
* Base className(s) used by `CSSTransition` to apply enter/exit classes.
|
|
267
|
+
* @param {boolean} [props.mountOnEnter=false]
|
|
268
|
+
* Whether to mount the transitioning child only when it begins entering.
|
|
269
|
+
* @param {boolean} [props.unmountOnExit=false]
|
|
270
|
+
* Whether to unmount the child after it finishes exiting.
|
|
271
|
+
* @param {boolean} [props.appear=false]
|
|
272
|
+
* Whether to run the initial transition on first mount.
|
|
273
|
+
*
|
|
274
|
+
* @param {(isAppearing: boolean) => void} [props.onExited]
|
|
275
|
+
* Called when the outgoing child finishes exiting.
|
|
276
|
+
* @param {(isAppearing: boolean) => void} [props.onEnter]
|
|
277
|
+
* Called when a child begins entering.
|
|
278
|
+
* @param {(isAppearing: boolean) => void} [props.onExiting]
|
|
279
|
+
* Called when a child begins exiting.
|
|
280
|
+
* @param {(isAppearing: boolean) => void} [props.onEntered]
|
|
281
|
+
* Called when a child finishes entering. Used to implement `"in-out"` sequencing.
|
|
282
|
+
*
|
|
283
|
+
* @param {object} [props.csstProps]
|
|
284
|
+
* Any additional props are forwarded to each `CSSTransition` instance.
|
|
285
|
+
*/
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
export function SwitchTransition({
|
|
289
|
+
children,
|
|
290
|
+
activeKey,
|
|
291
|
+
mode = "out-in", // "out-in" | "in-out"
|
|
292
|
+
|
|
293
|
+
timeout = Lilact.defaultTransitionTimeout,
|
|
294
|
+
classNames = "switch",
|
|
295
|
+
|
|
296
|
+
mountOnEnter = false,
|
|
297
|
+
unmountOnExit = false,
|
|
298
|
+
appear = false,
|
|
299
|
+
|
|
300
|
+
// callbacks (optional)
|
|
301
|
+
onExited,
|
|
302
|
+
onEnter,
|
|
303
|
+
onExiting,
|
|
304
|
+
onEntered,
|
|
305
|
+
|
|
306
|
+
...csstProps
|
|
307
|
+
}) {
|
|
308
|
+
const childArray = useMemo(() => Children.toArray(children), [children]);
|
|
309
|
+
|
|
310
|
+
// Outgoing key (the one that should exit) and phase flags
|
|
311
|
+
const [exitingKey, setExitingKey] = useState(null);
|
|
312
|
+
const [exitStarted, setExitStarted] = useState(false);
|
|
313
|
+
|
|
314
|
+
// Controls whether the incoming (activeKey) is currently "in"
|
|
315
|
+
const [enterAllowed, setEnterAllowed] = useState(true);
|
|
316
|
+
|
|
317
|
+
// Refs to avoid stale closures in callbacks
|
|
318
|
+
const prevKeyRef = useRef(activeKey);
|
|
319
|
+
const activeKeyRef = useRef(activeKey);
|
|
320
|
+
const exitingKeyRef = useRef(exitingKey);
|
|
321
|
+
|
|
322
|
+
useLayoutEffect(() => {
|
|
323
|
+
activeKeyRef.current = activeKey;
|
|
324
|
+
}, [activeKey]);
|
|
325
|
+
|
|
326
|
+
useLayoutEffect(() => {
|
|
327
|
+
exitingKeyRef.current = exitingKey;
|
|
328
|
+
}, [exitingKey]);
|
|
329
|
+
|
|
330
|
+
useLayoutEffect(() => {
|
|
331
|
+
const prevKey = prevKeyRef.current;
|
|
332
|
+
if (prevKey === activeKey) return;
|
|
333
|
+
|
|
334
|
+
prevKeyRef.current = activeKey;
|
|
335
|
+
|
|
336
|
+
// Start a new switch
|
|
337
|
+
setExitingKey(prevKey);
|
|
338
|
+
|
|
339
|
+
if (mode === "out-in") {
|
|
340
|
+
// Incoming blocked; outgoing should exit immediately.
|
|
341
|
+
setEnterAllowed(false);
|
|
342
|
+
setExitStarted(true); // outgoing in: true -> false => triggers exit
|
|
343
|
+
} else {
|
|
344
|
+
// Incoming allowed immediately; outgoing should exit only after incoming has entered.
|
|
345
|
+
setEnterAllowed(true);
|
|
346
|
+
setExitStarted(false); // outgoing stays in:true until we flip it later
|
|
347
|
+
}
|
|
348
|
+
}, [activeKey, mode]);
|
|
349
|
+
|
|
350
|
+
const handleExited = (key) => (node, isAppearing) => {
|
|
351
|
+
if (typeof onExited === "function") onExited(node, isAppearing);
|
|
352
|
+
|
|
353
|
+
if (key === exitingKeyRef.current) {
|
|
354
|
+
setExitingKey(null);
|
|
355
|
+
|
|
356
|
+
// out-in: once outgoing is fully exited, allow incoming to start
|
|
357
|
+
if (mode === "out-in") {
|
|
358
|
+
setExitStarted(false);
|
|
359
|
+
setEnterAllowed(true);
|
|
360
|
+
}
|
|
361
|
+
// in-out: incoming already entered; just clean up outgoing
|
|
362
|
+
if (mode === "in-out") {
|
|
363
|
+
setExitStarted(false);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const handleEntered = (key) => (node, isAppearing) => {
|
|
369
|
+
if (typeof onEntered === "function") onEntered(node, isAppearing);
|
|
370
|
+
|
|
371
|
+
if (mode === "in-out") {
|
|
372
|
+
// Only start outgoing exit when the CURRENT incoming (activeKey) finishes entering.
|
|
373
|
+
if (key === activeKeyRef.current && exitingKeyRef.current != null) {
|
|
374
|
+
setExitStarted(true); // outgoing in:true -> false => triggers exit
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<div style={{ position: "relative" }}>
|
|
381
|
+
{childArray.map((child, index) => {
|
|
382
|
+
const key = child?.props?.key || index;
|
|
383
|
+
|
|
384
|
+
const isIncoming = key === activeKey;
|
|
385
|
+
const isOutgoing = key === exitingKey;
|
|
386
|
+
|
|
387
|
+
// Core rule:
|
|
388
|
+
// - Incoming: in = enterAllowed
|
|
389
|
+
// - Outgoing: in = !exitStarted
|
|
390
|
+
// - Others: in = false
|
|
391
|
+
const inProp = isIncoming
|
|
392
|
+
? enterAllowed
|
|
393
|
+
: isOutgoing
|
|
394
|
+
? !exitStarted
|
|
395
|
+
: false;
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<CSSTransition
|
|
399
|
+
key={key}
|
|
400
|
+
in={inProp}
|
|
401
|
+
timeout={timeout}
|
|
402
|
+
classNames={classNames}
|
|
403
|
+
mountOnEnter={mountOnEnter}
|
|
404
|
+
unmountOnExit={unmountOnExit}
|
|
405
|
+
appear={appear}
|
|
406
|
+
onEnter={onEnter}
|
|
407
|
+
onExiting={onExiting}
|
|
408
|
+
onEntered={handleEntered(key)}
|
|
409
|
+
onExited={handleExited(key)}
|
|
410
|
+
{...csstProps}
|
|
411
|
+
>
|
|
412
|
+
<div style={{ position: "absolute", inset: 0 }}>
|
|
413
|
+
{child}
|
|
414
|
+
</div>
|
|
415
|
+
</CSSTransition>
|
|
416
|
+
);
|
|
417
|
+
})}
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
|
|
247
423
|
/**
|
|
248
424
|
* Lilact doesn't need TransitionGroup, so it is the same as a fragment.
|
|
249
425
|
* In Lilact all the transitions and timeouts are automatically grouped.
|