lilact 0.26.15 → 0.27.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.
Files changed (61) hide show
  1. package/dist/lilact.development.js +459 -324
  2. package/dist/lilact.development.js.map +3 -3
  3. package/dist/lilact.development.min.js +63 -54
  4. package/dist/lilact.development.min.js.map +3 -3
  5. package/dist/lilact.production.min.js +63 -54
  6. package/docs/assets/navigation.js +1 -1
  7. package/docs/assets/search.js +1 -1
  8. package/docs/classes/accessories.ErrorBoundary.html +8 -8
  9. package/docs/classes/accessories.Suspense.html +7 -7
  10. package/docs/classes/components.Component.html +11 -11
  11. package/docs/classes/components.HTMLComponent.html +11 -11
  12. package/docs/classes/components.RootComponent.html +11 -11
  13. package/docs/functions/components.cloneComponent.html +1 -1
  14. package/docs/functions/components.createComponent.html +1 -1
  15. package/docs/functions/components.createPortal.html +1 -1
  16. package/docs/functions/components.createRoot.html +1 -1
  17. package/docs/functions/components.memo.html +1 -1
  18. package/docs/functions/components.render.html +1 -1
  19. package/docs/functions/errors.globalErrorHandler.html +1 -8
  20. package/docs/functions/errors.scanBlockLabels.html +1 -0
  21. package/docs/functions/errors.traceError.html +1 -7
  22. package/docs/functions/hooks.startTransition.html +1 -1
  23. package/docs/functions/hooks.useActionState.html +1 -1
  24. package/docs/functions/hooks.useDebugValue.html +1 -1
  25. package/docs/functions/hooks.useDeferredValue.html +1 -1
  26. package/docs/functions/hooks.useEffect.html +1 -1
  27. package/docs/functions/hooks.useImperativeHandle.html +1 -1
  28. package/docs/functions/hooks.useInsertionEffect.html +1 -1
  29. package/docs/functions/hooks.useMemo.html +1 -1
  30. package/docs/functions/hooks.useReducer.html +1 -1
  31. package/docs/functions/run.lazy.html +1 -13
  32. package/docs/functions/run.require.html +1 -9
  33. package/docs/functions/run.run.html +1 -5
  34. package/docs/functions/run.runScripts.html +1 -7
  35. package/docs/functions/timers.timeoutPromise.html +3 -3
  36. package/docs/modules/errors.html +1 -1
  37. package/docs/modules/run.html +1 -1
  38. package/docs/static/index.html +1 -1
  39. package/docs/static/lilact.development.js +459 -324
  40. package/docs/static/lilact.development.js.map +3 -3
  41. package/docs/static/lilact.development.min.js +63 -54
  42. package/docs/static/lilact.development.min.js.map +3 -3
  43. package/docs/static/lilact.production.min.js +63 -54
  44. package/docs/variables/components.cloneElement.html +1 -1
  45. package/docs/variables/errors.blocks_info.html +1 -0
  46. package/docs/variables/errors.error.html +1 -0
  47. package/docs/variables/run.required_scripts.html +1 -0
  48. package/examples/index.html +1 -1
  49. package/examples/lilact.development.js +459 -324
  50. package/examples/lilact.development.js.map +3 -3
  51. package/examples/lilact.development.min.js +63 -54
  52. package/examples/lilact.development.min.js.map +3 -3
  53. package/examples/lilact.production.min.js +63 -54
  54. package/package.json +1 -1
  55. package/src/components.jsx +10 -4
  56. package/src/errors.jsx +344 -245
  57. package/src/expscan.js +1 -1
  58. package/src/hooks.jsx +8 -5
  59. package/src/jsx.js +16 -16
  60. package/src/lilact.jsx +2 -2
  61. package/src/run.jsx +313 -267
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lilact",
3
- "version": "0.26.15",
3
+ "version": "0.27.0",
4
4
  "description": "A Little React/JSX Runtime Implementation for Browser",
