lilact 0.0.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/Icon/r +0 -0
- package/LICENSE.TXT +26 -0
- package/README.md +116 -0
- package/bin/copy-demo.js +26 -0
- package/bin/transpile-dir.js +112 -0
- package/bin/transpile.js +137 -0
- package/dist/lilact.development.js +9344 -0
- package/dist/lilact.development.js.map +1 -0
- package/dist/lilact.production.min.js +63 -0
- package/dist/lilact.production.min.js.map +1 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +71 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1633 -0
- package/docs/classes/accessories.ErrorBoundary.html +37 -0
- package/docs/classes/accessories.Suspense.html +41 -0
- package/docs/classes/components.Component.html +45 -0
- package/docs/classes/components.HTMLComponent.html +32 -0
- package/docs/classes/components.RootComponent.html +32 -0
- package/docs/classes/transition.Transition.html +31 -0
- package/docs/functions/accessories.Spinner.html +9 -0
- package/docs/functions/components.createComponent.html +8 -0
- package/docs/functions/components.createRoot.html +5 -0
- package/docs/functions/components.render.html +5 -0
- package/docs/functions/hooks.createContext.html +3 -0
- package/docs/functions/hooks.useActionState.html +3 -0
- package/docs/functions/hooks.useCallback.html +5 -0
- package/docs/functions/hooks.useContext.html +3 -0
- package/docs/functions/hooks.useDeferredValue.html +4 -0
- package/docs/functions/hooks.useEffect.html +3 -0
- package/docs/functions/hooks.useHook.html +5 -0
- package/docs/functions/hooks.useId.html +4 -0
- package/docs/functions/hooks.useLayoutEffect.html +3 -0
- package/docs/functions/hooks.useLocalStorage.html +4 -0
- package/docs/functions/hooks.useMemo.html +4 -0
- package/docs/functions/hooks.useReducer.html +6 -0
- package/docs/functions/hooks.useRef.html +4 -0
- package/docs/functions/hooks.useState.html +3 -0
- package/docs/functions/hooks.useTransition.html +3 -0
- package/docs/functions/jsx.transpileJSX.html +6 -0
- package/docs/functions/misc.Fragment.html +3 -0
- package/docs/functions/misc.classNames.html +2 -0
- package/docs/functions/misc.deepEqual.html +3 -0
- package/docs/functions/misc.findDOMNode.html +3 -0
- package/docs/functions/misc.getComponentByPointer.html +3 -0
- package/docs/functions/misc.isAsync.html +3 -0
- package/docs/functions/misc.isClass.html +3 -0
- package/docs/functions/misc.isEmpty.html +3 -0
- package/docs/functions/misc.isError.html +3 -0
- package/docs/functions/misc.isThenable.html +3 -0
- package/docs/functions/misc.isValidElement.html +3 -0
- package/docs/functions/misc.shallowEqual.html +3 -0
- package/docs/functions/redux.Provider.html +5 -0
- package/docs/functions/redux.connect.html +5 -0
- package/docs/functions/redux.useDispatch.html +3 -0
- package/docs/functions/redux.useSelector.html +5 -0
- package/docs/functions/redux.useStore.html +3 -0
- package/docs/functions/router.HashRouter.html +5 -0
- package/docs/functions/router.Link.html +12 -0
- package/docs/functions/router.NavLink.html +10 -0
- package/docs/functions/router.Route.html +6 -0
- package/docs/functions/router.Routes.html +4 -0
- package/docs/functions/router.useLocation.html +3 -0
- package/docs/functions/router.useNavigate.html +3 -0
- package/docs/functions/run.require.html +14 -0
- package/docs/functions/run.run.html +13 -0
- package/docs/functions/run.runScripts.html +7 -0
- package/docs/functions/run.traceError.html +6 -0
- package/docs/functions/timers.animationFramePromise.html +3 -0
- package/docs/functions/timers.clearInterval.html +2 -0
- package/docs/functions/timers.clearTimeout.html +2 -0
- package/docs/functions/timers.grabTimers.html +3 -0
- package/docs/functions/timers.pauseTimers.html +2 -0
- package/docs/functions/timers.releaseTimers.html +2 -0
- package/docs/functions/timers.resetTimers.html +2 -0
- package/docs/functions/timers.resumeTimers.html +2 -0
- package/docs/functions/timers.setInterval.html +5 -0
- package/docs/functions/timers.setTimeout.html +4 -0
- package/docs/functions/timers.timeoutPromise.html +4 -0
- package/docs/functions/transition.CSSTransition.html +18 -0
- package/docs/functions/transition.TransitionGroup.html +5 -0
- package/docs/hierarchy.html +1 -0
- package/docs/index.html +88 -0
- package/docs/media/icon.png +0 -0
- package/docs/modules/accessories.html +1 -0
- package/docs/modules/components.html +1 -0
- package/docs/modules/hooks.html +1 -0
- package/docs/modules/jsx.html +1 -0
- package/docs/modules/misc.html +1 -0
- package/docs/modules/redux.html +1 -0
- package/docs/modules/router.html +1 -0
- package/docs/modules/run.html +1 -0
- package/docs/modules/timers.html +1 -0
- package/docs/modules/transition.html +1 -0
- package/docs/modules.html +1 -0
- package/docs/static/demos/actionstate.jsx +41 -0
- package/docs/static/demos/boundary.jsx +52 -0
- package/docs/static/demos/context.jsx +68 -0
- package/docs/static/demos/css-transition.jsx +65 -0
- package/docs/static/demos/modal.jsx +41 -0
- package/docs/static/demos/proptypes.jsx +37 -0
- package/docs/static/demos/reducer.jsx +71 -0
- package/docs/static/demos/redux.jsx +58 -0
- package/docs/static/demos/router.jsx +153 -0
- package/docs/static/demos/stopwatch.jsx +42 -0
- package/docs/static/demos/suspense.jsx +48 -0
- package/docs/static/demos/transition.jsx +40 -0
- package/docs/static/demos/use-differed.jsx +30 -0
- package/docs/static/demos/usetransition.jsx +37 -0
- package/docs/static/index.html +212 -0
- package/docs/static/lilact.development.js +9344 -0
- package/docs/static/lilact.production.min.js +63 -0
- package/docs/static/prismjs/LICENSE.md +21 -0
- package/docs/static/prismjs/prism-jsx.min.js +1 -0
- package/docs/static/prismjs/prism.min.css +1 -0
- package/docs/static/prismjs/prism.min.js +16 -0
- package/docs/variables/jsx.transpilerConfig.html +1 -0
- package/docs/variables/misc.Children.html +10 -0
- package/icon.png +0 -0
- package/package.json +39 -0
- package/root/demos/actionstate.jsx +41 -0
- package/root/demos/boundary.jsx +52 -0
- package/root/demos/context.jsx +68 -0
- package/root/demos/css-transition.jsx +65 -0
- package/root/demos/modal.jsx +41 -0
- package/root/demos/proptypes.jsx +37 -0
- package/root/demos/reducer.jsx +71 -0
- package/root/demos/redux.jsx +58 -0
- package/root/demos/router.jsx +153 -0
- package/root/demos/stopwatch.jsx +42 -0
- package/root/demos/suspense.jsx +48 -0
- package/root/demos/transition.jsx +40 -0
- package/root/demos/use-differed.jsx +30 -0
- package/root/demos/usetransition.jsx +37 -0
- package/root/index.html +212 -0
- package/root/lilact.development.js +9344 -0
- package/root/lilact.production.min.js +63 -0
- package/root/prismjs/LICENSE.md +21 -0
- package/root/prismjs/prism-jsx.min.js +1 -0
- package/root/prismjs/prism.min.css +1 -0
- package/root/prismjs/prism.min.js +16 -0
- package/src/accessories.jsx +287 -0
- package/src/components.jsx +1063 -0
- package/src/events.jsx +175 -0
- package/src/hooks.jsx +461 -0
- package/src/jsx.addons.js +70 -0
- package/src/jsx.js +1168 -0
- package/src/lilact.jsx +115 -0
- package/src/loader.cjs +59 -0
- package/src/misc.jsx +419 -0
- package/src/redux.jsx +198 -0
- package/src/router.jsx +282 -0
- package/src/run.jsx +275 -0
- package/src/timers.jsx +313 -0
- package/src/transition.jsx +244 -0
- package/src/vlq.js +94 -0
- package/tsconfig.json +15 -0
- package/typedoc.json +23 -0
- package/webpack.config.js +196 -0
package/src/events.jsx
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Lilact
|
|
4
|
+
Copyright (C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
BSD-2-Clause
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
* Redistributions of source code must retain the above copyright
|
|
13
|
+
notice, this list of conditions and the following disclaimer.
|
|
14
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
15
|
+
notice, this list of conditions and the following disclaimer in the
|
|
16
|
+
documentation and/or other materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
27
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Minimal shims (small and explicit)
|
|
33
|
+
if (typeof Element !== 'undefined' && !Element.prototype.matches) {
|
|
34
|
+
Element.prototype.matches = Element.prototype.msMatchesSelector ||
|
|
35
|
+
Element.prototype.webkitMatchesSelector ||
|
|
36
|
+
function(s) {
|
|
37
|
+
var matches = (this.document || this.ownerDocument).querySelectorAll(s);
|
|
38
|
+
var i = matches.length;
|
|
39
|
+
while (--i >= 0 && matches.item(i) !== this) {}
|
|
40
|
+
return i > -1;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof Event !== 'undefined' && !Event.prototype.composedPath) {
|
|
45
|
+
Event.prototype.composedPath = function() {
|
|
46
|
+
var path = [];
|
|
47
|
+
var el = this.target;
|
|
48
|
+
while (el) {
|
|
49
|
+
path.push(el);
|
|
50
|
+
el = el.parentElement;
|
|
51
|
+
}
|
|
52
|
+
path.push(window);
|
|
53
|
+
return path;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Event pool for reuse
|
|
58
|
+
const _pool = [];
|
|
59
|
+
const MAX_POOL_SIZE = 10;
|
|
60
|
+
|
|
61
|
+
function createSyntheticEvent(nativeEvent, currentTarget) {
|
|
62
|
+
// Reuse object from pool if available
|
|
63
|
+
const e = _pool.length ? _pool.pop() : {};
|
|
64
|
+
|
|
65
|
+
e.nativeEvent = nativeEvent;
|
|
66
|
+
e.type = nativeEvent.type;
|
|
67
|
+
e.target = nativeEvent.target || nativeEvent.srcElement || null;
|
|
68
|
+
e.currentTarget = currentTarget || nativeEvent.currentTarget || null;
|
|
69
|
+
e.timeStamp = nativeEvent.timeStamp || Date.now();
|
|
70
|
+
e.defaultPrevented = !!(nativeEvent.defaultPrevented);
|
|
71
|
+
e.isPropagationStopped = false;
|
|
72
|
+
e.isPersistent = false;
|
|
73
|
+
|
|
74
|
+
e.isDefaultPrevented = () => e.defaultPrevented;
|
|
75
|
+
e.preventDefault = () => {
|
|
76
|
+
if (nativeEvent.preventDefault) nativeEvent.preventDefault();
|
|
77
|
+
e.defaultPrevented = true;
|
|
78
|
+
};
|
|
79
|
+
e.stopPropagation = () => {
|
|
80
|
+
if (nativeEvent.stopPropagation) nativeEvent.stopPropagation();
|
|
81
|
+
e.isPropagationStopped = true;
|
|
82
|
+
};
|
|
83
|
+
e.persist = () => { e.isPersistent = true; };
|
|
84
|
+
|
|
85
|
+
// Convenience: normalized values for common props (touch, key, target value)
|
|
86
|
+
e.nativeEvent = nativeEvent;
|
|
87
|
+
e.key = nativeEvent.key || null;
|
|
88
|
+
e.code = nativeEvent.code || null;
|
|
89
|
+
e.which = nativeEvent.which || nativeEvent.keyCode || null;
|
|
90
|
+
|
|
91
|
+
// For input-like events normalize value and checked
|
|
92
|
+
try {
|
|
93
|
+
const tgt = e.target;
|
|
94
|
+
e.value = tgt && ('value' in tgt) ? tgt.value : undefined;
|
|
95
|
+
e.checked = tgt && ('checked' in tgt) ? tgt.checked : undefined;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
e.value = undefined;
|
|
98
|
+
e.checked = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// composedPath helper
|
|
102
|
+
e.path = typeof nativeEvent.composedPath === 'function' ? nativeEvent.composedPath() : [e.target];
|
|
103
|
+
|
|
104
|
+
return e;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function releaseSyntheticEvent(e) {
|
|
108
|
+
if (e && !e.isPersistent) {
|
|
109
|
+
// Clean up references to avoid leaks
|
|
110
|
+
e.nativeEvent = null;
|
|
111
|
+
e.type = null;
|
|
112
|
+
e.target = null;
|
|
113
|
+
e.currentTarget = null;
|
|
114
|
+
e.timeStamp = 0;
|
|
115
|
+
e.defaultPrevented = false;
|
|
116
|
+
e.isPropagationStopped = false;
|
|
117
|
+
e.isPersistent = false;
|
|
118
|
+
e.isDefaultPrevented = null;
|
|
119
|
+
e.preventDefault = null;
|
|
120
|
+
e.stopPropagation = null;
|
|
121
|
+
e.persist = null;
|
|
122
|
+
e.key = null;
|
|
123
|
+
e.code = null;
|
|
124
|
+
e.which = null;
|
|
125
|
+
e.value = undefined;
|
|
126
|
+
e.checked = undefined;
|
|
127
|
+
e.path = null;
|
|
128
|
+
|
|
129
|
+
if (_pool.length < MAX_POOL_SIZE) _pool.push(e);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Main wrapper factory
|
|
134
|
+
// fn: function(syntheticEvent) { ... }
|
|
135
|
+
// opts: { capture: bool, passive: bool, once: bool, stopPropagationOnTrueReturn: bool }
|
|
136
|
+
function wrapListener(fn, opts = {}) {
|
|
137
|
+
const { stopPropagationOnTrueReturn = false } = opts;
|
|
138
|
+
|
|
139
|
+
return function handler(nativeEvent) {
|
|
140
|
+
|
|
141
|
+
const currentTarget = this || nativeEvent.currentTarget || null;
|
|
142
|
+
const sEvent = createSyntheticEvent(nativeEvent, currentTarget);
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
const result = fn(sEvent);
|
|
146
|
+
|
|
147
|
+
// If user returns true and opted in, stop propagation
|
|
148
|
+
if (stopPropagationOnTrueReturn && result === true) {
|
|
149
|
+
sEvent.stopPropagation();
|
|
150
|
+
}
|
|
151
|
+
} finally {
|
|
152
|
+
// Release back to pool unless persisted
|
|
153
|
+
releaseSyntheticEvent(sEvent);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Convenience: add/remove wrapper-managed listener
|
|
159
|
+
function addWrappedEventListener(target, type, fn, options = {}) {
|
|
160
|
+
const handler = wrapListener(fn, options);
|
|
161
|
+
|
|
162
|
+
target.addEventListener(type, handler, options);
|
|
163
|
+
// Return a remover
|
|
164
|
+
return () => target.removeEventListener(type, handler, options);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Export (CommonJS / ES module-friendly)
|
|
168
|
+
const EventWrapper = {
|
|
169
|
+
wrapListener,
|
|
170
|
+
addWrappedEventListener,
|
|
171
|
+
createSyntheticEvent, // exported for advanced use
|
|
172
|
+
releaseSyntheticEvent
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export default EventWrapper;
|
package/src/hooks.jsx
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Lilact
|
|
4
|
+
Copyright (C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
BSD-2-Clause
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
* Redistributions of source code must retain the above copyright
|
|
13
|
+
notice, this list of conditions and the following disclaimer.
|
|
14
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
15
|
+
notice, this list of conditions and the following disclaimer in the
|
|
16
|
+
documentation and/or other materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
27
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
ʔ defineSymbols ( "LILACT", [ "CORE", "COMPONENT" ] ) ʔ
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook wrapper/primitive used to implement custom hooks.
|
|
36
|
+
* Can be used by the user, but not encouraged. Only use after you
|
|
37
|
+
* study how Lilact itself uses it.
|
|
38
|
+
*
|
|
39
|
+
* @returns {hook} Hook result.
|
|
40
|
+
*/
|
|
41
|
+
export function useHook()
|
|
42
|
+
{
|
|
43
|
+
const core = Lilact.current_component[0];
|
|
44
|
+
|
|
45
|
+
if(core.hooks[core.hook_index]===undefined) {
|
|
46
|
+
core.hooks.push({});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return core.hooks[core.hook_index++];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Adds state to a component using a React-like [value, setter] pattern.
|
|
54
|
+
*
|
|
55
|
+
* @param {any} initialValue - Initial state value.
|
|
56
|
+
* @returns {any} Hook result (implementation-dependent; typically `[state, setState]`).
|
|
57
|
+
*/
|
|
58
|
+
export function useState(val)
|
|
59
|
+
{
|
|
60
|
+
const hk = Lilact.useHook();
|
|
61
|
+
|
|
62
|
+
if( Lilact.isEmpty(hk) ) {
|
|
63
|
+
if(typeof(val)==='function') hk.value = val();
|
|
64
|
+
else hk.value = val;
|
|
65
|
+
|
|
66
|
+
hk.set_func = function(core, hk, val) {
|
|
67
|
+
if(typeof(val)==='function') hk.value = val(hk.value);
|
|
68
|
+
else hk.value = val;
|
|
69
|
+
|
|
70
|
+
core.component.forceUpdate();
|
|
71
|
+
}.bind(undefined, Lilact.current_component[0], hk);
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [hk.value, hk.set_func];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Returns a memoized callback function whose identity stays stable until dependencies change.
|
|
80
|
+
*
|
|
81
|
+
* @param {Function} callback - Function to memoize.
|
|
82
|
+
* @param {Array<any>} deps - Dependency list.
|
|
83
|
+
* @returns {Function} Memoized callback.
|
|
84
|
+
*/
|
|
85
|
+
export function useCallback(callback, deps=[{}])
|
|
86
|
+
{
|
|
87
|
+
const hk = Lilact.useHook();
|
|
88
|
+
|
|
89
|
+
if( Lilact.isEmpty(hk) ) {
|
|
90
|
+
hk.callback = callback;
|
|
91
|
+
hk.deps = deps;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
for(let d in deps) {
|
|
95
|
+
if( !Lilact.defaultIsEqual(deps[d],hk.deps[d]) ) {
|
|
96
|
+
hk.callback = callback;
|
|
97
|
+
|
|
98
|
+
hk.deps = deps;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return hk.callback;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a context object for sharing a value through the component tree.
|
|
109
|
+
*
|
|
110
|
+
* @param {any} [defaultValue] - Initial context value when no Provider is present.
|
|
111
|
+
* @returns {any} A context object (implementation-dependent).
|
|
112
|
+
*/
|
|
113
|
+
export function createContext(val)
|
|
114
|
+
{
|
|
115
|
+
const prov = function({value, children}) {
|
|
116
|
+
return children;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
default: val,
|
|
121
|
+
Provider: prov
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reads the current value from a context.
|
|
127
|
+
*
|
|
128
|
+
* @param {any} context - Context object created by `createContext`.
|
|
129
|
+
* @returns {any} Current context value.
|
|
130
|
+
*/
|
|
131
|
+
export function useContext(ctx)
|
|
132
|
+
{
|
|
133
|
+
let core = Lilact.current_component[0].parent;
|
|
134
|
+
|
|
135
|
+
while(core.entity!==ctx.Provider && core.parent) {
|
|
136
|
+
core = core.parent;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if(core.parent) {
|
|
140
|
+
let v = core.props?.value;
|
|
141
|
+
return v??=ctx.default;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return ctx.default;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns a stable, unique ID that is consistent across renders.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} [prefix] - Optional id prefix.
|
|
151
|
+
* @returns {string} Stable unique id.
|
|
152
|
+
*/
|
|
153
|
+
export function useId(prefix="N")
|
|
154
|
+
{
|
|
155
|
+
const hk = Lilact.useHook();
|
|
156
|
+
|
|
157
|
+
if( Lilact.isEmpty(hk) ) {
|
|
158
|
+
hk.id = prefix+Lilact.id_num++;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return hk.id;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Starts a transition and returns helpers for pending updates vs immediate ones.
|
|
166
|
+
*
|
|
167
|
+
* @returns {any} Transition API (implementation-dependent; typically `[isPending, startTransition]`).
|
|
168
|
+
*/
|
|
169
|
+
export function useTransition()
|
|
170
|
+
{
|
|
171
|
+
const hk = Lilact.useHook();
|
|
172
|
+
|
|
173
|
+
if( Lilact.isEmpty(hk) ) {
|
|
174
|
+
hk.count=0;
|
|
175
|
+
|
|
176
|
+
hk.func =
|
|
177
|
+
|
|
178
|
+
(async function(core, hk, fn) {
|
|
179
|
+
|
|
180
|
+
hk.count++;
|
|
181
|
+
|
|
182
|
+
if(hk.count===1) {
|
|
183
|
+
core.component.forceUpdate();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await fn();
|
|
187
|
+
|
|
188
|
+
hk.count--;
|
|
189
|
+
|
|
190
|
+
if(hk.count===0) {
|
|
191
|
+
core.component.forceUpdate();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
).bind(undefined, Lilact.current_component[0], hk);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return [ hk.count!=0, hk.func ];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Persists state in localStorage and keeps it in sync with the app.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} key - localStorage key.
|
|
205
|
+
* @param {any} initialValue - Initial value used if nothing exists in localStorage.
|
|
206
|
+
* @returns {any} Stored state/result.
|
|
207
|
+
*/
|
|
208
|
+
export function useLocalStorage(key, default_val)
|
|
209
|
+
{
|
|
210
|
+
|
|
211
|
+
const hk = Lilact.useHook();
|
|
212
|
+
let val;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
val = JSON.parse(localStorage[key]);
|
|
216
|
+
}
|
|
217
|
+
catch(e) {
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if(val===undefined) {
|
|
221
|
+
if(typeof(default_val)==='function') default_val = default_val();
|
|
222
|
+
val = default_val;
|
|
223
|
+
localStorage[key] = JSON.stringify(val);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if( Lilact.isEmpty(hk) ) {
|
|
227
|
+
hk.value = val;
|
|
228
|
+
hk.set_func = function(core, hk, val) {
|
|
229
|
+
|
|
230
|
+
if(typeof(val)==='function') val = val(hk.value);
|
|
231
|
+
|
|
232
|
+
if(val===hk.value) return;
|
|
233
|
+
|
|
234
|
+
localStorage[key] = JSON.stringify(val);
|
|
235
|
+
|
|
236
|
+
hk.value = val;
|
|
237
|
+
core.component.forceUpdate();
|
|
238
|
+
|
|
239
|
+
}.bind(undefined, Lilact.current_component[0], hk);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return [hk.value, hk.set_func];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Creates a mutable ref object with a stable identity across renders.
|
|
247
|
+
* The `.current` property holds the latest value.
|
|
248
|
+
*
|
|
249
|
+
* @param {any} initialValue - Initial ref value.
|
|
250
|
+
* @returns {Object} Ref object with `.current`.
|
|
251
|
+
*/
|
|
252
|
+
export function useRef(val = null)
|
|
253
|
+
{
|
|
254
|
+
const hk = Lilact.useHook();
|
|
255
|
+
|
|
256
|
+
if( Lilact.isEmpty(hk) ) {
|
|
257
|
+
hk.current = val;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return hk;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Runs an effect synchronously after all DOM mutations but before the browser paints.
|
|
265
|
+
*
|
|
266
|
+
* @param {Function} effect - Effect callback.
|
|
267
|
+
* @param {Array<any>} [deps] - Dependency list.
|
|
268
|
+
* @returns {void}
|
|
269
|
+
*/
|
|
270
|
+
export function useLayoutEffect(setup, deps=[{}])
|
|
271
|
+
{
|
|
272
|
+
if(typeof(deps)!=='object' || deps.constructor.name!=='Array') {
|
|
273
|
+
throw "effect dependencies must be an array or omitted.";
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const hk = Lilact.useHook();
|
|
277
|
+
|
|
278
|
+
if( !Lilact.isEmpty(hk) ) {
|
|
279
|
+
if(deps.every((v,i)=> v === hk.deps[i])) return;
|
|
280
|
+
if(hk.cleanup) {
|
|
281
|
+
hk.cleanup();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
hk.deps = deps;
|
|
286
|
+
Lilact.layout_effects.add( ()=>{ hk.cleanup = setup(); });
|
|
287
|
+
Lilact.current_component[0].component.forceUpdate();
|
|
288
|
+
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Runs a side effect after render commits.
|
|
293
|
+
*
|
|
294
|
+
* @param {Function} effect - Effect callback.
|
|
295
|
+
* @param {Array<any>} [deps] - Dependency list.
|
|
296
|
+
* @returns {void}
|
|
297
|
+
*/
|
|
298
|
+
export function useEffect(setup, deps=[{}])
|
|
299
|
+
{
|
|
300
|
+
if(typeof(deps)!=='object' || deps.constructor.name!=='Array') {
|
|
301
|
+
throw "effect dependencies must be an array or omitted.";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const hk = Lilact.useHook();
|
|
305
|
+
|
|
306
|
+
if( !Lilact.isEmpty(hk) ) {
|
|
307
|
+
if(deps.every((v,i)=> v === hk.deps[i])) return;
|
|
308
|
+
if(hk.cleanup) {
|
|
309
|
+
hk.cleanup();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
hk.deps = deps;
|
|
314
|
+
Lilact.setTimeout( ()=>{ hk.cleanup = setup(); }, 0 );
|
|
315
|
+
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Memoizes a computed value until dependencies change.
|
|
320
|
+
*
|
|
321
|
+
* @param {Function} factory - Function that creates the value.
|
|
322
|
+
* @param {Array<any>} deps - Dependency list.
|
|
323
|
+
* @returns {any} Memoized value.
|
|
324
|
+
*/
|
|
325
|
+
export function useMemo(fn,deps=[])
|
|
326
|
+
{
|
|
327
|
+
const hk = Lilact.useHook();
|
|
328
|
+
|
|
329
|
+
if( Lilact.isEmpty(hk) ) {
|
|
330
|
+
hk.deps = 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
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return hk.value;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* React-compatible hook for managing an async/queued action and deriving state from it.
|
|
348
|
+
*
|
|
349
|
+
* @param {Function} action - The async/queued action function.
|
|
350
|
+
* @param {any} initialState - Initial state for the hook.
|
|
351
|
+
* @returns {any} Hook result (implementation-dependent).
|
|
352
|
+
*/
|
|
353
|
+
export function useActionState(fn, initial_state)
|
|
354
|
+
{
|
|
355
|
+
const hk = Lilact.useHook();
|
|
356
|
+
const [is_pending, tran_start_func] = Lilact.useTransition();
|
|
357
|
+
|
|
358
|
+
if( Lilact.isEmpty(hk) ) {
|
|
359
|
+
|
|
360
|
+
hk.state = initial_state;
|
|
361
|
+
|
|
362
|
+
hk.form_action = (sub)=>{
|
|
363
|
+
event.preventDefault();
|
|
364
|
+
|
|
365
|
+
tran_start_func(
|
|
366
|
+
async ()=> {
|
|
367
|
+
const form_data = new FormData(sub.target, sub.submitter);
|
|
368
|
+
hk.state = await fn(hk.state, form_data);
|
|
369
|
+
},
|
|
370
|
+
[]
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return [hk.state, hk.form_action, is_pending];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Manages state via a reducer function and exposes dispatch to trigger state transitions.
|
|
382
|
+
*
|
|
383
|
+
* @param {Function} reducer - Reducer function.
|
|
384
|
+
* @param {any} initialArg - Initial state value (or initializer arg).
|
|
385
|
+
* @param {Function} [init] - Optional initializer function for lazy initial state.
|
|
386
|
+
* @returns {any} Hook result (implementation-dependent; typically `[state, dispatch]`).
|
|
387
|
+
*/
|
|
388
|
+
export function useReducer(reducer, initialArg, init)
|
|
389
|
+
{
|
|
390
|
+
const hk = Lilact.useHook();
|
|
391
|
+
|
|
392
|
+
if( Lilact.isEmpty(hk) ) {
|
|
393
|
+
hk.reducer = reducer;
|
|
394
|
+
hk.state = init?init(initialArg):initialArg;
|
|
395
|
+
hk.dispatch = function(core, hk, action) {
|
|
396
|
+
const newst = hk.reducer(hk.state, action);
|
|
397
|
+
if(!Lilact.defaultIsEqual(newst,hk.state)) {
|
|
398
|
+
hk.state = newst;
|
|
399
|
+
core.component.forceUpdate();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
}.bind(undefined, Lilact.current_component[0], hk);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return [hk.state, hk.dispatch];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
// todo: very simple implementation. not lilactish! must be modified
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Returns a deferred value that updates later than the provided input.
|
|
414
|
+
*
|
|
415
|
+
* @param {any} value - Value to defer.
|
|
416
|
+
* @returns {any} Deferred value.
|
|
417
|
+
*/
|
|
418
|
+
export function useDeferredValue(value, initialValue)
|
|
419
|
+
{
|
|
420
|
+
|
|
421
|
+
const { useEffect, useRef, useState } = Lilact;
|
|
422
|
+
|
|
423
|
+
const [deferred, setDeferred] = useState(
|
|
424
|
+
typeof initialValue !== "undefined" ? initialValue : value
|
|
425
|
+
);
|
|
426
|
+
const lastValueRef = useRef(value);
|
|
427
|
+
const pendingRef = useRef(null);
|
|
428
|
+
|
|
429
|
+
useEffect(() => {
|
|
430
|
+
if (lastValueRef.current === value) return;
|
|
431
|
+
lastValueRef.current = value;
|
|
432
|
+
|
|
433
|
+
// Cancel any previously scheduled update
|
|
434
|
+
if (pendingRef.current != null) {
|
|
435
|
+
pendingRef.current.cancelled = true;
|
|
436
|
+
pendingRef.current = null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Schedule update asynchronously using a microtask (Promise.resolve)
|
|
440
|
+
const job = { cancelled: false };
|
|
441
|
+
pendingRef.current = job;
|
|
442
|
+
|
|
443
|
+
Promise.resolve().then(() => {
|
|
444
|
+
if (job.cancelled) return;
|
|
445
|
+
// Batch state update into next event loop tick so current render finishes first.
|
|
446
|
+
setDeferred(value);
|
|
447
|
+
pendingRef.current = null;
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
return () => {
|
|
451
|
+
if (pendingRef.current) {
|
|
452
|
+
pendingRef.current.cancelled = true;
|
|
453
|
+
pendingRef.current = null;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}, [value]);
|
|
457
|
+
|
|
458
|
+
return deferred;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|
|
3
|
+
Lilact
|
|
4
|
+
Copyright (C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
BSD-2-Clause
|
|
8
|
+
|
|
9
|
+
Redistribution and use in source and binary forms, with or without
|
|
10
|
+
modification, are permitted provided that the following conditions are met:
|
|
11
|
+
|
|
12
|
+
* Redistributions of source code must retain the above copyright
|
|
13
|
+
notice, this list of conditions and the following disclaimer.
|
|
14
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
15
|
+
notice, this list of conditions and the following disclaimer in the
|
|
16
|
+
documentation and/or other materials provided with the distribution.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
|
22
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
25
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
27
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const TRANSPILER_OUTPUT = Symbol.for("LILACT:TRANSPILER_OUTPUT");
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export function defineSymbols(ns, defs) {
|
|
35
|
+
|
|
36
|
+
if(defs===undefined) {
|
|
37
|
+
defs=ns;
|
|
38
|
+
ns='';
|
|
39
|
+
}
|
|
40
|
+
else ns+=':';
|
|
41
|
+
|
|
42
|
+
const out = [];
|
|
43
|
+
const lbls = [];
|
|
44
|
+
if(defs instanceof Array) {
|
|
45
|
+
if(ns!=='') {
|
|
46
|
+
if(typeof(ns)!=='string') ns='';
|
|
47
|
+
for(let d of defs) {
|
|
48
|
+
lbls.push(d);
|
|
49
|
+
out.push(`Symbol.for('${ns}${d}')`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
for(const d of defs) {
|
|
54
|
+
lbls.push(d);
|
|
55
|
+
out.push(`Symbol()`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return `const [${lbls.join(',')}]=[${out.join(',')}];`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const quote = (x)=>`"${String(x).replace(/(["\n])/g, '\\$1')}"`;
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
// note: this directive is used for files that can be used separatly (i.e. timers.jsx)
|
|
66
|
+
// or in case you want to check the preprocessors output.
|
|
67
|
+
|
|
68
|
+
export function outputJS(filepath) {
|
|
69
|
+
globalThis[TRANSPILER_OUTPUT] = filepath;
|
|
70
|
+
}
|