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/run.jsx
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
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
|
+
import Lilact from './lilact.jsx';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Runs a jsx script. All scripts can access Lilact namespace as a global object.
|
|
37
|
+
*
|
|
38
|
+
* Notice that `run` uses eval internally. There is no standard way of getting the error location when
|
|
39
|
+
* running it with eval. Lilact has its own experimental workarounds for this, but generally speaking it
|
|
40
|
+
* works better when an script is loaded from a file (using <script type='text/jsx' src=''.../> and is not
|
|
41
|
+
* inline, because that way the generated sourcemap will be used by the browser and the exceptions will be
|
|
42
|
+
* located automatically. When an inline script raises exception, the original exception does not report
|
|
43
|
+
* which eval has caused the error, so Lilact should guess it from some labels, and for the sake of
|
|
44
|
+
* efficiency, it is block based and not exact.
|
|
45
|
+
*
|
|
46
|
+
* @param jsx - The code to run.
|
|
47
|
+
* @param path - The optional path to be used in reporting errors.
|
|
48
|
+
* @param is_inline - To treat the code as inline. The main difference at the moment is that inline code doesn't include sourcemap.
|
|
49
|
+
*
|
|
50
|
+
* @returns An array representation of the children.
|
|
51
|
+
*/
|
|
52
|
+
export function run(jsx, path=`<string input ${++Lilact.eval_num}>`, is_inline=true)
|
|
53
|
+
{
|
|
54
|
+
if(Lilact.checkTraceErrors) Lilact.checkTraceErrors;
|
|
55
|
+
|
|
56
|
+
const mappings = [];
|
|
57
|
+
|
|
58
|
+
let processed;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
processed = Lilact.transpileJSX( jsx,
|
|
62
|
+
{
|
|
63
|
+
path,
|
|
64
|
+
mappings,
|
|
65
|
+
factory: "Lilact.createComponent",
|
|
66
|
+
append_sourcemap: !is_inline,
|
|
67
|
+
wrap_all: false
|
|
68
|
+
} );
|
|
69
|
+
}
|
|
70
|
+
catch(e) {
|
|
71
|
+
e.lilact_trace = path;
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// the separation of module property is to create the required object even on error
|
|
76
|
+
|
|
77
|
+
const module = {};
|
|
78
|
+
|
|
79
|
+
Lilact.transpilerConfig.required[path] = {
|
|
80
|
+
mappings,
|
|
81
|
+
processed,
|
|
82
|
+
module,
|
|
83
|
+
is_inline,
|
|
84
|
+
path,
|
|
85
|
+
code: jsx
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Lilact.scanFunctionLabels(processed, path);
|
|
90
|
+
|
|
91
|
+
Lilact.transpilerConfig.func_labels[path] = {
|
|
92
|
+
path,
|
|
93
|
+
row: 0,
|
|
94
|
+
col: 0,
|
|
95
|
+
label: "<EXEC>",
|
|
96
|
+
required: Lilact.transpilerConfig.required[path]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
globalThis.Lilact = Lilact;
|
|
101
|
+
|
|
102
|
+
const res = eval ( processed );
|
|
103
|
+
if(module.exports) return module.exports;
|
|
104
|
+
return res;
|
|
105
|
+
}
|
|
106
|
+
catch(e) {
|
|
107
|
+
e.lilact_trace = path;
|
|
108
|
+
throw e;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// export async function importAsync(path)
|
|
113
|
+
// {
|
|
114
|
+
// const res = await fetch(path);
|
|
115
|
+
// const text = await res.text();
|
|
116
|
+
|
|
117
|
+
// return Lilact.run(text, path, false);
|
|
118
|
+
// }
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Loads a jsx script from a path. All scripts can access Lilact namespace as a global object.
|
|
122
|
+
*
|
|
123
|
+
* Lilact require loads synchronously, as it is expected to be loaded on the next instruction.
|
|
124
|
+
* It is better to load existing scripts using <script type="text/jsx" src+"..."/> if possible.
|
|
125
|
+
*
|
|
126
|
+
* Notice that run uses eval internally. There is no standard way of getting the error location when
|
|
127
|
+
* running it with eval. Lilact has its own experimental workarounds for this, but generally speaking it
|
|
128
|
+
* works better when an script is loaded from a file (using <script type='text/jsx' src=''.../> and is not
|
|
129
|
+
* inline, because that way the generated sourcemap will be used by the browser and the exceptions will be
|
|
130
|
+
* located automatically. When an inline script raises exception, the original exception does not report
|
|
131
|
+
* which eval has caused the error, so Lilact should guess it from some labels, and for the sake of
|
|
132
|
+
* efficiency, it is block based and not exact.
|
|
133
|
+
*
|
|
134
|
+
* @param path - The path to the required file.
|
|
135
|
+
* @param force_update - To treat the code as inline. The main difference at the moment is that inline code doesn't include sourcemap.
|
|
136
|
+
*
|
|
137
|
+
* @returns An array representation of the children.
|
|
138
|
+
*/
|
|
139
|
+
export function require(path, force_update)
|
|
140
|
+
{
|
|
141
|
+
if(Lilact.transpilerConfig.required[path]!==undefined && !force_update) return Lilact.transpilerConfig.required[path].module;
|
|
142
|
+
|
|
143
|
+
if(path[0]==='#') {
|
|
144
|
+
|
|
145
|
+
const el = document.querySelector(path);
|
|
146
|
+
|
|
147
|
+
if(el) {
|
|
148
|
+
return Lilact.run(el.innerText, path);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
|
|
154
|
+
// note: this makes a sync request. this is not advised,
|
|
155
|
+
// but import should be sync. for an async solution use importAsync.
|
|
156
|
+
|
|
157
|
+
const request = new XMLHttpRequest();
|
|
158
|
+
request.open("GET", path, false);
|
|
159
|
+
request.send(null);
|
|
160
|
+
|
|
161
|
+
if (request.status === 200) {
|
|
162
|
+
return Lilact.run(request.responseText, path, false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
throw `required resource not found (${path})`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Debug tool to get the Lilact traced location of an error. It can also produce some block based stack trace
|
|
172
|
+
* if `Lilact.transpilerConfig.enableLabelStack` is set to `true` before loading the script. This is `false`
|
|
173
|
+
* by default for efficiency.
|
|
174
|
+
*
|
|
175
|
+
* @returns A new object that includes `path`, `row`, `col`, `msg`, `name`, and optional `stack`.
|
|
176
|
+
* The exception itself is stored as `err`.
|
|
177
|
+
*/
|
|
178
|
+
export function traceError(err)
|
|
179
|
+
{
|
|
180
|
+
const loc = Lilact.getErrorLocation(err);
|
|
181
|
+
|
|
182
|
+
const obj = {
|
|
183
|
+
path: err.fileName,
|
|
184
|
+
label: null,
|
|
185
|
+
|
|
186
|
+
row: loc[0],
|
|
187
|
+
col: loc[1],
|
|
188
|
+
|
|
189
|
+
msg: err.message,
|
|
190
|
+
name: err.name,
|
|
191
|
+
|
|
192
|
+
stack: null,
|
|
193
|
+
err: err
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if(err.lilact_trace!==undefined) {
|
|
198
|
+
|
|
199
|
+
// to be able to trace, we assume that all of the scripts are running inside lilact.
|
|
200
|
+
// if not, the error is returned unchanged and stack and label would remain null.
|
|
201
|
+
|
|
202
|
+
let mps;
|
|
203
|
+
let blk;
|
|
204
|
+
if(typeof(err.lilact_trace)==='string') {
|
|
205
|
+
blk = Lilact.func_labels[err.lilact_trace];
|
|
206
|
+
mps = blk.required.mappings;
|
|
207
|
+
}
|
|
208
|
+
else if(typeof(err.lilact_trace)==='object') {
|
|
209
|
+
blk = Lilact.func_labels[err.lilact_trace[0]];
|
|
210
|
+
mps = blk.mappings;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
obj.path = blk.path;
|
|
214
|
+
obj.label = blk.label;
|
|
215
|
+
|
|
216
|
+
[obj.row, obj.col] = Lilact.mapLocation(mps, obj.row,obj.col);
|
|
217
|
+
|
|
218
|
+
//const rm = Lilact.func_labels[func_num].required;
|
|
219
|
+
|
|
220
|
+
// Lilact.onError( err.message,
|
|
221
|
+
// rm.path,
|
|
222
|
+
// Lilact.func_labels[func_num].label,
|
|
223
|
+
// ...Lilact.mapLocation(...loc,mps),
|
|
224
|
+
// Lilact.call_stack.map(
|
|
225
|
+
// (x)=>({ path: Lilact.func_labels[x].path,
|
|
226
|
+
// label: Lilact.func_labels[x].label,
|
|
227
|
+
// row: Lilact.func_labels[x].row
|
|
228
|
+
// })
|
|
229
|
+
// ), err );
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return obj;
|
|
235
|
+
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function scanScriptTagsWithType() {
|
|
239
|
+
const scripts = Array.from(
|
|
240
|
+
document.querySelectorAll('script[type="text/jsx"]')
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return scripts.map((el) => ({
|
|
244
|
+
src: el.getAttribute("src") ?? null,
|
|
245
|
+
content: el.textContent ?? ""
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// usage (in browser):
|
|
250
|
+
// const result = scanScriptTagsWithType();
|
|
251
|
+
// console.log(result);
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Scans the whole documents and runs all the script elements with type `text/jsx`.
|
|
256
|
+
* It is automatically attached to document.onload when Lilact is loaded.
|
|
257
|
+
*
|
|
258
|
+
* If element src is set, it will be loaded via `Lilact.require`.
|
|
259
|
+
* If element has inner content, it will be executed via `Lilact.run`.
|
|
260
|
+
*
|
|
261
|
+
* If both are present, first the src is loaded and then the inner content is executed.
|
|
262
|
+
*
|
|
263
|
+
* Note that it won't detect such elements that are added after document.onload.
|
|
264
|
+
* @returns {void}
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
export function runScripts()
|
|
268
|
+
{
|
|
269
|
+
const scripts = scanScriptTagsWithType();
|
|
270
|
+
|
|
271
|
+
for(const s of scripts) {
|
|
272
|
+
if(s.src) Lilact.require(s.src);
|
|
273
|
+
if(s.content) Lilact.run(s.content);
|
|
274
|
+
}
|
|
275
|
+
}
|
package/src/timers.jsx
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
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
|
+
// note: lilact timers can be used without lilact. uncomment the directive below to
|
|
32
|
+
// get the transpiled version in dist directory.
|
|
33
|
+
//ʔ outputJS('./dist/timers.js');
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
ʔ defineSymbols("LILACT:TIMERS", ["CORE", "IDX", "DUE", "REPEAT", "CLEARED", "INTERVAL", "CALLBACK", "ARGS"] ) ʔ
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Timer helpers for a promise-friendly timer framework.
|
|
40
|
+
*
|
|
41
|
+
* These functions keep the same call signatures as the standard JavaScript timer APIs where applicable (`setTimeout`/`setInterval`/`clearTimeout`/`clearInterval`), but add extra capabilities for promise-friendly control and lifecycle management. This module can “grab” timers and later pause/resume/reset/release them, plus provide promise wrappers like `timeoutPromise` and `animationFramePromise`.
|
|
42
|
+
*
|
|
43
|
+
* - `setTimeout` / `setInterval`: schedule callbacks (same interface as the built-ins).
|
|
44
|
+
* - `clearTimeout` / `clearInterval`: cancel scheduled timers (same interface as the built-ins).
|
|
45
|
+
* - `grabTimers` / `pauseTimers` / `resumeTimers` / `resetTimers` / `releaseTimers`: manage tracked timers (implementation-dependent behavior).
|
|
46
|
+
* - `timeoutPromise` / `animationFramePromise`: promise-based convenience wrappers.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
let timer_pause_time = undefined;
|
|
51
|
+
let current_timer_idx = -1;
|
|
52
|
+
let timer_list = [];
|
|
53
|
+
let timer_timeout = -1;
|
|
54
|
+
let all_timers = {};
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
// original functions
|
|
58
|
+
const _setTimeout = window.setTimeout,
|
|
59
|
+
_setInterval = window.setInterval,
|
|
60
|
+
_clearTimeout = window.clearTimeout,
|
|
61
|
+
_clearInterval = window.clearInterval;
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
function get_bucket(target)
|
|
65
|
+
{
|
|
66
|
+
let left = 0;
|
|
67
|
+
let right = timer_list.length - 1;
|
|
68
|
+
|
|
69
|
+
while (left <= right) {
|
|
70
|
+
const mid = Math.floor((left + right) / 2);
|
|
71
|
+
const mid_val = timer_list[mid][DUE];
|
|
72
|
+
|
|
73
|
+
if (mid_val === target) {
|
|
74
|
+
return [mid, timer_list[mid]];
|
|
75
|
+
}
|
|
76
|
+
else if (mid_val < target) {
|
|
77
|
+
left = mid + 1;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
right = mid - 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const bucket = [];
|
|
85
|
+
bucket[DUE] = target;
|
|
86
|
+
|
|
87
|
+
timer_list.splice(left, 0, bucket);
|
|
88
|
+
return [left, bucket];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function add_timer(t, is_repeat=false)
|
|
92
|
+
{
|
|
93
|
+
const [i,bucket] = get_bucket(t[DUE]);
|
|
94
|
+
|
|
95
|
+
if(!is_repeat) {
|
|
96
|
+
current_timer_idx++;
|
|
97
|
+
all_timers[current_timer_idx]=t;
|
|
98
|
+
t[IDX] = current_timer_idx;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
bucket.push(t);
|
|
102
|
+
|
|
103
|
+
if(timer_list[0][0]===t) {
|
|
104
|
+
_clearTimeout( timer_timeout );
|
|
105
|
+
timer_timeout = _setTimeout( run_timer, t[INTERVAL] );
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return current_timer_idx;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function run_timer()
|
|
112
|
+
{
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
|
|
115
|
+
let i = 0;
|
|
116
|
+
let buck = timer_list[i];
|
|
117
|
+
|
|
118
|
+
while( buck && buck[DUE]-now <= 0 ) {
|
|
119
|
+
for(const t of buck) {
|
|
120
|
+
|
|
121
|
+
if(!t[CLEARED]) {
|
|
122
|
+
t[CALLBACK](...t[ARGS]);
|
|
123
|
+
if(t[REPEAT]) {
|
|
124
|
+
t[DUE] = Date.now()+t[INTERVAL];
|
|
125
|
+
add_timer(t, true);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
delete all_timers[t[IDX]];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
delete all_timers[t[IDX]];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
i++;
|
|
136
|
+
buck = timer_list[i];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
timer_list.splice(0,i);
|
|
140
|
+
|
|
141
|
+
if(timer_list.length>0) {
|
|
142
|
+
_clearTimeout( timer_timeout );
|
|
143
|
+
timer_timeout = _setTimeout( run_timer, timer_list[0][DUE] - now);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//---
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Resets managed timers back to their initial scheduled state.
|
|
152
|
+
* @returns {void}
|
|
153
|
+
*/
|
|
154
|
+
export function resetTimers()
|
|
155
|
+
{
|
|
156
|
+
_clearTimeout(timer_timeout);
|
|
157
|
+
timer_pause_time = undefined;
|
|
158
|
+
current_timer_idx = -1;
|
|
159
|
+
timer_list = [];
|
|
160
|
+
timer_timeout = -1;
|
|
161
|
+
all_timers = {};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Pauses all grabbed timers.
|
|
166
|
+
* @returns {void}
|
|
167
|
+
*/
|
|
168
|
+
export function pauseTimers()
|
|
169
|
+
{
|
|
170
|
+
_clearTimeout( timer_timeout );
|
|
171
|
+
timer_pause_time = Date.now();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Resumes paused timers.
|
|
176
|
+
* @returns {void}
|
|
177
|
+
*/
|
|
178
|
+
export function resumeTimers()
|
|
179
|
+
{
|
|
180
|
+
if(!timer_pause_time) return;
|
|
181
|
+
|
|
182
|
+
if(timer_list.length>0) {
|
|
183
|
+
const now = Date.now();
|
|
184
|
+
|
|
185
|
+
timer_pause_time -= now;
|
|
186
|
+
|
|
187
|
+
for( const t of timer_list ) {
|
|
188
|
+
t[DUE] -= timer_pause_time;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
timer_timeout = _setTimeout( run_timer, timer_list[0][DUE] - now);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
timer_pause_time = undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Creates a timeout timer (same interface as JS `setTimeout`).
|
|
200
|
+
* @param {Function} callback - Function to run after the delay.
|
|
201
|
+
* @param {number} delay - Delay in milliseconds.
|
|
202
|
+
* @param {...any} [args] - Optional arguments passed to `callback`.
|
|
203
|
+
* @returns {any} Timeout id.
|
|
204
|
+
*/
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
export function setTimeout(func, interval, ...args)
|
|
208
|
+
{
|
|
209
|
+
return add_timer( { [CALLBACK]: func, [INTERVAL]: interval, [DUE]: Date.now()+interval, [REPEAT]: false, [ARGS]: args } );
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Creates an interval timer (same interface as JS `setInterval`).
|
|
214
|
+
* @param {Function} callback - Function to run repeatedly.
|
|
215
|
+
* @param {number} interval - Delay in milliseconds between executions.
|
|
216
|
+
* @param {...any} [args] - Optional arguments passed to `callback`.
|
|
217
|
+
* @returns {any} Interval id.
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
export function setInterval(func, interval, ...args)
|
|
221
|
+
{
|
|
222
|
+
return add_timer( { [CALLBACK]: func, [INTERVAL]: interval, [DUE]: Date.now()+interval, [REPEAT]: true, [ARGS]: args } );
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Clears a timeout created via this framework’s `setTimeout`.
|
|
227
|
+
* @param {any} id - Timeout id returned by `setTimeout`.
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
export function clearTimeout(t)
|
|
231
|
+
{
|
|
232
|
+
if(all_timers[t]) all_timers[t][CLEARED] = true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Clears an interval created via this framework’s `setInterval`.
|
|
237
|
+
* @param {any} id - Interval id returned by `setInterval`.
|
|
238
|
+
* @returns {void}
|
|
239
|
+
*/
|
|
240
|
+
export function clearInterval(t)
|
|
241
|
+
{
|
|
242
|
+
if(all_timers[t]) all_timers[t][CLEARED] = true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Captures/associates all timers with the framework so they can be managed. Calling this will
|
|
247
|
+
* shadow the global setTimeout and setInterval functions and channel them through Lilact.
|
|
248
|
+
* @returns {void}
|
|
249
|
+
*/
|
|
250
|
+
export function grabTimers()
|
|
251
|
+
{
|
|
252
|
+
globalThis.setTimeout = this.setTimeout;
|
|
253
|
+
globalThis.setInterval = this.setInterval;
|
|
254
|
+
globalThis.clearTimeout = this.clearTimeout;
|
|
255
|
+
globalThis.clearInterval = this.clearInterval;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Releases timers from framework control.
|
|
260
|
+
* @returns {void}
|
|
261
|
+
*/
|
|
262
|
+
export function releaseTimers()
|
|
263
|
+
{
|
|
264
|
+
globalThis.setTimeout = _setTimeout;
|
|
265
|
+
globalThis.setInterval = _setInterval;
|
|
266
|
+
globalThis.clearTimeout = _clearTimeout;
|
|
267
|
+
globalThis.clearInterval = _clearInterval;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Returns a Promise that resolves after a timeout (framework-managed) using the same delay semantics as `setTimeout`.
|
|
272
|
+
* @param {number} duration - Delay in milliseconds.
|
|
273
|
+
* @returns {Promise} Promise that resolves after the delay.
|
|
274
|
+
*/
|
|
275
|
+
export function timeoutPromise(duration=0, timer_source=this)
|
|
276
|
+
{
|
|
277
|
+
let id, resolve, reject;
|
|
278
|
+
|
|
279
|
+
const promise = new Promise((res, rej) => {
|
|
280
|
+
resolve = res;
|
|
281
|
+
reject = rej;
|
|
282
|
+
id = timer_source.setTimeout(() => {
|
|
283
|
+
resolve();
|
|
284
|
+
}, duration);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// note: proceed interrupts the timer, and continues the flow if used with await.
|
|
288
|
+
promise.proceed = () => {
|
|
289
|
+
timer_source.clearTimeout(id);
|
|
290
|
+
resolve();
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// note: cancel rejects so it throws exception when using with await, this allows handling it differently.
|
|
294
|
+
promise.cancel = () => {
|
|
295
|
+
timer_source.clearTimeout(id);
|
|
296
|
+
reject();
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
return promise;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Schedules a callback on the next animation frame and returns a Promise that resolves on that frame.
|
|
304
|
+
* @returns {Promise} Promise that resolves when the animation frame runs.
|
|
305
|
+
*/
|
|
306
|
+
export function animationFramePromise() {
|
|
307
|
+
return new Promise((resolve) => {
|
|
308
|
+
requestAnimationFrame(() => {
|
|
309
|
+
resolve();
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|