5
5
  "repository": "github:arashkazemi/lilact",
6
6
  "homepage": "https://arashkazemi.github.io/lilact/",
@@ -371,7 +371,7 @@ if(DEBUG) {
371
371
  if(this.outlet!==undefined) {
372
372
  for(let c of this.outlet) {
373
373
  if(c.cleanup) {
374
- c.cleanup();
374
+ promises.push(c.cleanup());
375
375
  }
376
376
  }
377
377
  }
@@ -379,7 +379,7 @@ if(DEBUG) {
379
379
  if(this.props?.children!==undefined) {
380
380
  for(let c of this.props.children) {
381
381
  if(c.cleanup) {
382
- c.cleanup();
382
+ promises.push(c.cleanup());
383
383
  }
384
384
  }
385
385
  }
@@ -387,10 +387,12 @@ if(DEBUG) {
387
387
  if(this.hooks!==undefined) {
388
388
  for(let h of this.hooks) {
389
389
  if(h.cleanup) {
390
- h.cleanup();
390
+ promises.push(h.cleanup());
391
391
  }
392
392
  }
393
393
  }
394
+
395
+ await Promise.all(promises);
394
396
  }
395
397
 
396
398
  catch(e) {
@@ -400,6 +402,9 @@ if(DEBUG) {
400
402
 
401
403
  updateElementProps(patch, force=false)
402
404
  {
405
+ // note: string.slice(0,undefined) returns the complete string. that's why
406
+ // the slices are correct even if maxLength is not set.
407
+
403
408
  if(this.entity==="input") {
404
409
  if(!patch?.type) patch.type = 'text';
405
410
  if(patch.type!==this.element.type) {
@@ -432,7 +437,7 @@ if(DEBUG) {
432
437
  this.event_detachers[al]();
433
438
  }
434
439
  else {
435
- this.element.setAttribute(a, undefined);
440
+ this.element.removeAttribute(a);
436
441
  }
437
442
  }
438
443
  }
@@ -485,6 +490,7 @@ if(DEBUG) {
485
490
  }
486
491
  else if(boolean_html_attributes_set.has(a)) { // not lower cased(al), as it is set as a js property
487
492
  this.element[a] = toBool(patch[a]);
493
+ if(!this.element[a]) this.element.removeAttribute(a);
488
494
  }
489
495
  else if(a==='autoFocus') { // not lower cased(al), as it is set as a js property
490
496
  this.element['autofocus'] = toBool(patch[a]);
package/src/errors.jsx CHANGED
@@ -27,277 +27,376 @@
27
27
  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
28
 
29
29
  */
30
- import Lilact from './lilact.jsx';
30
+ /*
31
+ Lilact
32
+ Copyright (C) 2024-2026 Arash Kazemi <contact.arash.kazemi@gmail.com>
33
+ BSD-2-Clause
34
+ */
31
35
 
32
- import { required_scripts } from './run.jsx'
33
- import { pauseTimers } from './timers.jsx'
36
+ import Lilact from "./lilact.jsx";
37
+ import { required_scripts } from "./run.jsx";
34
38
  import { css } from "@emotion/css";
35
39
 
36
- // Lilact API
40
+ function number(value) {
41
+ const result = Number(value);
42
+ return Number.isFinite(result) ? result : null;
43
+ }
44
+
45
+ function asError(value) {
46
+ if (value instanceof Error) return value;
47
+ if (value?.error instanceof Error) return value.error;
48
+
49
+ const error = new Error(
50
+ value?.message == null
51
+ ? String(value)
52
+ : String(value.message)
53
+ );
37
54
 
38
- function getErrorLocation(err) // works for both error and error-event, and also in node env
39
- {
40
- if(err.lineno!==undefined || err.line!==undefined || err.lineNumber!==undefined) {
41
- const l = err.lineNumber || err.lineno || err.line;
42
- const c = err.columnNumber || err.colno || err.column;
55
+ if (value && typeof value === "object") {
56
+ if (value.name) error.name = value.name;
57
+ if (value.stack) error.stack = value.stack;
43
58
 
44
- return {line: l, col: c};
45
- }
59
+ for (const key of Object.keys(value)) {
60
+ if (!(key in error)) error[key] = value[key];
61
+ }
62
+ }
46
63
 
47
- let match = /:(\d+):(\d+)[\n].*/m.exec(err.stack);
48
- if(match===null) {
49
- match = /:(\d+):(\d+)\)\s+at .*/m.exec(err.stack);
50
- }
64
+ return error;
65
+ }
66
+
67
+ function isParserError(error) {
68
+ return error?.name === "JSXParserError";
69
+ }
70
+
71
+ function stackLocation(stack) {
72
+ if (typeof stack !== "string") return null;
73
+
74
+ for (const line of stack.split(/\r?\n/)) {
75
+ const match = line.match(/(eval:\/.*):(\d+):(\d+)/);
76
+
77
+ if (!match) continue;
78
+
79
+ return {
80
+ path: match[1].replace(/^eval:\/+/, ""),
81
+ line: Number(match[2]) - 1,
82
+ column: Number(match[3]) - 1,
83
+ };
84
+ }
85
+
86
+ return null;
87
+ }
88
+
89
+ function browserLocation(error) {
90
+ const line = number(
91
+ error?.lineNumber ??
92
+ error?.lineno ??
93
+ error?.line
94
+ );
95
+
96
+ const column = number(
97
+ error?.columnNumber ??
98
+ error?.colno ??
99
+ error?.column
100
+ );
101
+
102
+ return {
103
+ line: line == null ? null : Math.max(0, line - 1),
104
+ column: column == null ? null : Math.max(0, column - 1),
105
+ };
106
+ }
51
107
 
52
- if(match) {
53
- return {line: parseInt(match[1]), col: parseInt(match[2])}
54
- }
108
+ function traceBlock(error) {
109
+ const trace = error?.lilact_trace;
110
+ return Array.isArray(trace) ? trace[0] : trace;
111
+ }
55
112
 
56
- return null;
113
+ function blockInfo(error) {
114
+ const trace = traceBlock(error);
115
+ return trace == null
116
+ ? null
117
+ : Lilact.blocks_info?.labels?.[trace];
57
118
  }
58
119
 
59
- // Extract { url, line, col } from an Error.stack.
60
- // Works with common formats like:
61
- // Chrome/Edge: at fn (eval:xxx:LINE:COL)
62
- // Firefox: fn@eval:xxx:LINE:COL
63
- // Safari: @eval:xxx:LINE:COL (sometimes)
64
- /** @ignore */
65
- function parseEvalLocationFromStack(stack, urlPrefix = "eval:/")
66
- {
67
- const raw = typeof stack === "string" ? stack : String(stack || "");
68
- const lines = raw.split(/\r?\n/);
69
-
70
- // Match "... (eval:path:LINE:COL)" or "... eval:path:LINE:COL"
71
- // Group 1: url, group 2: line, group 3: col (col optional in some formats)
72
- const re = new RegExp(`\\(?((?:${escapeRegExp(urlPrefix)})[^\\s):]+):(\\d+):(?:(\\d+))?\\)?$`);
73
-
74
- for (const l of lines) {
75
- const line = l.trim();
76
- if (!line.includes(urlPrefix)) continue;
77
-
78
- const m = line.match(re);
79
- if (!m) continue;
80
-
81
- const url = m[1];
82
- const parsedLine = Number(m[2]);
83
- const parsedCol = m[3] == null ? null : Number(m[3]);
84
-
85
- if (Number.isFinite(parsedLine) && (parsedCol === null || Number.isFinite(parsedCol))) {
86
- return { url, line: parsedLine, col: parsedCol, matched: line };
120
+ function mapLocation(mappings, line, column) {
121
+ if (
122
+ !Array.isArray(mappings) ||
123
+ !mappings.length ||
124
+ !Number.isFinite(line) ||
125
+ !Number.isFinite(column)
126
+ ) {
127
+ return { line, column };
128
+ }
129
+
130
+ const valid = mappings
131
+ .filter(mapping =>
132
+ Array.isArray(mapping) &&
133
+ mapping.length >= 4 &&
134
+ mapping
135
+ .slice(0, 4)
136
+ .every(value => Number.isFinite(Number(value)))
137
+ )
138
+ .sort((a, b) =>
139
+ Number(a[0]) - Number(b[0]) ||
140
+ Number(a[1]) - Number(b[1])
141
+ );
142
+
143
+ if (!valid.length) return { line, column };
144
+
145
+ let selected = valid[0];
146
+
147
+ for (const mapping of valid) {
148
+ const generatedLine = Number(mapping[0]);
149
+ const generatedColumn = Number(mapping[1]);
150
+
151
+ if (
152
+ generatedLine < line ||
153
+ (
154
+ generatedLine === line &&
155
+ generatedColumn <= column
156
+ )
157
+ ) {
158
+ selected = mapping;
159
+ } else {
160
+ break;
87
161
  }
88
162
  }
89
163
 
90
- return { url: null, line: null, col: null, matched: null, stackPreview: lines.slice(0, 6).join("\n") };
164
+ const generatedLine = Number(selected[0]);
165
+ const generatedColumn = Number(selected[1]);
166
+ const sourceLine = Number(selected[2]);
167
+ const sourceColumn = Number(selected[3]);
168
+
169
+ return {
170
+ line:
171
+ sourceLine +
172
+ (line === generatedLine ? 0 : line - generatedLine),
173
+ column:
174
+ line === generatedLine
175
+ ? Math.max(0, sourceColumn + column - generatedColumn)
176
+ : sourceColumn,
177
+ };
91
178
  }
92
179
 
93
- function escapeRegExp(s)
94
- {
95
- return String(s).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
180
+ export function traceError(value, runPath) {
181
+ if (value?.isTraced) return value;
182
+
183
+ const error = asError(value);
184
+ const source = error.lilact_source;
185
+ const stack = isParserError(error)
186
+ ? null
187
+ : stackLocation(error.stack);
188
+ const browser = isParserError(error)
189
+ ? {
190
+ line: number(error.lineNumber),
191
+ column: number(error.columnNumber),
192
+ }
193
+ : browserLocation(error);
194
+
195
+ /*
196
+ * lilact_source is authoritative. In particular, it prevents a syntax
197
+ * error from a nested eval from inheriting the caller's eval filename.
198
+ */
199
+ const fileName =
200
+ source?.path ||
201
+ stack?.path ||
202
+ error.fileName ||
203
+ runPath ||
204
+ null;
205
+
206
+ let line =
207
+ stack?.line ??
208
+ browser.line ??
209
+ null;
210
+
211
+ let column =
212
+ stack?.column ??
213
+ browser.column ??
214
+ null;
215
+
216
+ const result = {
217
+ fileName,
218
+ lineNumber: line,
219
+ columnNumber: column,
220
+ message:
221
+ error.message == null
222
+ ? String(error)
223
+ : String(error.message),
224
+ name: error.name || "Error",
225
+ stack: error.stack || null,
226
+ lilact_source: source || null,
227
+ _error: error,
228
+ isTraced: true,
229
+ };
230
+
231
+ const module = fileName && required_scripts[fileName];
232
+
233
+ if (module) {
234
+ const mapped = mapLocation(
235
+ module.mappings,
236
+ result.lineNumber,
237
+ result.columnNumber
238
+ );
239
+
240
+ result.lineNumber = mapped.line;
241
+ result.columnNumber = mapped.column;
242
+ }
243
+
244
+ const block = blockInfo(error);
245
+
246
+ if (
247
+ block &&
248
+ (
249
+ result.lineNumber == null ||
250
+ result.columnNumber == null ||
251
+ !result.fileName
252
+ )
253
+ ) {
254
+ result.fileName ||= block.path || runPath || null;
255
+ result.lineNumber ??= block.line;
256
+ result.columnNumber ??= block.col;
257
+ result.label = block.desc;
258
+ }
259
+
260
+ Lilact.error = result;
261
+ return result;
96
262
  }
97
263
 
98
- function mapLocation(mps, r,c)
99
- {
100
- let map = null;
101
-
102
- for(const i in mps) {
103
- if(mps[i][0]<r) continue;
104
- if(mps[i][0]>r || (mps[i][0]===r && mps[i][1]>=c)) {
105
- map = mps[i-1];
106
- break;
107
- }
108
- }
109
- if(!map) map = mps[mps.length-1];
110
- return {line: r-map[0]+map[2], col: ((r-map[0]===0)?map[3]:0) };
264
+ function escapeHtml(value) {
265
+ return String(value ?? "")
266
+ .replaceAll("&", "&amp;")
267
+ .replaceAll("<", "&lt;")
268
+ .replaceAll(">", "&gt;")
269
+ .replaceAll('"', "&quot;")
270
+ .replaceAll("'", "&#39;");
111
271
  }
112
272
 
273
+ function sourceExcerpt(module, line) {
274
+ if (!module || !Number.isFinite(line)) return null;
113
275
 
114
- /** @ignore */
115
- export function scanBlockLabels(code, path)
116
- {
117
- const ls = Array.from( code.matchAll(/LILACTBLOCK(\d+):(\d+),(\d+):([^*]+)\*\//mg) );
276
+ const lines = String(module.code ?? "").split(/\r?\n/);
118
277
 
119
- // todo: this is not memory efficient, the structure should be upside-down
120
- ls.forEach(
121
- (x) => {
122
- Lilact.blocks_info.labels[x[1]] = {
123
- path,
124
- desc: x[4]
125
- }
126
- } );
278
+ return {
279
+ before: lines[line - 1] ?? "",
280
+ current: lines[line] ?? "",
281
+ after: lines[line + 1] ?? "",
282
+ };
127
283
  }
128
284
 
285
+ export function globalErrorHandler(eventOrError) {
286
+ const value =
287
+ eventOrError?.error instanceof Error
288
+ ? eventOrError.error
289
+ : eventOrError?.reason !== undefined
290
+ ? eventOrError.reason
291
+ : eventOrError;
292
+
293
+ const error = traceError(
294
+ value,
295
+ eventOrError?.fileName || null
296
+ );
297
+
298
+ const excerpt = sourceExcerpt(
299
+ required_scripts[error.fileName],
300
+ error.lineNumber
301
+ );
302
+
303
+ const className = css(`
304
+ background: linear-gradient(135deg, #fff2f2d4, #ffffffd4);
305
+ backdrop-filter: blur(10px);
306
+ border: 1px solid rgba(255,255,255,.25);
307
+ border-radius: 5px;
308
+ box-shadow: 0 10px 30px rgba(0,0,0,.35);
309
+ overflow: hidden;
310
+ min-width: 400px;
311
+ width: 66%;
312
+
313
+ red {
314
+ color: #d00;
315
+ }
129
316
 
130
- /**
131
- * Debug tool to get the Lilact traced location of an error. It can also produce some block based stack trace
132
- * if `Lilact.transpilerConfig.enableLabelStack` is set to `true` before loading the script. This is `false`
133
- * by default for efficiency.
134
- *
135
- * @param error - The error object
136
- *
137
- * @returns A new object that includes `path`, `row`, `col`, `msg`, `name`, and optional `stack`.
138
- * The exception itself is stored as `err`.
139
- */
140
- export function traceError(error, run_path)
141
- {
142
- if(error?.is_traced) {
143
- return error;
144
- }
145
-
146
- const loc = parseEvalLocationFromStack(error.stack);
147
-
148
- const obj = {
149
- fileName: loc.url?.slice(6) || run_path || error.fileName,
150
-
151
- lineNumber: loc.line,
152
- columnNumber: loc.col,
153
-
154
- message: error.message,
155
- name: error.name,
156
-
157
- stack: error.stack,
158
- _error: error,
159
-
160
- is_traced: true
161
- };
162
-
163
- if( error.name!=='JSXParseError' ) {
164
-
165
- let mps;
166
-
167
- if(loc.url) {
168
- const rm = required_scripts[obj.fileName];
169
- mps = rm.mappings;
170
-
171
- const mloc = mapLocation(mps, obj.lineNumber-1, obj.columnNumber-1);
172
-
173
- obj.lineNumber = mloc.line;
174
- obj.columnNumber = mloc.col;
175
- }
176
- else {
177
-
178
- let loc = getErrorLocation(error);
179
-
180
- if( error.lilact_trace!==undefined) {
181
- let mps;
182
- let blk;
183
-
184
- if(typeof(error.lilact_trace)==='object') {
185
- blk = Lilact.blocks_info.labels[error.lilact_trace[0]];
186
- }
187
- else {
188
- blk = Lilact.blocks_info.labels[error.lilact_trace];
189
- }
190
-
191
- if(blk) {
192
- obj.fileName = blk.path;
193
- obj.label = blk.label;
194
-
195
- mps = required_scripts[blk.path].mappings;
196
-
197
- loc = mapLocation(mps, loc.line-1, loc.col-1);
198
- }
199
- }
200
-
201
- obj.lineNumber = loc.line;
202
- obj.columnNumber = loc.col;
203
- }
204
-
205
- }
206
- else {
207
- const loc = getErrorLocation(error);
208
-
209
- //if(!obj.fileName) {
210
- if(error.fileName) obj.fileName = error.fileName;
211
- else if(run_path) obj.fileName = run_path;
212
- //}
213
-
214
- obj.lineNumber = loc.line;
215
- obj.columnNumber = loc.col;
216
- }
217
-
218
- Lilact.error = obj;
219
- return obj;
220
-
317
+ code {
318
+ border: 1px solid #0003;
319
+ overflow: auto;
320
+ padding: 10px;
321
+ display: block;
322
+ }
323
+ `);
324
+
325
+ const dialog = document.createElement("dialog");
326
+ dialog.className = className;
327
+
328
+ const location = error.fileName
329
+ ? `At ${escapeHtml(error.fileName)}`
330
+ : "";
331
+
332
+ const line = Number.isFinite(error.lineNumber)
333
+ ? `: Line ${error.lineNumber + 1}`
334
+ : "";
335
+
336
+ const componentStack =
337
+ error._error?.componentStackLog ||
338
+ error._error?.componentStack ||
339
+ "";
340
+
341
+ dialog.innerHTML = `
342
+ <h3><red>Error!</red></h3>
343
+ <b>${location}${line}</b><br><br>
344
+ <b>${escapeHtml(error.name)}</b>:
345
+ <span>${escapeHtml(error.message)}</span>
346
+ <br><br>
347
+
348
+ ${
349
+ excerpt
350
+ ? "<code><pre></pre><red><pre></pre></red><pre></pre></code>"
351
+ : ""
352
+ }
353
+
354
+ ${
355
+ componentStack
356
+ ? `
357
+ <br>
358
+ Component Stack:
359
+ <br>
360
+ <code><pre>${escapeHtml(componentStack)}</pre></code>
361
+ `
362
+ : ""
363
+ }
364
+ `;
365
+
366
+ if (excerpt) {
367
+ const pre = dialog.querySelectorAll("pre");
368
+ pre[0].innerText = excerpt.before;
369
+ pre[1].innerText = excerpt.current;
370
+ pre[2].innerText = excerpt.after;
371
+ }
372
+
373
+ document.body.appendChild(dialog);
374
+
375
+ if (typeof dialog.showModal === "function") {
376
+ dialog.showModal();
377
+ } else {
378
+ dialog.setAttribute("open", "");
379
+ }
380
+
381
+ return error;
221
382
  }
222
383
 
223
- /**
224
- * The global Lilact error handler that shows a modal containing information
225
- * when an exception is not handled.
226
- *
227
- * It is by default attached to the `window.onerror` when using the development
228
- * bundle. It is not attached in the production mode, but it is available in the
229
- * bundle and can be attached.
230
- *
231
- *
232
- * `
233
- * window.addEventListener('error', (e) => {
234
- * Lilact.globalErrorHandler(e);
235
- * });
236
- * `
237
- * @param error - The error object
238
- *
239
- */
240
- export function globalErrorHandler(error)
241
- {
242
- if(error.error) error = error.error;
243
-
244
- error = traceError(error);
245
-
246
- const cls = css(`
247
- background: linear-gradient(135deg, #fff2f2d4, #ffffffd4);
248
- backdrop-filter: blur(10px);
249
- border: 1px solid rgba(255,255,255,.25);
250
- border-radius: 5px;
251
- box-shadow: 0 10px 30px rgba(0,0,0,.35);
252
- overflow:hidden;
253
- min-width: 400px;
254
- width: 66%;
255
- red {
256
- color:#d00;
257
- }
258
- code {
259
- border: 1px solid #0003;
260
- overflow: auto;
261
- padding: 10px;
262
- display: block;
263
- }
264
- `);
265
-
266
- const el = document.createElement('dialog');
267
-
268
- el.className=cls;
269
- //<b>⚠</b>
270
- el.innerHTML =
271
- `<h3 style=""><red>Error!</red></h3>
272
- <b>${error.fileName?'At '+error.fileName:''}
273
- ${Number.isFinite(error.lineNumber)?": Line "+(error.lineNumber+1):""}</b><br><br>
274
- <b>${error.name}</b>:&nbsp;<span>${error.message}</span><br><br>
275
- ${required_scripts[error.fileName]?'<code><pre></pre><pre><red></red></pre><pre></pre></code>':''}
276
- ${error._error.componentStackLog?'<br>Component Stack:<br><code><pre>'+error._error.componentStackLog+'</pre></code>':''}
277
- `;
278
-
279
-
280
- document.body.appendChild(el);
281
-
282
- const pres = el.querySelectorAll('pre');
283
-
284
- if(required_scripts[error.fileName]) {
285
- const lines = required_scripts[error.fileName].code.split("\n");
286
-
287
- if(lines?.[error.lineNumber-1])
288
- pres[0].innerText = lines[error.lineNumber-1];
289
-
290
- if(lines?.[error.lineNumber]) el.querySelector('pre red').innerText = lines[error.lineNumber];
291
-
292
- if(lines?.[error.lineNumber+1])
293
- pres[2].innerText = lines[error.lineNumber+1];
294
- }
295
-
296
- el.showModal();
384
+ export function scanBlockLabels(code, path) {
385
+ for (const match of String(code).matchAll(
386
+ /LILACTBLOCK(\d+):(\d+),(\d+):([^*]+)\*\//gm
387
+ )) {
388
+ Lilact.blocks_info.labels[match[1]] = {
389
+ path,
390
+ line: Number(match[2]),
391
+ col: Number(match[3]),
392
+ desc: match[4],
393
+ };
394
+ }
297
395
  }
298
396
 
299
- /** @ignore */
300
- export const blocks_info = { counter: 0, labels: {} };
397
+ export const blocks_info = {
398
+ counter: 0,
399
+ labels: {},
400
+ };
301
401
 
302
- /** @ignore */
303
- export let error = null; // this is only to ease debuggin,
402
+ export let error = null;