lilact 0.1.0 → 0.2.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 +13 -5
- package/dist/lilact.development.min.js +9657 -2
- 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/classes/accessories.ErrorBoundary.html +11 -10
- package/docs/classes/accessories.Suspense.html +12 -22
- package/docs/classes/components.Component.html +11 -10
- package/docs/classes/components.HTMLComponent.html +11 -10
- package/docs/classes/components.RootComponent.html +11 -10
- package/docs/functions/accessories.Spinner.html +1 -1
- package/docs/functions/components.createComponent.html +1 -1
- package/docs/functions/components.createRoot.html +1 -1
- package/docs/functions/components.render.html +1 -1
- package/docs/functions/hooks.createContext.html +2 -2
- package/docs/functions/hooks.forwardRef.html +4 -0
- package/docs/functions/hooks.useActionState.html +1 -1
- package/docs/functions/hooks.useCallback.html +1 -1
- package/docs/functions/hooks.useContext.html +1 -1
- package/docs/functions/hooks.useEffect.html +1 -1
- package/docs/functions/hooks.useId.html +1 -1
- package/docs/functions/hooks.useImperativeHandle.html +7 -0
- package/docs/functions/hooks.useLayoutEffect.html +2 -2
- package/docs/functions/hooks.useLocalStorage.html +1 -1
- package/docs/functions/hooks.useMemo.html +2 -2
- package/docs/functions/hooks.useReducer.html +1 -1
- package/docs/functions/hooks.useRef.html +1 -1
- package/docs/functions/hooks.useState.html +2 -2
- package/docs/functions/hooks.useTransition.html +2 -2
- package/docs/functions/jsx.transpileJSX.html +1 -1
- package/docs/functions/run.lazy.html +7 -15
- package/docs/functions/run.runScripts.html +1 -1
- package/docs/functions/run.traceError.html +1 -1
- package/docs/functions/transition.CSSTransition.html +1 -1
- package/docs/functions/transition.Transition.html +15 -0
- package/docs/functions/transition.TransitionGroup.html +1 -1
- package/docs/index.html +9 -5
- package/docs/modules/hooks.html +1 -1
- package/docs/modules/jsx.html +1 -1
- package/docs/modules/transition.html +1 -1
- package/docs/static/demos/actionstate.jsx +4 -6
- package/docs/static/demos/context.jsx +1 -3
- package/docs/static/demos/lazy.jsx +5 -7
- package/docs/static/demos/modal.jsx +13 -15
- package/docs/static/demos/proptypes.jsx +5 -8
- package/docs/static/demos/reducer.jsx +21 -23
- package/docs/static/demos/suspense.jsx +4 -6
- package/docs/static/demos/transition.jsx +25 -25
- package/docs/static/demos/usetransition.jsx +2 -4
- package/docs/static/lilact.development.min.js +9657 -2
- package/docs/static/lilact.production.min.js +1 -1
- package/package.json +1 -1
- package/root/demos/actionstate.jsx +4 -6
- package/root/demos/context.jsx +1 -3
- package/root/demos/lazy.jsx +5 -7
- package/root/demos/modal.jsx +13 -15
- package/root/demos/proptypes.jsx +5 -8
- package/root/demos/reducer.jsx +21 -23
- package/root/demos/suspense.jsx +4 -6
- package/root/demos/transition.jsx +25 -25
- package/root/demos/usetransition.jsx +2 -4
- package/root/lilact.development.min.js +9657 -2
- package/root/lilact.production.min.js +1 -1
- package/src/accessories.jsx +12 -11
- package/src/components.jsx +9 -4
- package/src/hooks.jsx +82 -41
- package/src/jsx.js +1 -0
- package/src/lilact.jsx +5 -7
- package/src/pane.jsx +287 -0
- package/src/run.jsx +3 -17
- package/src/transition.jsx +32 -21
- package/webpack.config.js +1 -1
- package/docs/classes/transition.Transition.html +0 -31
- package/docs/variables/jsx.transpilerConfig.html +0 -1
package/src/hooks.jsx
CHANGED
|
@@ -50,10 +50,10 @@ export function useHook()
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Adds state to a component using a
|
|
53
|
+
* Adds state to a component using a [value, setter] pattern.
|
|
54
54
|
*
|
|
55
55
|
* @param {any} initialValue - Initial state value.
|
|
56
|
-
* @returns {any} Hook result
|
|
56
|
+
* @returns {any} Hook result `[state, setState]`.
|
|
57
57
|
*/
|
|
58
58
|
export function useState(val)
|
|
59
59
|
{
|
|
@@ -82,25 +82,27 @@ export function useState(val)
|
|
|
82
82
|
* @param {Array<any>} deps - Dependency list.
|
|
83
83
|
* @returns {Function} Memoized callback.
|
|
84
84
|
*/
|
|
85
|
-
export function useCallback(callback, deps=
|
|
85
|
+
export function useCallback(callback, deps=undefined)
|
|
86
86
|
{
|
|
87
|
+
if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
|
|
88
|
+
throw "callback dependencies must be an array or omitted.";
|
|
89
|
+
}
|
|
90
|
+
|
|
87
91
|
const hk = Lilact.useHook();
|
|
88
92
|
|
|
89
93
|
if( Lilact.isEmpty(hk) ) {
|
|
90
94
|
hk.callback = callback;
|
|
91
|
-
hk.deps = deps;
|
|
92
95
|
}
|
|
93
96
|
else {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
hk.callback = callback;
|
|
97
|
+
if(deps!==undefined && hk?.deps!==undefined && Lilact.shallowEqual(deps, hk.deps)) return hk.callback;
|
|
98
|
+
}
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
}
|
|
100
|
+
if(hk?.cleanup) {
|
|
101
|
+
hk.cleanup();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
hk.deps = deps;
|
|
105
|
+
|
|
104
106
|
return hk.callback;
|
|
105
107
|
}
|
|
106
108
|
|
|
@@ -108,7 +110,7 @@ export function useCallback(callback, deps=[{}])
|
|
|
108
110
|
* Creates a context object for sharing a value through the component tree.
|
|
109
111
|
*
|
|
110
112
|
* @param {any} [defaultValue] - Initial context value when no Provider is present.
|
|
111
|
-
* @returns {any} A context object
|
|
113
|
+
* @returns {any} A context object
|
|
112
114
|
*/
|
|
113
115
|
export function createContext(val)
|
|
114
116
|
{
|
|
@@ -164,7 +166,7 @@ export function useId(prefix="N")
|
|
|
164
166
|
/**
|
|
165
167
|
* Starts a transition and returns helpers for pending updates vs immediate ones.
|
|
166
168
|
*
|
|
167
|
-
* @returns {any}
|
|
169
|
+
* @returns {any} `[isPending, startTransition]`
|
|
168
170
|
*/
|
|
169
171
|
export function useTransition()
|
|
170
172
|
{
|
|
@@ -267,25 +269,25 @@ export function useRef(val = null)
|
|
|
267
269
|
* @param {Array<any>} [deps] - Dependency list.
|
|
268
270
|
* @returns {void}
|
|
269
271
|
*/
|
|
270
|
-
export function useLayoutEffect(setup, deps=
|
|
272
|
+
export function useLayoutEffect(setup, deps=undefined)
|
|
271
273
|
{
|
|
272
|
-
if(typeof(deps)!=='object' || deps.constructor.name!=='Array') {
|
|
273
|
-
throw "effect dependencies must be an array or omitted.";
|
|
274
|
+
if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
|
|
275
|
+
throw "layout effect dependencies must be an array or omitted.";
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
const hk = Lilact.useHook();
|
|
277
279
|
|
|
278
280
|
if( !Lilact.isEmpty(hk) ) {
|
|
279
|
-
if(deps.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
if(deps!==undefined && hk?.deps!==undefined && Lilact.shallowEqual(deps, hk.deps)) return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if(hk?.cleanup) {
|
|
285
|
+
hk.cleanup();
|
|
283
286
|
}
|
|
284
287
|
|
|
285
288
|
hk.deps = deps;
|
|
286
289
|
Lilact.layout_effects.add( ()=>{ hk.cleanup = setup(); });
|
|
287
290
|
Lilact.current_component[0].component.forceUpdate();
|
|
288
|
-
|
|
289
291
|
}
|
|
290
292
|
|
|
291
293
|
/**
|
|
@@ -297,17 +299,18 @@ export function useLayoutEffect(setup, deps=[{}])
|
|
|
297
299
|
*/
|
|
298
300
|
export function useEffect(setup, deps=[{}])
|
|
299
301
|
{
|
|
300
|
-
if(typeof(deps)!=='object' || deps.constructor.name!=='Array') {
|
|
302
|
+
if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
|
|
301
303
|
throw "effect dependencies must be an array or omitted.";
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
const hk = Lilact.useHook();
|
|
305
307
|
|
|
306
308
|
if( !Lilact.isEmpty(hk) ) {
|
|
307
|
-
if(deps.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
if(deps!==undefined && hk?.deps!==undefined && Lilact.shallowEqual(deps, hk.deps)) return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if(hk?.cleanup) {
|
|
313
|
+
hk.cleanup();
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
hk.deps = deps;
|
|
@@ -322,24 +325,21 @@ export function useEffect(setup, deps=[{}])
|
|
|
322
325
|
* @param {Array<any>} deps - Dependency list.
|
|
323
326
|
* @returns {any} Memoized value.
|
|
324
327
|
*/
|
|
325
|
-
export function useMemo(fn,deps=
|
|
328
|
+
export function useMemo(fn,deps=undefined)
|
|
326
329
|
{
|
|
330
|
+
if( deps!==undefined && (typeof(deps)!=='object' || deps.constructor.name!=='Array') ) {
|
|
331
|
+
throw "memo dependencies must be an array or omitted.";
|
|
332
|
+
}
|
|
333
|
+
|
|
327
334
|
const hk = Lilact.useHook();
|
|
328
335
|
|
|
329
|
-
if( Lilact.isEmpty(hk) ) {
|
|
330
|
-
hk.deps
|
|
331
|
-
hk.value = fn();
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
for(let d in deps) {
|
|
335
|
-
if( !Lilact.defaultIsEqual(deps[d],hk.deps[d]) ) {
|
|
336
|
-
hk.deps = deps;
|
|
337
|
-
hk.value = fn(hk.value);
|
|
338
|
-
break;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
336
|
+
if( !Lilact.isEmpty(hk) ) {
|
|
337
|
+
if(deps!==undefined && hk?.deps!==undefined && Lilact.shallowEqual(deps, hk.deps)) return hk.value;
|
|
341
338
|
}
|
|
342
339
|
|
|
340
|
+
hk.deps = deps;
|
|
341
|
+
hk.value = fn(hk.value);
|
|
342
|
+
|
|
343
343
|
return hk.value;
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -348,7 +348,7 @@ export function useMemo(fn,deps=[])
|
|
|
348
348
|
*
|
|
349
349
|
* @param {Function} action - The async/queued action function.
|
|
350
350
|
* @param {any} initialState - Initial state for the hook.
|
|
351
|
-
* @returns {any} Hook result
|
|
351
|
+
* @returns {any} Hook result
|
|
352
352
|
*/
|
|
353
353
|
export function useActionState(fn, initial_state)
|
|
354
354
|
{
|
|
@@ -383,7 +383,7 @@ export function useActionState(fn, initial_state)
|
|
|
383
383
|
* @param {Function} reducer - Reducer function.
|
|
384
384
|
* @param {any} initialArg - Initial state value (or initializer arg).
|
|
385
385
|
* @param {Function} [init] - Optional initializer function for lazy initial state.
|
|
386
|
-
* @returns {any} Hook result
|
|
386
|
+
* @returns {any} Hook result `[state, dispatch]`.
|
|
387
387
|
*/
|
|
388
388
|
export function useReducer(reducer, initialArg, init)
|
|
389
389
|
{
|
|
@@ -459,3 +459,44 @@ export function useDeferredValue(value, initialValue)
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
|
|
462
|
+
/**
|
|
463
|
+
* Wraps a render function so that a parent can pass a `ref` into it.
|
|
464
|
+
* The forwarded `ref` is provided as the second argument to the render function: `(props, ref)`.
|
|
465
|
+
*
|
|
466
|
+
* @param {function(props: any, ref: any)} render
|
|
467
|
+
* The component render function that receives the props and the forwarded ref.
|
|
468
|
+
* @returns {}
|
|
469
|
+
*/
|
|
470
|
+
export function forwardRef(render)
|
|
471
|
+
{
|
|
472
|
+
return (props)=>render({...props, ref: undefined}, props.ref);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Customizes the value that is exposed to the parent when it uses a `ref` on a component created with `forwardRef`.
|
|
478
|
+
* The object returned by `factory` becomes the value of `ref.current` for object refs.
|
|
479
|
+
*
|
|
480
|
+
* @param {Ref<any>} ref
|
|
481
|
+
* The ref that was received in your component (typically via `forwardRef` as the second argument).
|
|
482
|
+
* @param {function(): any} factory
|
|
483
|
+
* Function that returns the value to expose to the parent. Commonly an object with methods,
|
|
484
|
+
* or a DOM node reference.
|
|
485
|
+
* @param {Array<any>} [deps]
|
|
486
|
+
* Dependency list that controls when the exposed value is recalculated.
|
|
487
|
+
* @returns {void}
|
|
488
|
+
*/
|
|
489
|
+
export function useImperativeHandle(ref, factory, deps)
|
|
490
|
+
{
|
|
491
|
+
if(deps!==undefined && ref?.deps!==undefined && Lilact.shallowEqual(deps, ref.deps)) return;
|
|
492
|
+
|
|
493
|
+
ref.deps = deps;
|
|
494
|
+
|
|
495
|
+
Lilact.setTimeout( ()=>{
|
|
496
|
+
if(typeof ref?.current !== 'object') {
|
|
497
|
+
ref.current = {};
|
|
498
|
+
}
|
|
499
|
+
Object.assign( ref.current, factory(), 0 );
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
package/src/jsx.js
CHANGED
package/src/lilact.jsx
CHANGED
|
@@ -47,6 +47,7 @@ import * as misc from './misc.jsx';
|
|
|
47
47
|
|
|
48
48
|
import * as router from './router.jsx';
|
|
49
49
|
import * as accessories from './accessories.jsx';
|
|
50
|
+
import {ResizablePane} from './pane.jsx';
|
|
50
51
|
|
|
51
52
|
import {transpileJSX, transpilerConfig} from "./jsx";
|
|
52
53
|
|
|
@@ -61,7 +62,7 @@ import {transpileJSX, transpilerConfig} from "./jsx";
|
|
|
61
62
|
* @property emotion - Handle to Emotion’s CSS-in-JS package for styling (https://github.com/emotion-js/emotion).
|
|
62
63
|
*/
|
|
63
64
|
export const Lilact =
|
|
64
|
-
{
|
|
65
|
+
{
|
|
65
66
|
|
|
66
67
|
VERSION: "beta.0",
|
|
67
68
|
|
|
@@ -86,27 +87,24 @@ export const Lilact =
|
|
|
86
87
|
...router,
|
|
87
88
|
...accessories,
|
|
88
89
|
|
|
90
|
+
ResizablePane,
|
|
91
|
+
|
|
89
92
|
transpileJSX,
|
|
90
93
|
transpilerConfig,
|
|
91
94
|
|
|
92
95
|
// Dependencies ()
|
|
93
96
|
|
|
94
97
|
PropTypes,
|
|
98
|
+
//redux: {...redux, createSlice, createAsyncThunk},
|
|
95
99
|
redux,
|
|
96
100
|
emotion,
|
|
97
101
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
// or, if you prefer DOMContentLoaded:
|
|
102
104
|
document.addEventListener('DOMContentLoaded', () => {
|
|
103
105
|
Lilact.runScripts();
|
|
104
106
|
});
|
|
105
107
|
|
|
106
|
-
function yourFunctionHere() {
|
|
107
|
-
// ...
|
|
108
|
-
}
|
|
109
|
-
|
|
110
108
|
ʔ if(DEBUG) {
|
|
111
109
|
console.log(`Lilact (Version: ${Lilact.VERSION}) - Debug Mode`);
|
|
112
110
|
console.log(`Copyright(C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>`);
|
package/src/pane.jsx
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from "./hooks.jsx";
|
|
2
|
+
import { Children } from "./misc.jsx";
|
|
3
|
+
|
|
4
|
+
function clamp(n, min, max) {
|
|
5
|
+
return Math.max(min, Math.min(max, n));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ResizablePane
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Supports "horizontal" (left/top size affects width) and "vertical" (affects height)
|
|
13
|
+
* - Initial position via props.position
|
|
14
|
+
* - Methods to set mode and position via ref:
|
|
15
|
+
* ref.current.setMode(mode)
|
|
16
|
+
* ref.current.setPosition(position)
|
|
17
|
+
* - Callback when size changes via onSizeChange
|
|
18
|
+
* - Children are rendered in two separate containers (no portals)
|
|
19
|
+
*/
|
|
20
|
+
export const ResizablePane = forwardRef(function Pane(
|
|
21
|
+
{
|
|
22
|
+
mode = "horizontal", // "horizontal" | "vertical"
|
|
23
|
+
initialPosition, // optional (alias-ish to position)
|
|
24
|
+
position, // controlled position (number)
|
|
25
|
+
defaultPosition = 0.5, // used if position/initialPosition not provided
|
|
26
|
+
min = 0.1, // clamp in "percent of container" units: 0..1
|
|
27
|
+
max = 0.9, // clamp in "percent of container" units: 0..1
|
|
28
|
+
splitterSize = 8, // px thickness of the drag handle
|
|
29
|
+
onSizeChange, // (newPosition: number) => void
|
|
30
|
+
style,
|
|
31
|
+
className,
|
|
32
|
+
leftPaneStyle,
|
|
33
|
+
rightPaneStyle,
|
|
34
|
+
splitterStyle,
|
|
35
|
+
children, // expects two children: [A, B]
|
|
36
|
+
},
|
|
37
|
+
ref
|
|
38
|
+
) {
|
|
39
|
+
const [internalMode, setInternalMode] = useState(mode);
|
|
40
|
+
const [pos, setPos] = useState(() => {
|
|
41
|
+
const start =
|
|
42
|
+
position ?? initialPosition ?? (defaultPosition != null ? defaultPosition : 0.5);
|
|
43
|
+
return clamp(start, min, max);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const containerRef = useRef(null);
|
|
47
|
+
const draggingRef = useRef(false);
|
|
48
|
+
|
|
49
|
+
const panes = Children.toArray(children);
|
|
50
|
+
const leftChild = panes[0] ?? null;
|
|
51
|
+
const rightChild = panes[1] ?? null;
|
|
52
|
+
|
|
53
|
+
const modeResolved = mode != null ? mode : internalMode;
|
|
54
|
+
|
|
55
|
+
// Keep internalMode aligned if mode is provided as a prop
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (mode != null) setInternalMode(mode);
|
|
58
|
+
}, [mode]);
|
|
59
|
+
|
|
60
|
+
// Controlled position: update state when prop changes
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (position == null) return;
|
|
63
|
+
setPos(clamp(position, min, max));
|
|
64
|
+
}, [position, min, max]);
|
|
65
|
+
|
|
66
|
+
const setPosition = (newPos) => {
|
|
67
|
+
const next = clamp(newPos, min, max);
|
|
68
|
+
if (position == null) setPos(next); // only update internal state if uncontrolled
|
|
69
|
+
onSizeChange?.(next);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const setMode = (nextMode) => {
|
|
73
|
+
const m = nextMode === "vertical" ? "vertical" : "horizontal";
|
|
74
|
+
if (mode == null) setInternalMode(m);
|
|
75
|
+
// If mode is controlled via prop, consumer should re-render with new prop.
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
useImperativeHandle(
|
|
79
|
+
ref,
|
|
80
|
+
() => ({
|
|
81
|
+
setPosition,
|
|
82
|
+
setMode,
|
|
83
|
+
getPosition: () => (position == null ? pos : clamp(position, min, max)),
|
|
84
|
+
getMode: () => modeResolved,
|
|
85
|
+
}),
|
|
86
|
+
[pos, position, min, max, modeResolved, onSizeChange, mode]
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const updateFromClientXorY = (clientX, clientY) => {
|
|
90
|
+
const el = containerRef.current;
|
|
91
|
+
if (!el) return;
|
|
92
|
+
|
|
93
|
+
const rect = el.getBoundingClientRect();
|
|
94
|
+
let next;
|
|
95
|
+
|
|
96
|
+
if (modeResolved === "horizontal") {
|
|
97
|
+
const usable = rect.width;
|
|
98
|
+
if (usable <= 0) return;
|
|
99
|
+
next = (clientX - rect.left) / usable;
|
|
100
|
+
} else {
|
|
101
|
+
const usable = rect.height;
|
|
102
|
+
if (usable <= 0) return;
|
|
103
|
+
next = (clientY - rect.top) / usable;
|
|
104
|
+
}
|
|
105
|
+
setPosition(next);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const onPointerDown = (e) => {
|
|
109
|
+
e.preventDefault();
|
|
110
|
+
draggingRef.current = true;
|
|
111
|
+
|
|
112
|
+
// Capture pointer so we still get events outside the handle area.
|
|
113
|
+
try {
|
|
114
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
115
|
+
} catch {
|
|
116
|
+
// ignore
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
updateFromClientXorY(e.clientX, e.clientY);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const onPointerMove = (e) => {
|
|
123
|
+
if (!draggingRef.current) return;
|
|
124
|
+
updateFromClientXorY(e.clientX, e.clientY);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const stopDragging = () => {
|
|
128
|
+
draggingRef.current = false;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Attach global listeners to ensure smooth dragging even if pointer capture fails
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const handleMove = (e) => {
|
|
134
|
+
if (!draggingRef.current) return;
|
|
135
|
+
updateFromClientXorY(e.clientX, e.clientY);
|
|
136
|
+
};
|
|
137
|
+
const handleUp = () => stopDragging();
|
|
138
|
+
|
|
139
|
+
window.addEventListener("pointermove", handleMove, { passive: false });
|
|
140
|
+
window.addEventListener("pointerup", handleUp, { passive: true });
|
|
141
|
+
window.addEventListener("pointercancel", handleUp, { passive: true });
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
window.removeEventListener("pointermove", handleMove);
|
|
145
|
+
window.removeEventListener("pointerup", handleUp);
|
|
146
|
+
window.removeEventListener("pointercancel", handleUp);
|
|
147
|
+
};
|
|
148
|
+
}, [modeResolved, min, max, position, onSizeChange]);
|
|
149
|
+
|
|
150
|
+
const posResolved = position == null ? pos : clamp(position, min, max);
|
|
151
|
+
|
|
152
|
+
const sizes = useMemo(() => {
|
|
153
|
+
if (modeResolved === "horizontal") {
|
|
154
|
+
// left width = pos, splitter width = splitterSize, right flexes
|
|
155
|
+
// We set left/right as percentages and keep splitter fixed in px.
|
|
156
|
+
return {
|
|
157
|
+
left: `${posResolved * 100}%`,
|
|
158
|
+
right: `calc(${100 - posResolved * 100}% - ${splitterSize}px)`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
left: `${posResolved * 100}%`,
|
|
163
|
+
right: `calc(${100 - posResolved * 100}% - ${splitterSize}px)`,
|
|
164
|
+
};
|
|
165
|
+
}, [modeResolved, posResolved, splitterSize]);
|
|
166
|
+
|
|
167
|
+
// Improve keyboard accessibility (arrow keys when handle is focused)
|
|
168
|
+
const onSplitterKeyDown = (e) => {
|
|
169
|
+
const step = 0.02;
|
|
170
|
+
let delta = 0;
|
|
171
|
+
if (modeResolved === "horizontal") {
|
|
172
|
+
if (e.key === "ArrowLeft") delta = -step;
|
|
173
|
+
if (e.key === "ArrowRight") delta = step;
|
|
174
|
+
} else {
|
|
175
|
+
if (e.key === "ArrowUp") delta = -step;
|
|
176
|
+
if (e.key === "ArrowDown") delta = step;
|
|
177
|
+
}
|
|
178
|
+
if (delta !== 0) {
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
setPosition(posResolved + delta);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Ensure position clamps correctly if min/max change
|
|
185
|
+
useLayoutEffect(() => {
|
|
186
|
+
setPos((p) => clamp(p, min, max));
|
|
187
|
+
}, [min, max]);
|
|
188
|
+
|
|
189
|
+
const rootStyle = {
|
|
190
|
+
display: "flex",
|
|
191
|
+
width: "100%",
|
|
192
|
+
height: "100%",
|
|
193
|
+
overflow: "hidden",
|
|
194
|
+
touchAction: "none",
|
|
195
|
+
...(style || {}),
|
|
196
|
+
flexDirection: modeResolved === "horizontal" ? "row" : "column",
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const leftPaneComputed =
|
|
200
|
+
modeResolved === "horizontal"
|
|
201
|
+
? {
|
|
202
|
+
width: sizes.left,
|
|
203
|
+
flex: `0 0 ${sizes.left}`,
|
|
204
|
+
overflow: "auto",
|
|
205
|
+
...(leftPaneStyle || {}),
|
|
206
|
+
}
|
|
207
|
+
: {
|
|
208
|
+
height: sizes.left,
|
|
209
|
+
flex: `0 0 ${sizes.left}`,
|
|
210
|
+
overflow: "auto",
|
|
211
|
+
...(leftPaneStyle || {}),
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const rightPaneComputed =
|
|
215
|
+
modeResolved === "horizontal"
|
|
216
|
+
? {
|
|
217
|
+
width: `calc(${100 - posResolved * 100}% - ${splitterSize}px)`,
|
|
218
|
+
flex: `1 1 auto`,
|
|
219
|
+
overflow: "auto",
|
|
220
|
+
...(rightPaneStyle || {}),
|
|
221
|
+
}
|
|
222
|
+
: {
|
|
223
|
+
height: `calc(${100 - posResolved * 100}% - ${splitterSize}px)`,
|
|
224
|
+
flex: `1 1 auto`,
|
|
225
|
+
overflow: "auto",
|
|
226
|
+
...(rightPaneStyle || {}),
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const splitterComputed =
|
|
230
|
+
modeResolved === "horizontal"
|
|
231
|
+
? {
|
|
232
|
+
width: `${splitterSize}px`,
|
|
233
|
+
flex: `0 0 ${splitterSize}px`,
|
|
234
|
+
cursor: "col-resize",
|
|
235
|
+
background: "transparent",
|
|
236
|
+
...(splitterStyle || {}),
|
|
237
|
+
}
|
|
238
|
+
: {
|
|
239
|
+
height: `${splitterSize}px`,
|
|
240
|
+
flex: `0 0 ${splitterSize}px`,
|
|
241
|
+
cursor: "row-resize",
|
|
242
|
+
background: "transparent",
|
|
243
|
+
...(splitterStyle || {}),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const dividerVisualStyle =
|
|
247
|
+
modeResolved === "horizontal"
|
|
248
|
+
? {
|
|
249
|
+
height: "100%",
|
|
250
|
+
width: "100%",
|
|
251
|
+
background: "rgba(0,0,0,0.08)",
|
|
252
|
+
}
|
|
253
|
+
: {
|
|
254
|
+
width: "100%",
|
|
255
|
+
height: "100%",
|
|
256
|
+
background: "rgba(0,0,0,0.08)",
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
<div
|
|
261
|
+
ref={containerRef}
|
|
262
|
+
className={className}
|
|
263
|
+
style={rootStyle}
|
|
264
|
+
onPointerMove={onPointerMove}
|
|
265
|
+
>
|
|
266
|
+
<div style={leftPaneComputed}>{leftChild}</div>
|
|
267
|
+
|
|
268
|
+
<div
|
|
269
|
+
role="separator"
|
|
270
|
+
aria-orientation={modeResolved === "horizontal" ? "vertical" : "horizontal"}
|
|
271
|
+
aria-valuemin={min}
|
|
272
|
+
aria-valuemax={max}
|
|
273
|
+
aria-valuenow={posResolved}
|
|
274
|
+
tabIndex={0}
|
|
275
|
+
onPointerDown={onPointerDown}
|
|
276
|
+
onPointerUp={stopDragging}
|
|
277
|
+
onPointerCancel={stopDragging}
|
|
278
|
+
onKeyDown={onSplitterKeyDown}
|
|
279
|
+
style={splitterComputed}
|
|
280
|
+
>
|
|
281
|
+
<div style={dividerVisualStyle} />
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<div style={rightPaneComputed}>{rightChild}</div>
|
|
285
|
+
</div>
|
|
286
|
+
);
|
|
287
|
+
});
|
package/src/run.jsx
CHANGED
|
@@ -180,21 +180,7 @@ export function require(path, force_update)
|
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
182
|
* Wrapper that enables async, code-split component loading. `lazy` should be used
|
|
183
|
-
* outside the component definintion
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* @example
|
|
187
|
-
* ```jsx
|
|
188
|
-
* const LazyWidget = lazy(() => Lilact.require("./Widget"));
|
|
189
|
-
*
|
|
190
|
-
* export function Page() {
|
|
191
|
-
* return (
|
|
192
|
-
* <Suspense fallback={<Spinner/>}>
|
|
193
|
-
* <LazyWidget />
|
|
194
|
-
* </Suspense>
|
|
195
|
-
* );
|
|
196
|
-
* }
|
|
197
|
-
* ```
|
|
183
|
+
* outside the component definintion or it will produce new components on each rerender.
|
|
198
184
|
*
|
|
199
185
|
* @param factory - A function with **no arguments** that returns a `Promise`.
|
|
200
186
|
* The promise must resolve to a module whose module.exports.default is a Lilact component
|
|
@@ -202,12 +188,12 @@ export function require(path, force_update)
|
|
|
202
188
|
*
|
|
203
189
|
* @returns A Lilact component that should be rendered inside a {@link Suspense} boundary.
|
|
204
190
|
*/
|
|
205
|
-
export function lazy(
|
|
191
|
+
export function lazy(factory) {
|
|
206
192
|
let status = "pending"; // pending | success | error
|
|
207
193
|
let result; // component | error
|
|
208
194
|
|
|
209
195
|
Lilact[LAZY] = true;
|
|
210
|
-
result =
|
|
196
|
+
result = factory();
|
|
211
197
|
|
|
212
198
|
if(Lilact.isThenable(result)) {
|
|
213
199
|
result.then(
|