lilact 0.10.2 → 0.11.1
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 +165 -21
- 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/misc.deepEqual.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/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/misc.html +1 -1
- package/docs/modules/transition.html +1 -1
- package/docs/static/demos/switch-transition.jsx +38 -34
- package/docs/static/demos/transition.jsx +1 -1
- package/docs/static/index.html +6 -5
- package/docs/static/lilact.development.js +165 -21
- 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/demos/transition.jsx +1 -1
- package/root/index.html +6 -5
- package/root/lilact.development.js +165 -21
- package/root/lilact.development.min.js +1 -1
- package/root/lilact.production.min.js +1 -1
- package/src/lilact.jsx +1 -1
- package/src/misc.jsx +0 -13
- package/src/transition.jsx +180 -4
- package/docs/functions/misc.classNames.html +0 -3
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.
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html><html class="default" lang="en" data-base="../"><head><meta charset="utf-8"/><meta http-equiv="x-ua-compatible" content="IE=edge"/><title>classNames | Lilact</title><meta name="description" content="Documentation for Lilact"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="../assets/style.css"/><link rel="stylesheet" href="../assets/highlight.css"/><script defer src="../assets/main.js"></script><script async src="../assets/icons.js" id="tsd-icons-script"></script><script async src="../assets/search.js" id="tsd-search-script"></script><script async src="../assets/navigation.js" id="tsd-nav-script"></script><script async src="../assets/hierarchy.js" id="tsd-hierarchy-script"></script></head><body><script>document.documentElement.dataset.theme = localStorage.getItem("tsd-theme") || "os";document.body.style.display="none";setTimeout(() => window.app?app.showPage():document.body.style.removeProperty("display"),500)</script><header class="tsd-page-toolbar"><div class="tsd-toolbar-contents container"><a href="../index.html" class="title">Lilact</a><div id="tsd-toolbar-links"></div><button id="tsd-search-trigger" class="tsd-widget" aria-label="Search"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-search"></use></svg></button><dialog id="tsd-search" aria-label="Search"><input role="combobox" id="tsd-search-input" aria-controls="tsd-search-results" aria-autocomplete="list" aria-expanded="true" autocapitalize="off" autocomplete="off" placeholder="Search the docs" maxLength="100"/><ul role="listbox" id="tsd-search-results"></ul><div id="tsd-search-status" aria-live="polite" aria-atomic="true"><div>Preparing search index...</div></div></dialog><a href="#" class="tsd-widget menu" id="tsd-toolbar-menu-trigger" data-toggle="menu" aria-label="Menu"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-menu"></use></svg></a></div></header><div class="container container-main"><div class="col-content"><div class="tsd-page-title"><ul class="tsd-breadcrumb" aria-label="Breadcrumb"><li><a href="../modules/misc.html">misc</a></li><li><a href="" aria-current="page">classNames</a></li></ul><h1>Function classNames</h1></div><section class="tsd-panel"><ul class="tsd-signatures"><li class=""><div class="tsd-signature tsd-anchor-link" id="classnames"><span class="tsd-kind-call-signature">classNames</span><span class="tsd-signature-symbol">(</span><span class="tsd-kind-parameter">classes</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">any</span><span class="tsd-signature-symbol">)</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span><a href="#classnames" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></div><div class="tsd-description"><div class="tsd-comment tsd-typography"><p>Utility for applying one or more class names to an element.</p>
|
|
2
|
-
</div><div class="tsd-parameters"><h4 class="tsd-parameters-title">Parameters</h4><ul class="tsd-parameter-list"><li><span><span class="tsd-kind-parameter">classes</span>: <span class="tsd-signature-type">any</span></span><div class="tsd-comment tsd-typography"><p>One or more class name values to combine.</p>
|
|
3
|
-
</div></li></ul></div><h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">string</span></h4><aside class="tsd-sources"><ul><li>Defined in misc.js:200</li></ul></aside></div></li></ul></section></div><div class="col-sidebar"><div class="page-menu"><div class="tsd-navigation settings"><details class="tsd-accordion"><summary class="tsd-accordion-summary"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" aria-hidden="true"><use href="../assets/icons.svg#icon-chevronDown"></use></svg><h3>Settings</h3></summary><div class="tsd-accordion-details"><div class="tsd-filter-visibility"><span class="settings-label">Member Visibility</span><ul id="tsd-filter-options"><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-protected" name="protected"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Protected</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-inherited" name="inherited" checked/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>Inherited</span></label></li><li class="tsd-filter-item"><label class="tsd-filter-input"><input type="checkbox" id="tsd-filter-external" name="external"/><svg width="32" height="32" viewBox="0 0 32 32" aria-hidden="true"><rect class="tsd-checkbox-background" width="30" height="30" x="1" y="1" rx="6" fill="none"></rect><path class="tsd-checkbox-checkmark" d="M8.35422 16.8214L13.2143 21.75L24.6458 10.25" stroke="none" stroke-width="3.5" stroke-linejoin="round" fill="none"></path></svg><span>External</span></label></li></ul></div><div class="tsd-theme-toggle"><label class="settings-label" for="tsd-theme">Theme</label><select id="tsd-theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></div></div></details></div></div><div class="site-menu"><nav class="tsd-navigation"><a href="../modules.html">Lilact</a><ul class="tsd-small-nested-navigation" id="tsd-nav-container"><li>Loading...</li></ul></nav></div></div></div><footer><p class="tsd-generator">Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></footer><div class="overlay"></div></body></html>
|