@lmjs/core 2.0.0 → 2.1.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/dist/lumenjs-core-with-plugins.js +6 -6
- package/dist/lumenjs-core.js +7 -7
- package/package.json +1 -1
- package/src/_re.js +608 -77
- package/src/dom-shim.js +167 -0
- package/src/index-bootstrap.js +7 -1
- package/src/lstnrs.js +20 -2
- package/src/walk.js +101 -13
- package/vendor/reconnecting-websocket.js +25 -0
package/src/dom-shim.js
CHANGED
|
@@ -128,11 +128,33 @@
|
|
|
128
128
|
return this;
|
|
129
129
|
};
|
|
130
130
|
$.fn.get = function (i) { return i === undefined ? toArray(this) : this[i]; };
|
|
131
|
+
// 2026-09-17, found alongside .find()/.is() (same real bug's radio-
|
|
132
|
+
// group branch in checkFormInputs()) — excludes elements matching a
|
|
133
|
+
// raw DOM node, an array of nodes, or a $()-wrapped set; jQuery also
|
|
134
|
+
// supports a selector string here, not needed by anything in this
|
|
135
|
+
// codebase's actual usage (grepped), so not implemented.
|
|
136
|
+
$.fn.not = function (exclude) {
|
|
137
|
+
var excludeArr = exclude instanceof $ ? toArray(exclude) : (Array.isArray(exclude) ? exclude : [exclude]);
|
|
138
|
+
var out = [];
|
|
139
|
+
this.each(function () { if (excludeArr.indexOf(this) === -1) out.push(this); });
|
|
140
|
+
return $(out);
|
|
141
|
+
};
|
|
131
142
|
|
|
132
143
|
$.fn.attr = function (name, value) {
|
|
133
144
|
if (value === undefined) return this[0] ? this[0].getAttribute(name) : undefined;
|
|
134
145
|
return this.each(function () { this.setAttribute(name, value); });
|
|
135
146
|
};
|
|
147
|
+
// 2026-09-17, found in the same real-page click test as .find()/.is()/
|
|
148
|
+
// .not() above — real jQuery distinguishes .prop() (a direct DOM
|
|
149
|
+
// property, e.g. .checked/.disabled/.scrollHeight) from .attr() (the
|
|
150
|
+
// HTML attribute string); the two diverge for exactly the boolean
|
|
151
|
+
// properties this codebase's real usage needs (checkFormInputs()'s
|
|
152
|
+
// "disabled", the vendored search-widget's "disabled", scroll
|
|
153
|
+
// measurements) — grepped actual call sites, not guessed.
|
|
154
|
+
$.fn.prop = function (name, value) {
|
|
155
|
+
if (value === undefined) return this[0] ? this[0][name] : undefined;
|
|
156
|
+
return this.each(function () { this[name] = value; });
|
|
157
|
+
};
|
|
136
158
|
$.fn.removeAttr = function (name) {
|
|
137
159
|
return this.each(function () { this.removeAttribute(name); });
|
|
138
160
|
};
|
|
@@ -188,6 +210,15 @@
|
|
|
188
210
|
}
|
|
189
211
|
return this.each(function () { this.innerHTML = value; });
|
|
190
212
|
};
|
|
213
|
+
// 2026-09-17, found alongside .find()/.is()/.not()/.prop() above —
|
|
214
|
+
// checkFormInputs()'s "data-into" error-message branch calls
|
|
215
|
+
// into.text(...). Not hit by the specific real page that surfaced
|
|
216
|
+
// the others (this codebase's checkbox demo has no data-into), added
|
|
217
|
+
// proactively rather than waiting for a second real crash to find it.
|
|
218
|
+
$.fn.text = function (value) {
|
|
219
|
+
if (value === undefined) return this[0] ? this[0].textContent : '';
|
|
220
|
+
return this.each(function () { this.textContent = value; });
|
|
221
|
+
};
|
|
191
222
|
$.fn.css = function (name, value) {
|
|
192
223
|
if (typeof name === 'object') {
|
|
193
224
|
return this.each(function () {
|
|
@@ -306,6 +337,45 @@
|
|
|
306
337
|
$.fn.parent = function () { return $(this[0] ? this[0].parentElement : null); };
|
|
307
338
|
$.fn.next = function () { return $(this[0] ? this[0].nextElementSibling : null); };
|
|
308
339
|
$.fn.closest = function (sel) { return $(this[0] ? this[0].closest(sel) : null); };
|
|
340
|
+
// 2026-09-17, real bug found and fixed: never implemented at all —
|
|
341
|
+
// vendor/reconnecting-websocket.js's ported V1 form-validation code
|
|
342
|
+
// (checkFormInputs()) calls f.find(...) on the result of .closest(
|
|
343
|
+
// "form"), and .closest() legitimately returns an empty $() when
|
|
344
|
+
// there's no enclosing form (a completely normal case — a single
|
|
345
|
+
// checkbox/input outside a <form>). With .find() missing, that threw
|
|
346
|
+
// "f.find is not a function" the moment ANY input/textarea/select
|
|
347
|
+
// fired a "change" event anywhere on the page, form or no form —
|
|
348
|
+
// found clicking a plain checkbox on lumenjs.com's own real V2
|
|
349
|
+
// homepage. Real jQuery .find() semantics: search descendants of
|
|
350
|
+
// each matched element, union the results (safe/empty on an empty
|
|
351
|
+
// set, exactly what's needed here).
|
|
352
|
+
$.fn.find = function (sel) {
|
|
353
|
+
var out = [];
|
|
354
|
+
this.each(function () {
|
|
355
|
+
var found = this.querySelectorAll(sel);
|
|
356
|
+
for (var i = 0; i < found.length; i++) if (out.indexOf(found[i]) === -1) out.push(found[i]);
|
|
357
|
+
});
|
|
358
|
+
return $(out);
|
|
359
|
+
};
|
|
360
|
+
// 2026-09-17, found alongside .find() (same crash, next line of the
|
|
361
|
+
// same real bug): checkFormInputs() also calls el.is(":checkbox")/
|
|
362
|
+
// ":radio"/":checked" — real CSS pseudo-classes for the former two
|
|
363
|
+
// don't exist (native .matches(":checkbox") throws a SyntaxError,
|
|
364
|
+
// it's a jQuery-only pseudo-selector), so a short, explicit table for
|
|
365
|
+
// the handful actually used across the vendored code (grepped, not
|
|
366
|
+
// guessed) plus a native .matches() fallback for everything else
|
|
367
|
+
// (tag names, real attribute selectors like "[dp0]") — safely false
|
|
368
|
+
// rather than throwing if a selector native matches() can't parse.
|
|
369
|
+
var JQUERY_PSEUDOS = {
|
|
370
|
+
':checkbox': function (el) { return el.tagName === 'INPUT' && el.type === 'checkbox'; },
|
|
371
|
+
':radio': function (el) { return el.tagName === 'INPUT' && el.type === 'radio'; },
|
|
372
|
+
':checked': function (el) { return !!el.checked; },
|
|
373
|
+
};
|
|
374
|
+
$.fn.is = function (sel) {
|
|
375
|
+
if (!this[0]) return false;
|
|
376
|
+
if (JQUERY_PSEUDOS[sel]) return JQUERY_PSEUDOS[sel](this[0]);
|
|
377
|
+
try { return this[0].matches(sel); } catch (e) { return false; }
|
|
378
|
+
};
|
|
309
379
|
$.fn.ready = function (fn) {
|
|
310
380
|
if (document.readyState !== 'loading') fn($);
|
|
311
381
|
else document.addEventListener('DOMContentLoaded', function () { fn($); });
|
|
@@ -468,6 +538,103 @@
|
|
|
468
538
|
return this;
|
|
469
539
|
};
|
|
470
540
|
|
|
541
|
+
// 2026-09-18, real gap found testing a real migrated V1 project end-to-
|
|
542
|
+
// end: `st()` (vendor/reconnecting-websocket.js's own real, shipped
|
|
543
|
+
// scroll-to helper — not project code, framework code) does
|
|
544
|
+
// `$("html,body").stop().animate({scrollTop: x}, {duration: 400})` —
|
|
545
|
+
// a very common pattern across real V1 pages (called at the top of
|
|
546
|
+
// most real view scripts for smooth-scroll navigation). Neither
|
|
547
|
+
// method existed in the shim at all, throwing "not a function" and
|
|
548
|
+
// aborting the rest of that script on every real page that called it.
|
|
549
|
+
// `scrollTop`/`scrollLeft` are DOM properties, not CSS ones — handled
|
|
550
|
+
// as a special case, matching real jQuery's own animate() behavior;
|
|
551
|
+
// any other property is treated as a numeric CSS property (adding
|
|
552
|
+
// "px" unless it's already a bare number-only string/value, matching
|
|
553
|
+
// jQuery's own unit-inference for a plain number).
|
|
554
|
+
var _animTimers = new WeakMap();
|
|
555
|
+
function _animGetScroll(el, prop) {
|
|
556
|
+
if (el === window) return prop === 'scrollLeft' ? window.pageXOffset : window.pageYOffset;
|
|
557
|
+
return el[prop];
|
|
558
|
+
}
|
|
559
|
+
function _animSetScroll(el, prop, val) {
|
|
560
|
+
if (el === window) {
|
|
561
|
+
if (prop === 'scrollLeft') window.scrollTo(val, window.pageYOffset);
|
|
562
|
+
else window.scrollTo(window.pageXOffset, val);
|
|
563
|
+
} else {
|
|
564
|
+
el[prop] = val;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
// Real jQuery's own cssNumber list — these CSS properties take a bare
|
|
568
|
+
// unitless number, not "Npx" (opacity: "0.5px" is simply invalid and
|
|
569
|
+
// silently ignored by the browser, which is exactly what broke the
|
|
570
|
+
// very first real-content test of this: an opacity fade never moved).
|
|
571
|
+
var _cssNumberProps = { opacity: 1, zIndex: 1, zoom: 1, fontWeight: 1, lineHeight: 1, columnCount: 1, flexGrow: 1, flexShrink: 1, order: 1, widows: 1, orphans: 1 };
|
|
572
|
+
function _animSetStyle(el, prop, val) {
|
|
573
|
+
el.style[prop] = _cssNumberProps[prop] ? val : (val + 'px');
|
|
574
|
+
}
|
|
575
|
+
$.fn.animate = function (props, options) {
|
|
576
|
+
var opts = typeof options === 'number' ? { duration: options } : (options || {});
|
|
577
|
+
var duration = opts.duration == null ? 400 : opts.duration;
|
|
578
|
+
this.each(function () {
|
|
579
|
+
var el = this;
|
|
580
|
+
var isWindowScroll = (el === document.documentElement || el === document.body);
|
|
581
|
+
var target = isWindowScroll ? window : el;
|
|
582
|
+
var start = {};
|
|
583
|
+
var isScrollProp = {};
|
|
584
|
+
for (var prop in props) {
|
|
585
|
+
if (!Object.prototype.hasOwnProperty.call(props, prop)) continue;
|
|
586
|
+
isScrollProp[prop] = (prop === 'scrollTop' || prop === 'scrollLeft');
|
|
587
|
+
if (isScrollProp[prop]) {
|
|
588
|
+
start[prop] = _animGetScroll(isWindowScroll ? window : el, prop);
|
|
589
|
+
} else {
|
|
590
|
+
start[prop] = parseFloat(getComputedStyle(el)[prop]) || 0;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
var existing = _animTimers.get(el);
|
|
594
|
+
if (existing) cancelAnimationFrame(existing);
|
|
595
|
+
if (duration === 0) {
|
|
596
|
+
for (var p in props) {
|
|
597
|
+
var v = parseFloat(props[p]);
|
|
598
|
+
if (isScrollProp[p]) _animSetScroll(isWindowScroll ? window : el, p, v);
|
|
599
|
+
else _animSetStyle(el, p, v);
|
|
600
|
+
}
|
|
601
|
+
if (opts.complete) opts.complete.call(el);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
var startTime = null;
|
|
605
|
+
function step(ts) {
|
|
606
|
+
if (startTime === null) startTime = ts;
|
|
607
|
+
var elapsed = ts - startTime;
|
|
608
|
+
var t = Math.min(1, elapsed / duration);
|
|
609
|
+
for (var p in props) {
|
|
610
|
+
if (!Object.prototype.hasOwnProperty.call(props, p)) continue;
|
|
611
|
+
var end = parseFloat(props[p]);
|
|
612
|
+
var val = start[p] + (end - start[p]) * t;
|
|
613
|
+
if (isScrollProp[p]) _animSetScroll(isWindowScroll ? window : el, p, val);
|
|
614
|
+
else _animSetStyle(el, p, val);
|
|
615
|
+
}
|
|
616
|
+
if (t < 1) {
|
|
617
|
+
_animTimers.set(el, requestAnimationFrame(step));
|
|
618
|
+
} else {
|
|
619
|
+
_animTimers.delete(el);
|
|
620
|
+
if (opts.complete) opts.complete.call(el);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
_animTimers.set(el, requestAnimationFrame(step));
|
|
624
|
+
});
|
|
625
|
+
return this;
|
|
626
|
+
};
|
|
627
|
+
$.fn.stop = function () {
|
|
628
|
+
this.each(function () {
|
|
629
|
+
var id = _animTimers.get(this);
|
|
630
|
+
if (id) {
|
|
631
|
+
cancelAnimationFrame(id);
|
|
632
|
+
_animTimers.delete(this);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
return this;
|
|
636
|
+
};
|
|
637
|
+
|
|
471
638
|
global.$ = $;
|
|
472
639
|
global.jQuery = $;
|
|
473
640
|
})(typeof window !== 'undefined' ? window : globalThis);
|
package/src/index-bootstrap.js
CHANGED
|
@@ -41,7 +41,13 @@ if (typeof _beaTn === 'undefined' || !_beaTn) {
|
|
|
41
41
|
console.log('Failed to load index.js: HTTP ' + _idxXhr.status);
|
|
42
42
|
}
|
|
43
43
|
} catch (e) {
|
|
44
|
-
console.log
|
|
44
|
+
// 2026-09-17: was console.log — a real exception partway through
|
|
45
|
+
// index.js (e.g. the _vt.Global.fns crash this session found and
|
|
46
|
+
// fixed) silently looked like nothing happened at all in dev
|
|
47
|
+
// mode, while the same bug was fatal in a real production
|
|
48
|
+
// build (index.js runs inline there, unguarded). A real error
|
|
49
|
+
// here should be as visible as reportLumenError()'s.
|
|
50
|
+
console.error('Failed to load index.js: ', e);
|
|
45
51
|
}
|
|
46
52
|
}
|
|
47
53
|
}
|
package/src/lstnrs.js
CHANGED
|
@@ -496,9 +496,27 @@ $("body").on("modal-open", "[\\@modal-open]", function (ev, modal) {
|
|
|
496
496
|
function evalEvAttr(_v, ev, el, n, arg1, arg2, arg3) {
|
|
497
497
|
let _fn = _v.split("(")[0];
|
|
498
498
|
try {
|
|
499
|
-
|
|
499
|
+
// 2026-09-17: prefer the triggering element's OWN rendering instance's
|
|
500
|
+
// function registry (._ownerRe.view.fns, set up by walk.js's
|
|
501
|
+
// _fnRegisterStatement() + _re.js's createEl()/renderHST()) over the
|
|
502
|
+
// plain `window[_fn]` lookup below — real bug found and fixed: every
|
|
503
|
+
// mounted instance of the same subview declaring a same-named handler
|
|
504
|
+
// (e.g. multiple `<div view="widgets/card">` each with their own
|
|
505
|
+
// `function bump(){}`) used to collide on one global, since a plain
|
|
506
|
+
// <script> tag's top-level function declarations all write to the
|
|
507
|
+
// same `window` object — window.bump silently became whichever
|
|
508
|
+
// instance's script last ran, so every instance's @click fired THAT
|
|
509
|
+
// one instance's handler regardless of which was actually clicked.
|
|
510
|
+
// Only applies to the plain-name form (`_v === _fn`, no `(...)`) —
|
|
511
|
+
// a parameterized attribute like `@click="fn(x)"` already falls
|
|
512
|
+
// through to the eval() branch below unchanged, exactly as before.
|
|
513
|
+
let _rawEl = el[0];
|
|
514
|
+
let _ownerFns = _rawEl && _rawEl._ownerRe && _rawEl._ownerRe.view && _rawEl._ownerRe.view.fns;
|
|
515
|
+
let _ownFn = (_v === _fn && _ownerFns && typeof _ownerFns[_fn] === "function") ? _ownerFns[_fn] : null;
|
|
516
|
+
if (_ownFn || typeof window[_v] === "function") {
|
|
500
517
|
ev["cType"] = n;
|
|
501
|
-
|
|
518
|
+
let _handler = _ownFn || window[_fn];
|
|
519
|
+
var _data = arg3 ? _handler(ev, el, arg1, arg2, arg3) : (arg2 ? _handler(ev, el, arg1, arg2) : (arg1 ? _handler(ev, el, arg1) : _handler(ev, el)));
|
|
502
520
|
return _data;
|
|
503
521
|
} else {
|
|
504
522
|
let _eva = eval(_v);
|
package/src/walk.js
CHANGED
|
@@ -278,6 +278,28 @@ function getL1Vs(AST, view, vazzz) {
|
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
function getWatcher(AST, view, vazzz, targetKey = "View") {
|
|
281
|
+
// 2026-09-17, real bug found and fixed: changeReactiveVarsOccurences()/
|
|
282
|
+
// transformTopLevelDeclarations() mutate the AST IN PLACE (they
|
|
283
|
+
// replace nodes via `parent[prop] = replacement`, not by building a
|
|
284
|
+
// new tree) — harmless as long as a given AST object is only ever
|
|
285
|
+
// rewritten once, which used to always be true. Per-instance subview
|
|
286
|
+
// scoping changed that: renderHST() (_re.js) is called once per
|
|
287
|
+
// subview MOUNT, but all mounts of the same .view file share the
|
|
288
|
+
// exact same parsed `nd._jst` object (parsed once, cached, reused) —
|
|
289
|
+
// so the first instance's rewrite (into e.g. _vt.View.views[0].vars)
|
|
290
|
+
// permanently consumed the raw identifiers, and every subsequent
|
|
291
|
+
// instance's "rewrite" silently found nothing left to rewrite,
|
|
292
|
+
// regenerating instance 0's already-rewritten code unchanged. Found
|
|
293
|
+
// testing 3 real mounted instances of the same subview on lumenjs.com's
|
|
294
|
+
// own V2 homepage — only the first ever got its own correct scope.
|
|
295
|
+
// Cloning here (not at each call site) makes getWatcher() safe to
|
|
296
|
+
// call repeatedly against the same input AST, which is now a real
|
|
297
|
+
// requirement, not just good hygiene. ASTs are plain, JSON-safe data
|
|
298
|
+
// (no functions/circular refs), so JSON round-tripping is a correct,
|
|
299
|
+
// dependency-free deep clone — walk.js has no access to the vendored
|
|
300
|
+
// recursive clone() helper (a different, browser-only bundle file).
|
|
301
|
+
AST = JSON.parse(JSON.stringify(AST));
|
|
302
|
+
|
|
281
303
|
let Vars = getL1Vs(AST, view, vazzz);
|
|
282
304
|
AST = Vars['AST'];
|
|
283
305
|
let varz = Vars['varz'];
|
|
@@ -313,11 +335,46 @@ function validateBeforeRewrite(AST, view) {
|
|
|
313
335
|
}
|
|
314
336
|
}
|
|
315
337
|
|
|
338
|
+
// 2026-09-17: `targetKey` used to only ever be a plain string ("View" or
|
|
339
|
+
// "Global"), producing a fixed 2-level root (`_vt.View`/`_vt.Global`). Real
|
|
340
|
+
// per-instance subview scoping (`_vt.View.views[i].vars`, and its nested
|
|
341
|
+
// form `_vt.View.views[0].views[2].vars` for a subview hosting a subview)
|
|
342
|
+
// needs a *deeper* chain than a single Identifier can express. Accepts
|
|
343
|
+
// either form now: a bare string is treated as a one-element path (100%
|
|
344
|
+
// unchanged output for every existing caller — "View"/"Global" still build
|
|
345
|
+
// exactly `_vt.View`/`_vt.Global`); an array of segments builds the real
|
|
346
|
+
// chain, a string segment as a non-computed Identifier access, a number
|
|
347
|
+
// segment as a computed numeric-literal access (`.views[3]`).
|
|
348
|
+
function buildTargetRootExpr(targetKey) {
|
|
349
|
+
const segments = Array.isArray(targetKey) ? targetKey : [targetKey];
|
|
350
|
+
let expr = { type: "Identifier", name: "_vt" };
|
|
351
|
+
for (const seg of segments) {
|
|
352
|
+
if (typeof seg === "number") {
|
|
353
|
+
expr = {
|
|
354
|
+
type: "MemberExpression",
|
|
355
|
+
object: expr,
|
|
356
|
+
property: { type: "Literal", value: seg, raw: String(seg) },
|
|
357
|
+
computed: true
|
|
358
|
+
};
|
|
359
|
+
} else {
|
|
360
|
+
expr = {
|
|
361
|
+
type: "MemberExpression",
|
|
362
|
+
object: expr,
|
|
363
|
+
property: { type: "Identifier", name: seg },
|
|
364
|
+
computed: false
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return expr;
|
|
369
|
+
}
|
|
370
|
+
|
|
316
371
|
// The kept reactive-variable rewrite: turns a reference to a reactive
|
|
317
372
|
// variable into `_vt.View.vars["<name>"]`, matching `_re.js`'s Proxy target.
|
|
318
373
|
// `targetKey` (2026-09-14): which root of `_vt` to rewrite into — "View"
|
|
319
374
|
// (default, unchanged) for .view <script> blocks, "Global" for index.js's
|
|
320
375
|
// top-level vars, which need to outlive a single view (see _re.js's _vt.Global).
|
|
376
|
+
// Can also be a path array (see buildTargetRootExpr above) for a subview
|
|
377
|
+
// instance's own scope.
|
|
321
378
|
function changeReactiveVarsOccurences(AST, reactiveVariables, targetKey = "View") {
|
|
322
379
|
const reactive = new Set(reactiveVariables || []);
|
|
323
380
|
|
|
@@ -611,17 +668,12 @@ function changeReactiveVarsOccurences(AST, reactiveVariables, targetKey = "View"
|
|
|
611
668
|
parent.shorthand = false;
|
|
612
669
|
}
|
|
613
670
|
|
|
614
|
-
// Build computed member expression: _vt
|
|
671
|
+
// Build computed member expression: _vt.<targetKey>.vars["<name>"]
|
|
615
672
|
const replacement = {
|
|
616
673
|
type: "MemberExpression",
|
|
617
674
|
object: {
|
|
618
675
|
type: "MemberExpression",
|
|
619
|
-
object:
|
|
620
|
-
type: "MemberExpression",
|
|
621
|
-
object: { type: "Identifier", name: "_vt" },
|
|
622
|
-
property: { type: "Identifier", name: targetKey },
|
|
623
|
-
computed: false
|
|
624
|
-
},
|
|
676
|
+
object: buildTargetRootExpr(targetKey),
|
|
625
677
|
property: { type: "Identifier", name: "vars" },
|
|
626
678
|
computed: false
|
|
627
679
|
},
|
|
@@ -684,12 +736,7 @@ function _reactiveAssignStatement(name, init, targetKey = "View") {
|
|
|
684
736
|
object: {
|
|
685
737
|
type: "MemberExpression",
|
|
686
738
|
computed: false,
|
|
687
|
-
object:
|
|
688
|
-
type: "MemberExpression",
|
|
689
|
-
computed: false,
|
|
690
|
-
object: { type: "Identifier", name: "_vt" },
|
|
691
|
-
property: { type: "Identifier", name: targetKey }
|
|
692
|
-
},
|
|
739
|
+
object: buildTargetRootExpr(targetKey),
|
|
693
740
|
property: { type: "Identifier", name: "vars" }
|
|
694
741
|
},
|
|
695
742
|
property: { type: "Literal", value: name }
|
|
@@ -699,6 +746,41 @@ function _reactiveAssignStatement(name, init, targetKey = "View") {
|
|
|
699
746
|
};
|
|
700
747
|
}
|
|
701
748
|
|
|
749
|
+
// 2026-09-17: registers a top-level function declaration onto this
|
|
750
|
+
// render's own scope (`_vt.<targetKey>.fns["name"] = name`) — ADDITIVE,
|
|
751
|
+
// not a replacement: the function declaration itself is left completely
|
|
752
|
+
// untouched right above this (still a real `window.name` too, the
|
|
753
|
+
// existing, deliberate "functions stay real globals so onclick='fn()'
|
|
754
|
+
// keeps working" design — see compileProjectScript's matching comment).
|
|
755
|
+
// Needed once multiple instances of the same subview can be mounted at
|
|
756
|
+
// once (see _re.js's per-instance scoping work) — every instance
|
|
757
|
+
// declaring a same-named handler collided on the one global function
|
|
758
|
+
// (window.bump became whichever instance's script ran last), so every
|
|
759
|
+
// instance's @click ended up calling THAT one instance's handler
|
|
760
|
+
// regardless of which was actually clicked. lstnrs.js's evalEvAttr()
|
|
761
|
+
// checks this per-instance copy first, before falling back to window.
|
|
762
|
+
function _fnRegisterStatement(name, targetKey) {
|
|
763
|
+
return {
|
|
764
|
+
type: "ExpressionStatement",
|
|
765
|
+
expression: {
|
|
766
|
+
type: "AssignmentExpression",
|
|
767
|
+
operator: "=",
|
|
768
|
+
left: {
|
|
769
|
+
type: "MemberExpression",
|
|
770
|
+
computed: true,
|
|
771
|
+
object: {
|
|
772
|
+
type: "MemberExpression",
|
|
773
|
+
computed: false,
|
|
774
|
+
object: buildTargetRootExpr(targetKey),
|
|
775
|
+
property: { type: "Identifier", name: "fns" }
|
|
776
|
+
},
|
|
777
|
+
property: { type: "Literal", value: name }
|
|
778
|
+
},
|
|
779
|
+
right: { type: "Identifier", name: name }
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
702
784
|
// Handles any number of declarators in a single top-level statement, not just
|
|
703
785
|
// one. `var x = 1, y = 2;` with only `x` reactive becomes two statements:
|
|
704
786
|
// `_vt.View.vars["x"] = 1;` followed by a real `var y = 2;` for the rest —
|
|
@@ -709,6 +791,12 @@ function transformTopLevelDeclarations(AST, reactiveVariables, targetKey = "View
|
|
|
709
791
|
const newBody = [];
|
|
710
792
|
|
|
711
793
|
for (const stmt of AST.body) {
|
|
794
|
+
if (stmt.type === "FunctionDeclaration" && stmt.id) {
|
|
795
|
+
newBody.push(stmt);
|
|
796
|
+
newBody.push(_fnRegisterStatement(stmt.id.name, targetKey));
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
|
|
712
800
|
if (stmt.type !== "VariableDeclaration") {
|
|
713
801
|
newBody.push(stmt);
|
|
714
802
|
continue;
|
|
@@ -247,6 +247,31 @@ var _ups = [];
|
|
|
247
247
|
if (o.Additional) {
|
|
248
248
|
bea([o.Additional], "ready", function () { });
|
|
249
249
|
}
|
|
250
|
+
// 2026-09-18, real bug found and fixed: Reactor({init, tick, auth})
|
|
251
|
+
// is documented (lumenjs-spec.md §3, llms.md §3) and real V1 projects
|
|
252
|
+
// depend on it (app-wide bootstrap work in init() — auth/session
|
|
253
|
+
// setup, an SDK instance like `rx = new RX(...)`/`bea = new BEA(...)`,
|
|
254
|
+
// globals defaults; periodic work in tick(), e.g. re-highlighting
|
|
255
|
+
// code blocks after every render) — but nothing in this whole
|
|
256
|
+
// runtime ever read o.init/o.tick/o.auth at all. A real, unrelated-
|
|
257
|
+
// looking function (setAppFuncs(), earlier in this same file) does
|
|
258
|
+
// check appSettings.init — but is itself never called from
|
|
259
|
+
// anywhere in the shipped runtime, dead code calling more dead
|
|
260
|
+
// code. Confirmed via a real migrated V1 project end-to-end:
|
|
261
|
+
// `init()`-set globals (`globals.Settings`, an `rx`/`bea` SDK
|
|
262
|
+
// instance) were silently never set, with no error at all — every
|
|
263
|
+
// reference to them elsewhere threw "not defined" instead, easy to
|
|
264
|
+
// misdiagnose as an unrelated data/scoping bug (which is exactly
|
|
265
|
+
// what this took real, extensive tracing to rule out).
|
|
266
|
+
// init(): synchronous, called once, right here — Reactor(o) itself
|
|
267
|
+
// is always the first thing a real index.js does, before whatever
|
|
268
|
+
// triggers the first render (the ready-handler retry guard
|
|
269
|
+
// elsewhere in this file already waits on appSettings.Base, set
|
|
270
|
+
// just above, so no render can start before this function returns).
|
|
271
|
+
if (o.init && typeof o.init === "function") o.init();
|
|
272
|
+
// tick(): "after every re-render, anywhere" (documented) — hooked
|
|
273
|
+
// into _lm.prototype.renderAll in _re.js instead of duplicated
|
|
274
|
+
// here; see that file's own comment for why there, not here.
|
|
250
275
|
};
|
|
251
276
|
});
|
|
252
277
|
|