@stacksjs/stx 0.2.112 → 0.2.114
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/app-shell.d.ts +1 -1
- package/dist/app-shell.js +7 -1
- package/dist/caching.js +2 -1
- package/dist/cli.js +1437 -956
- package/dist/client-script-bundler.js +33 -16
- package/dist/client-script.js +50 -5
- package/dist/component-renderer.js +56 -13
- package/dist/composable-loader.d.ts +15 -0
- package/dist/composable-loader.js +117 -0
- package/dist/desktop.d.ts +15 -0
- package/dist/desktop.js +1 -0
- package/dist/dev-server/crosswind.js +28 -13
- package/dist/dev-server/serve-app.js +1 -1
- package/dist/expressions.js +9 -2
- package/dist/includes.js +6 -4
- package/dist/menubar.d.ts +63 -0
- package/dist/menubar.js +103 -0
- package/dist/plugin.js +4 -3
- package/dist/process.js +83 -23
- package/dist/pwa.js +7359 -6878
- package/dist/render.js +4 -24
- package/dist/runtime-globals.d.ts +59 -0
- package/dist/runtime-globals.js +65 -0
- package/dist/runtime-injection.js +2 -2
- package/dist/serve.js +4 -21
- package/dist/sfc-template.d.ts +17 -0
- package/dist/sfc-template.js +32 -0
- package/dist/signal-processing.js +27 -12
- package/dist/signals.js +558 -128
- package/dist/slots.js +105 -20
- package/dist/ssg.js +7381 -6900
- package/dist/stx.d.ts +8 -0
- package/dist/testing.js +8 -1
- package/dist/types/config-types.d.ts +2 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +66 -19
- package/dist/variable-extractor.js +3 -3
- package/package.json +6 -8
- package/stx.d.ts +8 -0
package/dist/signals.js
CHANGED
|
@@ -264,18 +264,19 @@ else {
|
|
|
264
264
|
// expressions like store.someSignal (no parens) and store.x === 'y'
|
|
265
265
|
// resolve to the value. Action methods on the store pass through as
|
|
266
266
|
// functions because they don't carry _isSignal/_isDerived markers.
|
|
267
|
-
function createAutoUnwrapProxy(scope) {
|
|
267
|
+
function createAutoUnwrapProxy(scope, preserveSignal) {
|
|
268
268
|
return new Proxy(scope, {
|
|
269
269
|
get(target, prop) {
|
|
270
270
|
const val = target[prop];
|
|
271
271
|
// If it's a signal or derived, call it to get the value
|
|
272
272
|
if (val && typeof val === 'function' && (val._isSignal || val._isDerived)) {
|
|
273
|
+
if (preserveSignal && preserveSignal(prop, val)) return val;
|
|
273
274
|
return val();
|
|
274
275
|
}
|
|
275
276
|
// If it's an stx store, return a recursively-unwrapping wrapper so
|
|
276
277
|
// store.signalProp resolves to the value in template expressions
|
|
277
278
|
if (val && typeof val === 'object' && val._isStxStore) {
|
|
278
|
-
return createAutoUnwrapProxy(val);
|
|
279
|
+
return createAutoUnwrapProxy(val, preserveSignal);
|
|
279
280
|
}
|
|
280
281
|
return val;
|
|
281
282
|
},
|
|
@@ -700,8 +701,32 @@ finally {
|
|
|
700
701
|
const mountCallbacks = [];
|
|
701
702
|
const destroyCallbacks = [];
|
|
702
703
|
|
|
703
|
-
function
|
|
704
|
-
|
|
704
|
+
function currentLifecycleScope() {
|
|
705
|
+
var el = window.__STX_CURRENT_ELEMENT__;
|
|
706
|
+
if (!el || !el.getAttribute || !window.stx || !window.stx._scopes) return null;
|
|
707
|
+
var scopeId = el.getAttribute('data-stx-scope');
|
|
708
|
+
return scopeId ? window.stx._scopes[scopeId] || null : null;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function onMount(fn) {
|
|
712
|
+
var scope = currentLifecycleScope();
|
|
713
|
+
if (scope) {
|
|
714
|
+
scope.__mountCallbacks = scope.__mountCallbacks || [];
|
|
715
|
+
scope.__mountCallbacks.push(fn);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
mountCallbacks.push(fn);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function onDestroy(fn) {
|
|
722
|
+
var scope = currentLifecycleScope();
|
|
723
|
+
if (scope) {
|
|
724
|
+
scope.__destroyCallbacks = scope.__destroyCallbacks || [];
|
|
725
|
+
scope.__destroyCallbacks.push(fn);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
destroyCallbacks.push(fn);
|
|
729
|
+
}
|
|
705
730
|
|
|
706
731
|
// Drain any early mount/destroy calls captured by the pre-initialization shim
|
|
707
732
|
if (window.__stx_early_mounts) { window.__stx_early_mounts.forEach(function(fn) { mountCallbacks.push(fn); }); window.__stx_early_mounts = null; }
|
|
@@ -959,17 +984,22 @@ else {
|
|
|
959
984
|
|
|
960
985
|
// setRouteParams: called by the server-injected script to set initial params
|
|
961
986
|
function setRouteParams(params) {
|
|
962
|
-
|
|
987
|
+
var nextParams = params || {};
|
|
988
|
+
_routeParams.set(nextParams);
|
|
989
|
+
window.__stx_rp = nextParams;
|
|
990
|
+
if (window.stx) window.stx._rp = nextParams;
|
|
963
991
|
}
|
|
964
992
|
|
|
965
993
|
// Listen for SPA navigation to update params
|
|
966
994
|
window.addEventListener('stx:navigate', function(e) {
|
|
967
|
-
//
|
|
968
|
-
//
|
|
969
|
-
//
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
995
|
+
// The destination fragment's data-stx-route-params script runs after this
|
|
996
|
+
// event and before stx:load. Reset first so a static route can never inherit
|
|
997
|
+
// params from the dynamic route that preceded it.
|
|
998
|
+
var nextParams = e.detail
|
|
999
|
+
&& Object.prototype.hasOwnProperty.call(e.detail, 'params')
|
|
1000
|
+
? e.detail.params
|
|
1001
|
+
: {};
|
|
1002
|
+
setRouteParams(nextParams);
|
|
973
1003
|
});
|
|
974
1004
|
|
|
975
1005
|
// Apply any server-injected params (set before runtime loaded)
|
|
@@ -1252,19 +1282,31 @@ finally {
|
|
|
1252
1282
|
}
|
|
1253
1283
|
// First try component-level scope
|
|
1254
1284
|
const elementScope = findElementScope(el || currentElement);
|
|
1255
|
-
const baseScope = { ...componentScope, ...(elementScope || {})
|
|
1285
|
+
const baseScope = { ...globalHelpers, ...componentScope, ...(elementScope || {}) };
|
|
1256
1286
|
|
|
1257
1287
|
// Check for pipe syntax (Feature #2)
|
|
1258
1288
|
const pipeResult = parsePipeExpression(expr, baseScope);
|
|
1259
1289
|
if (pipeResult) {
|
|
1260
1290
|
// Use auto-unwrap proxy for pipe expressions
|
|
1261
|
-
const unwrapScope = enableAutoUnwrap
|
|
1291
|
+
const unwrapScope = enableAutoUnwrap
|
|
1292
|
+
? createAutoUnwrapProxy(baseScope, function(prop) {
|
|
1293
|
+
if (typeof prop !== 'string') return false;
|
|
1294
|
+
return expressionCallsSignal(expr, prop)
|
|
1295
|
+
|| expressionUsesSignalApi(expr, prop);
|
|
1296
|
+
})
|
|
1297
|
+
: baseScope;
|
|
1262
1298
|
const piped = executePipeExpression(pipeResult.valueExpr, pipeResult.pipes, unwrapScope);
|
|
1263
1299
|
return (piped && typeof piped === 'function' && (piped._isSignal || piped._isDerived)) ? piped() : piped;
|
|
1264
1300
|
}
|
|
1265
1301
|
|
|
1266
1302
|
// Use auto-unwrap proxy if enabled (Feature #1)
|
|
1267
|
-
const scope = enableAutoUnwrap
|
|
1303
|
+
const scope = enableAutoUnwrap
|
|
1304
|
+
? createAutoUnwrapProxy(baseScope, function(prop) {
|
|
1305
|
+
if (typeof prop !== 'string') return false;
|
|
1306
|
+
return expressionCallsSignal(expr, prop)
|
|
1307
|
+
|| expressionUsesSignalApi(expr, prop);
|
|
1308
|
+
})
|
|
1309
|
+
: baseScope;
|
|
1268
1310
|
const fn = new Function(...Object.keys(baseScope), 'return ' + expr);
|
|
1269
1311
|
const result = fn(...Object.values(scope));
|
|
1270
1312
|
// Post-eval unwrap \u2014 see evalAttrExpr note for the reason.
|
|
@@ -1364,6 +1406,50 @@ catch (e) {
|
|
|
1364
1406
|
return null;
|
|
1365
1407
|
}
|
|
1366
1408
|
|
|
1409
|
+
function expressionUsesSignalApi(expression, name) {
|
|
1410
|
+
var escapedName = name.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');
|
|
1411
|
+
return expressionUsesSignalMethod(expression, name)
|
|
1412
|
+
|| expressionUsesSignalValue(expression, name);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
function expressionUsesSignalMethod(expression, name) {
|
|
1416
|
+
var escapedName = name.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');
|
|
1417
|
+
return new RegExp('(?:^|[^\\\\w$])' + escapedName + '\\\\s*\\\\.\\\\s*(?:set|update|subscribe)\\\\s*\\\\(').test(expression);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function expressionUsesSignalValue(expression, name) {
|
|
1421
|
+
var escapedName = name.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');
|
|
1422
|
+
return new RegExp('(?:^|[^\\\\w$])' + escapedName + '\\\\s*\\\\.\\\\s*value\\\\b').test(expression);
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function expressionCallsSignal(expression, name) {
|
|
1426
|
+
var escapedName = name.replace(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&');
|
|
1427
|
+
return new RegExp('(?:^|[^\\\\w$])' + escapedName + '\\\\s*\\\\(').test(expression);
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function createExpressionAutoUnwrapProxy(scope, expression) {
|
|
1431
|
+
return createAutoUnwrapProxy(scope, function(prop, signal) {
|
|
1432
|
+
if (typeof prop !== 'string') return false;
|
|
1433
|
+
if (expressionCallsSignal(expression, prop) || expressionUsesSignalMethod(expression, prop))
|
|
1434
|
+
return true;
|
|
1435
|
+
if (!expressionUsesSignalValue(expression, prop))
|
|
1436
|
+
return false;
|
|
1437
|
+
|
|
1438
|
+
// A :for item is itself represented by a signal. If that item's value
|
|
1439
|
+
// is an object with a real value field, item.value means the field,
|
|
1440
|
+
// not the signal wrapper API. Primitive signals still support
|
|
1441
|
+
// count.value, matching the explicit ref syntax.
|
|
1442
|
+
try {
|
|
1443
|
+
var currentValue = signal();
|
|
1444
|
+
return !(currentValue && typeof currentValue === 'object'
|
|
1445
|
+
&& Object.prototype.hasOwnProperty.call(currentValue, 'value'));
|
|
1446
|
+
}
|
|
1447
|
+
catch (e) {
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1367
1453
|
function executeHandler(expr, event, el) {
|
|
1368
1454
|
try {
|
|
1369
1455
|
// Skip placeholder expressions like __TITLE__ (build-time placeholders)
|
|
@@ -1372,7 +1458,7 @@ catch (e) {
|
|
|
1372
1458
|
}
|
|
1373
1459
|
// First try component-level scope
|
|
1374
1460
|
const elementScope = findElementScope(el || currentElement);
|
|
1375
|
-
const scope = { ...componentScope, ...(elementScope || {})
|
|
1461
|
+
const scope = { ...globalHelpers, ...componentScope, ...(elementScope || {}) };
|
|
1376
1462
|
|
|
1377
1463
|
// Check for shorthand syntax (Feature #8)
|
|
1378
1464
|
const shorthandFn = parseEventShorthand(expr, scope);
|
|
@@ -1534,6 +1620,111 @@ catch (e) {
|
|
|
1534
1620
|
processElement(el, scope);
|
|
1535
1621
|
}
|
|
1536
1622
|
|
|
1623
|
+
function isForDirectiveExpression(expr) {
|
|
1624
|
+
return typeof expr === 'string'
|
|
1625
|
+
&& /^\\s*\\(?\\s*\\w+(?:\\s*,\\s*\\w+)?\\s*\\)?\\s+(?:in|of)\\s+.+\\s*$/.test(expr);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
function getModelBinding(el) {
|
|
1629
|
+
if (!el || !el.attributes) return null;
|
|
1630
|
+
var prefixes = ['x-model', ':model', '@model'];
|
|
1631
|
+
var attrs = Array.from(el.attributes);
|
|
1632
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
1633
|
+
var name = attrs[i] && attrs[i].name;
|
|
1634
|
+
if (!name) continue;
|
|
1635
|
+
for (var j = 0; j < prefixes.length; j++) {
|
|
1636
|
+
if (name === prefixes[j] || name.indexOf(prefixes[j] + '.') === 0)
|
|
1637
|
+
return { name: name, expression: attrs[i].value };
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
function bindParentComponentProps(el, callerScope) {
|
|
1644
|
+
if (!el || !el.getAttribute || el.__stx_parent_props_bound) return;
|
|
1645
|
+
var names = (el.getAttribute('data-stx-parent-bindings') || '').split(/\\s+/).filter(Boolean);
|
|
1646
|
+
if (!names.length) return;
|
|
1647
|
+
el.__stx_parent_props_bound = true;
|
|
1648
|
+
var scope = { ...globalHelpers, ...callerScope };
|
|
1649
|
+
|
|
1650
|
+
names.forEach(function(name) {
|
|
1651
|
+
var sourceName = ':' + name;
|
|
1652
|
+
if (!el.hasAttribute(sourceName)) return;
|
|
1653
|
+
var expression = el.getAttribute(sourceName);
|
|
1654
|
+
effect(function() {
|
|
1655
|
+
var value;
|
|
1656
|
+
try {
|
|
1657
|
+
// Support both signal styles in forwarded props:
|
|
1658
|
+
// :open="open" and :open="open()".
|
|
1659
|
+
// Auto-unwrap bare signals, but preserve a signal function when this
|
|
1660
|
+
// expression explicitly calls it or accesses its object API.
|
|
1661
|
+
var unwrapScope = createAutoUnwrapProxy(scope, function(prop) {
|
|
1662
|
+
if (typeof prop !== 'string') return false;
|
|
1663
|
+
return expressionCallsSignal(expression, prop)
|
|
1664
|
+
|| expressionUsesSignalApi(expression, prop);
|
|
1665
|
+
});
|
|
1666
|
+
var fn = new Function('__scope__', 'with(__scope__) { return (' + expression + ') }');
|
|
1667
|
+
value = fn(unwrapScope);
|
|
1668
|
+
if (value && typeof value === 'function' && (value._isSignal || value._isDerived)) value = value();
|
|
1669
|
+
}
|
|
1670
|
+
catch (e) {
|
|
1671
|
+
if (!(e instanceof ReferenceError) && !(e instanceof TypeError))
|
|
1672
|
+
console.warn('[STX] Parent component prop error:', expression, e);
|
|
1673
|
+
value = undefined;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
if (value === false || value === null || value === undefined) {
|
|
1677
|
+
el.removeAttribute(name);
|
|
1678
|
+
}
|
|
1679
|
+
else if (value === true) {
|
|
1680
|
+
el.setAttribute(name, '');
|
|
1681
|
+
}
|
|
1682
|
+
else {
|
|
1683
|
+
var serialized = value;
|
|
1684
|
+
if (typeof value === 'object') {
|
|
1685
|
+
try { serialized = JSON.stringify(value); }
|
|
1686
|
+
catch (e) { serialized = String(value); }
|
|
1687
|
+
}
|
|
1688
|
+
el.setAttribute(name, serialized);
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
el.removeAttribute(sourceName);
|
|
1692
|
+
});
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function resolveComponentCallerScope(el, pageScope) {
|
|
1696
|
+
var resolved = { ...pageScope };
|
|
1697
|
+
var ancestors = [];
|
|
1698
|
+
var current = el && (el.parentElement || el.parentNode);
|
|
1699
|
+
while (current && current !== document) {
|
|
1700
|
+
if (current.getAttribute && current.hasAttribute('data-stx-scope'))
|
|
1701
|
+
ancestors.push(current.getAttribute('data-stx-scope'));
|
|
1702
|
+
current = current.parentElement || current.parentNode;
|
|
1703
|
+
}
|
|
1704
|
+
ancestors.reverse().forEach(function(scopeId) {
|
|
1705
|
+
var ancestorScope = window.stx._scopes && window.stx._scopes[scopeId];
|
|
1706
|
+
if (ancestorScope) Object.assign(resolved, ancestorScope);
|
|
1707
|
+
});
|
|
1708
|
+
return resolved;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// A false :if detaches its subtree before the DOMContentLoaded scope walk
|
|
1712
|
+
// reaches nested components. Capture and bind their caller-owned props while
|
|
1713
|
+
// the ancestor chain is still connected, otherwise resolveComponentCallerScope
|
|
1714
|
+
// can no longer see the component that declared expressions such as
|
|
1715
|
+
// :options="environmentOptions".
|
|
1716
|
+
function preserveConditionalComponentScopes(root, callerScope) {
|
|
1717
|
+
if (!root || !root.querySelectorAll) return;
|
|
1718
|
+
var scopeEls = [];
|
|
1719
|
+
if (root.matches && root.matches('[data-stx-scope]')) scopeEls.push(root);
|
|
1720
|
+
root.querySelectorAll('[data-stx-scope]').forEach(function(scopeEl) { scopeEls.push(scopeEl); });
|
|
1721
|
+
scopeEls.forEach(function(scopeEl) {
|
|
1722
|
+
if (scopeEl.__stx_parent_props_bound) return;
|
|
1723
|
+
scopeEl.__stx_parent_scope = resolveComponentCallerScope(scopeEl, callerScope);
|
|
1724
|
+
bindParentComponentProps(scopeEl, scopeEl.__stx_parent_scope);
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1537
1728
|
function processElement(el, scope = componentScope) {
|
|
1538
1729
|
// Debug: log every element with x-class or @click
|
|
1539
1730
|
if (el.nodeType === Node.ELEMENT_NODE && el.hasAttribute) {
|
|
@@ -1566,7 +1757,7 @@ catch (e) {
|
|
|
1566
1757
|
if (el.hasAttribute('data-stx-memo')) {
|
|
1567
1758
|
var memoExpr = el.getAttribute('data-stx-memo');
|
|
1568
1759
|
try {
|
|
1569
|
-
var memoScope = { ...scope, ...(findElementScope(el) || {})
|
|
1760
|
+
var memoScope = { ...globalHelpers, ...scope, ...(findElementScope(el) || {}) };
|
|
1570
1761
|
var memoFn = new Function(...Object.keys(memoScope), 'return ' + memoExpr);
|
|
1571
1762
|
var memoVals = JSON.stringify(memoFn(...Object.values(memoScope)));
|
|
1572
1763
|
if (el.__stx_memo_prev === memoVals) {
|
|
@@ -1586,7 +1777,7 @@ catch (e) {
|
|
|
1586
1777
|
// Capture scope NOW before effects run asynchronously
|
|
1587
1778
|
// Use the passed scope parameter (not global componentScope) to preserve context
|
|
1588
1779
|
// through nested @if/@for processing where componentScope may be restored
|
|
1589
|
-
const capturedScope = { ...scope, ...(findElementScope(parentEl) || {})
|
|
1780
|
+
const capturedScope = { ...globalHelpers, ...scope, ...(findElementScope(parentEl) || {}) };
|
|
1590
1781
|
parts.forEach(part => {
|
|
1591
1782
|
const match = part.match(/^\\{\\{\\s*([\\s\\S]+?)\\s*\\}\\}$/);
|
|
1592
1783
|
if (match) {
|
|
@@ -1596,22 +1787,29 @@ catch (e) {
|
|
|
1596
1787
|
fragment.appendChild(document.createTextNode(part));
|
|
1597
1788
|
return;
|
|
1598
1789
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1790
|
+
// Native text-only elements cannot contain the span wrapper used
|
|
1791
|
+
// for general interpolation. Bind a text node there so options,
|
|
1792
|
+
// titles, and textareas retain valid DOM and accessible labels.
|
|
1793
|
+
const isTextOnlyParent = parentEl
|
|
1794
|
+
&& (parentEl.tagName === 'OPTION' || parentEl.tagName === 'TITLE' || parentEl.tagName === 'TEXTAREA');
|
|
1795
|
+
const bindingNode = isTextOnlyParent
|
|
1796
|
+
? document.createTextNode('')
|
|
1797
|
+
: document.createElement('span');
|
|
1798
|
+
fragment.appendChild(bindingNode);
|
|
1601
1799
|
// Use captured scope, not dynamic lookup
|
|
1602
1800
|
effect(() => {
|
|
1603
1801
|
try {
|
|
1604
1802
|
// Check for pipe syntax (Feature #2)
|
|
1605
1803
|
const pipeResult = parsePipeExpression(expr, capturedScope);
|
|
1606
1804
|
if (pipeResult) {
|
|
1607
|
-
const unwrapScope =
|
|
1608
|
-
|
|
1805
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(capturedScope, expr);
|
|
1806
|
+
bindingNode.textContent = executePipeExpression(pipeResult.valueExpr, pipeResult.pipes, unwrapScope);
|
|
1609
1807
|
}
|
|
1610
1808
|
else {
|
|
1611
1809
|
// Use auto-unwrap proxy (Feature #1)
|
|
1612
|
-
const unwrapScope =
|
|
1810
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(capturedScope, expr);
|
|
1613
1811
|
const fn = new Function(...Object.keys(capturedScope), 'return ' + expr);
|
|
1614
|
-
|
|
1812
|
+
bindingNode.textContent = fn(...Object.values(unwrapScope));
|
|
1615
1813
|
}
|
|
1616
1814
|
}
|
|
1617
1815
|
catch (e) {
|
|
@@ -1620,11 +1818,11 @@ catch (e) {
|
|
|
1620
1818
|
// Retry without auto-unwrap so signal functions remain callable.
|
|
1621
1819
|
try {
|
|
1622
1820
|
const fn = new Function(...Object.keys(capturedScope), 'return ' + expr);
|
|
1623
|
-
|
|
1821
|
+
bindingNode.textContent = fn(...Object.values(capturedScope));
|
|
1624
1822
|
}
|
|
1625
1823
|
catch (e2) {
|
|
1626
1824
|
if (!(e2 instanceof ReferenceError) && !(e2 instanceof TypeError)) console.warn('[STX] Expression error:', expr, e2);
|
|
1627
|
-
|
|
1825
|
+
bindingNode.textContent = '';
|
|
1628
1826
|
}
|
|
1629
1827
|
}
|
|
1630
1828
|
});
|
|
@@ -1652,8 +1850,9 @@ else if (part) {
|
|
|
1652
1850
|
}
|
|
1653
1851
|
|
|
1654
1852
|
// Handle @for / :for / x-for first (reactive list)
|
|
1655
|
-
|
|
1656
|
-
|
|
1853
|
+
var hasColonForLoop = el.hasAttribute(':for') && isForDirectiveExpression(el.getAttribute(':for'));
|
|
1854
|
+
if (el.hasAttribute('@for') || hasColonForLoop || el.hasAttribute('x-for')) {
|
|
1855
|
+
var forAttr = hasColonForLoop ? ':for' : el.hasAttribute('x-for') ? 'x-for' : '@for';
|
|
1657
1856
|
bindFor(el, scope, forAttr);
|
|
1658
1857
|
return;
|
|
1659
1858
|
}
|
|
@@ -1697,14 +1896,14 @@ else if (part) {
|
|
|
1697
1896
|
}
|
|
1698
1897
|
|
|
1699
1898
|
// Handle @model / :model / x-model (two-way binding)
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
bindModel(el,
|
|
1899
|
+
var modelBinding = getModelBinding(el);
|
|
1900
|
+
if (modelBinding) {
|
|
1901
|
+
bindModel(el, modelBinding.expression, scope, modelBinding.name);
|
|
1703
1902
|
}
|
|
1704
1903
|
|
|
1705
1904
|
// Capture scope once for all attribute bindings on this element
|
|
1706
1905
|
// Use the passed scope parameter to preserve context through nested processing
|
|
1707
|
-
const attrCapturedScope = { ...scope, ...(findElementScope(el) || {})
|
|
1906
|
+
const attrCapturedScope = { ...globalHelpers, ...scope, ...(findElementScope(el) || {}) };
|
|
1708
1907
|
|
|
1709
1908
|
// Post-eval unwrap: if the expression result is a signal function (e.g.
|
|
1710
1909
|
// step was resolved via identifier lookup, not via the auto-unwrap
|
|
@@ -1717,7 +1916,8 @@ else if (part) {
|
|
|
1717
1916
|
if (v && typeof v === 'function' && (v._isSignal || v._isDerived)) return v();
|
|
1718
1917
|
return v;
|
|
1719
1918
|
};
|
|
1720
|
-
const evalAttrExpr = (rawExpr) => {
|
|
1919
|
+
const evalAttrExpr = (rawExpr, evaluationScope) => {
|
|
1920
|
+
const activeScope = evaluationScope || attrCapturedScope;
|
|
1721
1921
|
// Decode HTML entities that browsers may encode in attribute values
|
|
1722
1922
|
// e.g. :text="a > b" may be stored as :text="a > b" in HTML
|
|
1723
1923
|
var expr = rawExpr.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,"'").replace(/'/g,"'");
|
|
@@ -1725,23 +1925,23 @@ else if (part) {
|
|
|
1725
1925
|
if (/^__[A-Z_]+__$/.test(expr.trim())) return expr;
|
|
1726
1926
|
|
|
1727
1927
|
// Check for pipe syntax (Feature #2)
|
|
1728
|
-
const pipeResult = parsePipeExpression(expr,
|
|
1928
|
+
const pipeResult = parsePipeExpression(expr, activeScope);
|
|
1729
1929
|
if (pipeResult) {
|
|
1730
|
-
const unwrapScope =
|
|
1930
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(activeScope, expr);
|
|
1731
1931
|
return maybeUnwrapSignal(executePipeExpression(pipeResult.valueExpr, pipeResult.pipes, unwrapScope));
|
|
1732
1932
|
}
|
|
1733
1933
|
|
|
1734
1934
|
// Use auto-unwrap proxy (Feature #1)
|
|
1735
|
-
const unwrapScope =
|
|
1736
|
-
const fn = new Function(
|
|
1737
|
-
return maybeUnwrapSignal(fn(
|
|
1935
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(activeScope, expr);
|
|
1936
|
+
const fn = new Function('__scope__', 'with(__scope__) { return (' + expr + ') }');
|
|
1937
|
+
return maybeUnwrapSignal(fn(unwrapScope));
|
|
1738
1938
|
}
|
|
1739
1939
|
catch (e) {
|
|
1740
1940
|
// Auto-unwrap can break explicit signal calls like errorData().message
|
|
1741
1941
|
// Retry without auto-unwrap so signal functions remain callable
|
|
1742
1942
|
try {
|
|
1743
|
-
const fn = new Function(
|
|
1744
|
-
return maybeUnwrapSignal(fn(
|
|
1943
|
+
const fn = new Function('__scope__', 'with(__scope__) { return (' + expr + ') }');
|
|
1944
|
+
return maybeUnwrapSignal(fn(activeScope));
|
|
1745
1945
|
}
|
|
1746
1946
|
catch (e2) {
|
|
1747
1947
|
if (!(e2 instanceof ReferenceError) && !(e2 instanceof TypeError)) console.warn('[STX] Attribute expression error:', expr, e2);
|
|
@@ -1751,7 +1951,7 @@ catch (e2) {
|
|
|
1751
1951
|
};
|
|
1752
1952
|
|
|
1753
1953
|
// Known directive names to exclude from generic :attr binding
|
|
1754
|
-
var DIRECTIVE_NAMES = {class:1, style:1, text:1, html:1, show:1, model:1, 'if':1,
|
|
1954
|
+
var DIRECTIVE_NAMES = {class:1, style:1, text:1, html:1, show:1, model:1, 'if':1, ref:1};
|
|
1755
1955
|
// SVG attributes are case-sensitive, but the HTML parser lowercases
|
|
1756
1956
|
// prefixed attribute names \u2014 ':viewBox' arrives as ':viewbox' because
|
|
1757
1957
|
// the spec's "adjust SVG attributes" step only covers unprefixed names.
|
|
@@ -1785,8 +1985,27 @@ catch (e2) {
|
|
|
1785
1985
|
|| (name.startsWith('x-') && !X_HANDLED[name.split('.')[0]] && !X_HANDLED[name])) {
|
|
1786
1986
|
let attrName = name.startsWith('@bind:') ? name.slice(6) : name.startsWith('x-bind:') ? name.slice(7) : name.startsWith('x-') ? name.slice(2) : name.slice(1);
|
|
1787
1987
|
if (el.namespaceURI === SVG_NS && SVG_ATTR_CASE[attrName]) attrName = SVG_ATTR_CASE[attrName];
|
|
1988
|
+
var parentBindingNames = (el.getAttribute && el.getAttribute('data-stx-parent-bindings') || '').split(/\\s+/);
|
|
1989
|
+
var bindingScope = el.__stx_parent_scope && parentBindingNames.includes(attrName)
|
|
1990
|
+
? el.__stx_parent_scope
|
|
1991
|
+
: attrCapturedScope;
|
|
1788
1992
|
effect(() => {
|
|
1789
|
-
const v = evalAttrExpr(value);
|
|
1993
|
+
const v = evalAttrExpr(value, bindingScope);
|
|
1994
|
+
// Form control values are live DOM properties. Setting only the HTML
|
|
1995
|
+
// attribute does not update a <select>'s chosen option and can leave
|
|
1996
|
+
// inputs displaying stale text after a reactive update.
|
|
1997
|
+
if (attrName === 'value' && 'value' in el) {
|
|
1998
|
+
var controlValue = v;
|
|
1999
|
+
if (typeof v === 'object' && v !== null) {
|
|
2000
|
+
try { controlValue = JSON.stringify(v); }
|
|
2001
|
+
catch (e) { controlValue = String(v); }
|
|
2002
|
+
}
|
|
2003
|
+
controlValue = v === false || v === null || v === undefined ? '' : String(controlValue);
|
|
2004
|
+
el.value = controlValue;
|
|
2005
|
+
if (controlValue === '' && el.tagName !== 'OPTION') el.removeAttribute(attrName);
|
|
2006
|
+
else el.setAttribute(attrName, controlValue);
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
1790
2009
|
if (v === false || v === null || v === undefined) {
|
|
1791
2010
|
el.removeAttribute(attrName);
|
|
1792
2011
|
}
|
|
@@ -1794,7 +2013,12 @@ else if (v === true) {
|
|
|
1794
2013
|
el.setAttribute(attrName, '');
|
|
1795
2014
|
}
|
|
1796
2015
|
else {
|
|
1797
|
-
|
|
2016
|
+
var attrValue = v;
|
|
2017
|
+
if (typeof v === 'object') {
|
|
2018
|
+
try { attrValue = JSON.stringify(v); }
|
|
2019
|
+
catch (e) { attrValue = String(v); }
|
|
2020
|
+
}
|
|
2021
|
+
el.setAttribute(attrName, attrValue);
|
|
1798
2022
|
}
|
|
1799
2023
|
});
|
|
1800
2024
|
el.removeAttribute(name);
|
|
@@ -1827,7 +2051,7 @@ else if (name === 'ref' || name === ':ref' || name === 'x-ref' || name === 'data
|
|
|
1827
2051
|
if (scope.$refs) scope.$refs[value] = el;
|
|
1828
2052
|
if (componentScope.$refs) componentScope.$refs[value] = el;
|
|
1829
2053
|
}
|
|
1830
|
-
else if (name.startsWith('@') || name.startsWith(':')) {
|
|
2054
|
+
else if (name.startsWith('@') || name.startsWith(':')) {
|
|
1831
2055
|
// Event handlers: @click, :click, @submit.prevent, :keydown.enter, etc.
|
|
1832
2056
|
const parts = name.slice(1).split('.');
|
|
1833
2057
|
const eventName = parts[0];
|
|
@@ -1844,11 +2068,28 @@ else if (name.startsWith('@') || name.startsWith(':')) {
|
|
|
1844
2068
|
el[eventKey] = true;
|
|
1845
2069
|
|
|
1846
2070
|
// Capture scope at setup time so @for loop variables are available when event fires
|
|
1847
|
-
const
|
|
2071
|
+
const parentEventNames = (el.getAttribute && el.getAttribute('data-stx-parent-events') || '').split(/\\s+/);
|
|
2072
|
+
const isForwardedComponentEvent = !!(el.__stx_parent_scope && parentEventNames.includes(eventName));
|
|
2073
|
+
const eventCapturedScope = isForwardedComponentEvent
|
|
2074
|
+
? { ...globalHelpers, ...el.__stx_parent_scope }
|
|
2075
|
+
: { ...globalHelpers, ...scope, ...(findElementScope(el) || {}) };
|
|
1848
2076
|
|
|
1849
2077
|
console.log('[stx] binding event:', eventName, 'on', el.tagName, 'expr:', value.substring(0, 40));
|
|
1850
2078
|
el.addEventListener(eventName, (event) => {
|
|
1851
2079
|
console.log('[stx] event fired:', eventName, 'on', el.tagName);
|
|
2080
|
+
// A component listener belongs to the component root. Ignore native
|
|
2081
|
+
// events bubbling out of its internal markup, otherwise a form
|
|
2082
|
+
// component with @submit fires the parent once for the native form
|
|
2083
|
+
// submit and again for defineEmits('submit').
|
|
2084
|
+
if (isForwardedComponentEvent && event.target !== el) return;
|
|
2085
|
+
|
|
2086
|
+
// Vue-style component handlers receive the emitted payload directly.
|
|
2087
|
+
// Native root events still receive the Event object so server-only
|
|
2088
|
+
// components can forward clicks and other browser events.
|
|
2089
|
+
var handlerEvent = isForwardedComponentEvent && event instanceof CustomEvent
|
|
2090
|
+
? event.detail
|
|
2091
|
+
: event;
|
|
2092
|
+
|
|
1852
2093
|
// Skip clicks on elements that just became visible in this frame.
|
|
1853
2094
|
// Prevents modal backdrop from catching the click that opened the modal.
|
|
1854
2095
|
if (eventName === 'click') {
|
|
@@ -1876,7 +2117,7 @@ else if (name.startsWith('@') || name.startsWith(':')) {
|
|
|
1876
2117
|
try {
|
|
1877
2118
|
if (!value || /^__[A-Z_]+__$/.test(value.trim())) return;
|
|
1878
2119
|
var shorthandFn = parseEventShorthand(value, eventCapturedScope);
|
|
1879
|
-
if (shorthandFn) { shorthandFn(
|
|
2120
|
+
if (shorthandFn) { shorthandFn(handlerEvent); return; }
|
|
1880
2121
|
// Check if any scope values are signals (x-data pattern)
|
|
1881
2122
|
var hasSignals = Object.values(eventCapturedScope).some(function(v) {
|
|
1882
2123
|
return v && typeof v === 'function' && v._isSignal;
|
|
@@ -1898,23 +2139,23 @@ else if (name.startsWith('@') || name.startsWith(':')) {
|
|
|
1898
2139
|
}).join(';');
|
|
1899
2140
|
var body = getVars + ';' + value + ';' + setVars;
|
|
1900
2141
|
var fn2 = new Function('__s', '$event', body);
|
|
1901
|
-
fn2(eventCapturedScope,
|
|
2142
|
+
fn2(eventCapturedScope, handlerEvent);
|
|
1902
2143
|
} else if (hasSignals) {
|
|
1903
|
-
// Function
|
|
1904
|
-
//
|
|
2144
|
+
// Function calls with signals in scope keep any signal used through
|
|
2145
|
+
// its object API as a raw signal function, and unwrap the rest for
|
|
2146
|
+
// primitive reads.
|
|
1905
2147
|
var unwrapVars = Object.keys(eventCapturedScope).map(function(k) {
|
|
1906
2148
|
var v = eventCapturedScope[k];
|
|
1907
|
-
|
|
1908
|
-
if (v && typeof v === 'function' && v._isSignal && value.includes(k + '.set(')) {
|
|
2149
|
+
if (v && typeof v === 'function' && v._isSignal && expressionUsesSignalApi(value, k)) {
|
|
1909
2150
|
return 'var ' + k + ' = __s["' + k + '"]';
|
|
1910
2151
|
}
|
|
1911
2152
|
return 'var ' + k + ' = __s["' + k + '"] && typeof __s["' + k + '"] === "function" && __s["' + k + '"]._isSignal ? __s["' + k + '"]() : __s["' + k + '"]';
|
|
1912
2153
|
}).join(';');
|
|
1913
2154
|
var fn3 = new Function('__s', '$event', unwrapVars + ';' + value);
|
|
1914
|
-
fn3(eventCapturedScope,
|
|
2155
|
+
fn3(eventCapturedScope, handlerEvent);
|
|
1915
2156
|
} else {
|
|
1916
2157
|
var fn = new Function(...Object.keys(eventCapturedScope), '$event', value);
|
|
1917
|
-
fn(...Object.values(eventCapturedScope),
|
|
2158
|
+
fn(...Object.values(eventCapturedScope), handlerEvent);
|
|
1918
2159
|
}
|
|
1919
2160
|
}
|
|
1920
2161
|
catch (e) {
|
|
@@ -2015,7 +2256,7 @@ catch (e) {
|
|
|
2015
2256
|
|
|
2016
2257
|
const currentDisplay = el.style.display;
|
|
2017
2258
|
const originalDisplay = (currentDisplay && currentDisplay !== 'none') ? currentDisplay : '';
|
|
2018
|
-
const capturedScope = { ...passedScope, ...(findElementScope(el) || {})
|
|
2259
|
+
const capturedScope = { ...globalHelpers, ...passedScope, ...(findElementScope(el) || {}) };
|
|
2019
2260
|
|
|
2020
2261
|
// Check if the expression is a simple signal reference (most common case for :show)
|
|
2021
2262
|
const directSignal = capturedScope[expr];
|
|
@@ -2035,7 +2276,7 @@ catch (e) {
|
|
|
2035
2276
|
effect(() => {
|
|
2036
2277
|
var value;
|
|
2037
2278
|
try {
|
|
2038
|
-
var unwrapScope =
|
|
2279
|
+
var unwrapScope = createExpressionAutoUnwrapProxy(capturedScope, expr);
|
|
2039
2280
|
var fn = new Function(...Object.keys(capturedScope), 'return ' + expr);
|
|
2040
2281
|
value = fn(...Object.values(unwrapScope));
|
|
2041
2282
|
} catch (e1) {
|
|
@@ -2071,20 +2312,40 @@ catch (e) {
|
|
|
2071
2312
|
el.__stx_model_bound = true;
|
|
2072
2313
|
const tag = el.tagName.toLowerCase();
|
|
2073
2314
|
const type = el.type;
|
|
2315
|
+
const modifiers = attrName.split('.').slice(1);
|
|
2316
|
+
const capturedScope = { ...globalHelpers, ...passedScope, ...(findElementScope(el) || {}) };
|
|
2317
|
+
const coerceValue = (value) => {
|
|
2318
|
+
var next = value;
|
|
2319
|
+
if (modifiers.includes('trim') && typeof next === 'string') next = next.trim();
|
|
2320
|
+
if (modifiers.includes('number') && next !== '') {
|
|
2321
|
+
var numeric = Number(next);
|
|
2322
|
+
if (!Number.isNaN(numeric)) next = numeric;
|
|
2323
|
+
}
|
|
2324
|
+
return next;
|
|
2325
|
+
};
|
|
2074
2326
|
|
|
2075
|
-
const getValue = () =>
|
|
2327
|
+
const getValue = () => {
|
|
2328
|
+
var direct = capturedScope[expr];
|
|
2329
|
+
if (direct && typeof direct === 'function' && (direct._isSignal || direct._isDerived))
|
|
2330
|
+
return direct();
|
|
2331
|
+
try {
|
|
2332
|
+
var unwrapScope = createExpressionAutoUnwrapProxy(capturedScope, expr);
|
|
2333
|
+
var fn = new Function('__scope__', 'with(__scope__) { return (' + expr + ') }');
|
|
2334
|
+
return fn(unwrapScope);
|
|
2335
|
+
}
|
|
2336
|
+
catch (e) {
|
|
2337
|
+
if (!(e instanceof ReferenceError) && !(e instanceof TypeError)) console.warn('[STX] ' + attrName + ' get error:', expr, e);
|
|
2338
|
+
return '';
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2076
2341
|
const setValue = (val) => {
|
|
2077
2342
|
try {
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const scope = { ...passedScope, ...(elementScope || {}) };
|
|
2081
|
-
|
|
2082
|
-
if (scope[expr] && scope[expr]._isSignal) {
|
|
2083
|
-
scope[expr].set(val);
|
|
2343
|
+
if (capturedScope[expr] && capturedScope[expr]._isSignal) {
|
|
2344
|
+
capturedScope[expr].set(val);
|
|
2084
2345
|
}
|
|
2085
2346
|
else {
|
|
2086
|
-
const fn = new Function(...Object.keys(
|
|
2087
|
-
fn(...Object.values(
|
|
2347
|
+
const fn = new Function(...Object.keys(capturedScope), 'v', expr + ' = v');
|
|
2348
|
+
fn(...Object.values(capturedScope), val);
|
|
2088
2349
|
}
|
|
2089
2350
|
}
|
|
2090
2351
|
catch (e) {
|
|
@@ -2096,13 +2357,13 @@ catch (e) {
|
|
|
2096
2357
|
effect(() => { el.checked = getValue(); });
|
|
2097
2358
|
el.addEventListener('change', () => setValue(el.checked));
|
|
2098
2359
|
}
|
|
2099
|
-
else if (tag === 'select') {
|
|
2360
|
+
else if (tag === 'select') {
|
|
2100
2361
|
effect(() => { el.value = getValue(); });
|
|
2101
|
-
el.addEventListener('change', () => setValue(el.value));
|
|
2362
|
+
el.addEventListener('change', () => setValue(coerceValue(el.value)));
|
|
2102
2363
|
}
|
|
2103
2364
|
else {
|
|
2104
2365
|
effect(() => { el.value = getValue() ?? ''; });
|
|
2105
|
-
el.addEventListener('input', () => setValue(el.value));
|
|
2366
|
+
el.addEventListener(modifiers.includes('lazy') ? 'change' : 'input', () => setValue(coerceValue(el.value)));
|
|
2106
2367
|
}
|
|
2107
2368
|
|
|
2108
2369
|
el.removeAttribute(attrName);
|
|
@@ -2110,7 +2371,7 @@ else {
|
|
|
2110
2371
|
|
|
2111
2372
|
function bindClass(el, expr, passedScope = componentScope) {
|
|
2112
2373
|
const originalClasses = el.className;
|
|
2113
|
-
const capturedScope = { ...passedScope, ...(findElementScope(el) || {})
|
|
2374
|
+
const capturedScope = { ...globalHelpers, ...passedScope, ...(findElementScope(el) || {}) };
|
|
2114
2375
|
const keys = Object.keys(capturedScope);
|
|
2115
2376
|
|
|
2116
2377
|
// Pre-compile \u2014 filter out keys that aren't valid JS identifiers
|
|
@@ -2140,7 +2401,7 @@ else {
|
|
|
2140
2401
|
// First pass: auto-unwrap proxy \u2014 handles expressions that read
|
|
2141
2402
|
// signals as primitives (e.g. { 'active': count > 5 } where count
|
|
2142
2403
|
// is a signal).
|
|
2143
|
-
const unwrapScope =
|
|
2404
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(capturedScope, expr);
|
|
2144
2405
|
value = fn.apply(null, safeKeys.map(k => unwrapScope[k]));
|
|
2145
2406
|
} catch (e1) {
|
|
2146
2407
|
// Retry with raw scope \u2014 handles call-expressions like activeTab()
|
|
@@ -2163,11 +2424,11 @@ else {
|
|
|
2163
2424
|
});
|
|
2164
2425
|
}
|
|
2165
2426
|
else if (Array.isArray(value)) {
|
|
2166
|
-
var _clsArr = originalClasses
|
|
2427
|
+
var _clsArr = [originalClasses, value.filter(Boolean).join(' ')].filter(Boolean).join(' ');
|
|
2167
2428
|
if (typeof el.className === 'string') el.className = _clsArr; else el.setAttribute('class', _clsArr);
|
|
2168
2429
|
}
|
|
2169
2430
|
else {
|
|
2170
|
-
var _clsStr = originalClasses
|
|
2431
|
+
var _clsStr = [originalClasses, value || ''].filter(Boolean).join(' ');
|
|
2171
2432
|
if (typeof el.className === 'string') el.className = _clsStr; else el.setAttribute('class', _clsStr);
|
|
2172
2433
|
}
|
|
2173
2434
|
});
|
|
@@ -2175,7 +2436,7 @@ else {
|
|
|
2175
2436
|
|
|
2176
2437
|
function bindStyle(el, expr, passedScope = componentScope) {
|
|
2177
2438
|
// Capture scope at setup time - use passed scope to preserve context
|
|
2178
|
-
const capturedScope = { ...passedScope, ...(findElementScope(el) || {})
|
|
2439
|
+
const capturedScope = { ...globalHelpers, ...passedScope, ...(findElementScope(el) || {}) };
|
|
2179
2440
|
|
|
2180
2441
|
const evalExpr = () => {
|
|
2181
2442
|
// Mirror bindClass: first try the auto-unwrap proxy (so an expression
|
|
@@ -2185,7 +2446,7 @@ else {
|
|
|
2185
2446
|
// values, which would make theme() try to call a primitive and
|
|
2186
2447
|
// throw TypeError.
|
|
2187
2448
|
try {
|
|
2188
|
-
const unwrapScope =
|
|
2449
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(capturedScope, expr);
|
|
2189
2450
|
const fn = new Function(...Object.keys(capturedScope), 'return ' + expr);
|
|
2190
2451
|
return fn(...Object.values(unwrapScope));
|
|
2191
2452
|
}
|
|
@@ -2409,8 +2670,12 @@ else {
|
|
|
2409
2670
|
return expression;
|
|
2410
2671
|
}
|
|
2411
2672
|
// Use passedScope instead of componentScope to preserve context through nested processing
|
|
2412
|
-
const scope = { ...passedScope, ...(capturedScope || {}), ...
|
|
2413
|
-
const unwrapScope = createAutoUnwrapProxy(scope)
|
|
2673
|
+
const scope = { ...globalHelpers, ...passedScope, ...(capturedScope || {}), ...extraScope };
|
|
2674
|
+
const unwrapScope = createAutoUnwrapProxy(scope, function(prop) {
|
|
2675
|
+
if (typeof prop !== 'string') return false;
|
|
2676
|
+
return expressionCallsSignal(expression, prop)
|
|
2677
|
+
|| expressionUsesSignalApi(expression, prop);
|
|
2678
|
+
});
|
|
2414
2679
|
const fn = new Function(...Object.keys(scope), 'return ' + expression);
|
|
2415
2680
|
return fn(...Object.values(unwrapScope));
|
|
2416
2681
|
}
|
|
@@ -2426,8 +2691,12 @@ catch (e) {
|
|
|
2426
2691
|
const evalLazy = (expression, extraScope = {}) => {
|
|
2427
2692
|
try {
|
|
2428
2693
|
if (/^__[A-Z_]+__$/.test(expression.trim())) return expression;
|
|
2429
|
-
const scope = { ...passedScope, ...(capturedScope || {}), ...
|
|
2430
|
-
const unwrapScope = createAutoUnwrapProxy(scope)
|
|
2694
|
+
const scope = { ...globalHelpers, ...passedScope, ...(capturedScope || {}), ...extraScope };
|
|
2695
|
+
const unwrapScope = createAutoUnwrapProxy(scope, function(prop) {
|
|
2696
|
+
if (typeof prop !== 'string') return false;
|
|
2697
|
+
return expressionCallsSignal(expression, prop)
|
|
2698
|
+
|| expressionUsesSignalApi(expression, prop);
|
|
2699
|
+
});
|
|
2431
2700
|
// new Function body is non-strict, so with() works \u2014 only accessed
|
|
2432
2701
|
// properties trigger the proxy's get trap and register as dependencies
|
|
2433
2702
|
const fn = new Function('__scope__', 'with(__scope__) { return ' + expression + ' }');
|
|
@@ -2487,7 +2756,7 @@ catch (e) {
|
|
|
2487
2756
|
const indexSignal = state(index);
|
|
2488
2757
|
if (key) itemSignalMap.set(key, { item: itemSignal, index: indexSignal });
|
|
2489
2758
|
|
|
2490
|
-
const itemScope = { ...passedScope, ...(capturedScope || {})
|
|
2759
|
+
const itemScope = { ...globalHelpers, ...passedScope, ...(capturedScope || {}) };
|
|
2491
2760
|
itemScope[itemName] = itemSignal;
|
|
2492
2761
|
if (indexName) itemScope[indexName] = indexSignal;
|
|
2493
2762
|
|
|
@@ -2496,17 +2765,15 @@ catch (e) {
|
|
|
2496
2765
|
Array.from(templateContent.childNodes).forEach(node => {
|
|
2497
2766
|
const clone = node.cloneNode(true);
|
|
2498
2767
|
if (clone.nodeType === 1) {
|
|
2499
|
-
|
|
2500
|
-
clone.
|
|
2501
|
-
clone.
|
|
2768
|
+
// Structural directives need a live parent. Keep the iteration
|
|
2769
|
+
// scope on the clone until the keyed diff inserts it, then bind.
|
|
2770
|
+
clone.__stx_for_scope = itemScope;
|
|
2502
2771
|
}
|
|
2503
2772
|
elements.push(clone);
|
|
2504
2773
|
});
|
|
2505
2774
|
} else {
|
|
2506
2775
|
const clone = templateContent.cloneNode(true);
|
|
2507
|
-
|
|
2508
|
-
clone.removeAttribute('x-cloak');
|
|
2509
|
-
clone.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
2776
|
+
clone.__stx_for_scope = itemScope;
|
|
2510
2777
|
elements.push(clone);
|
|
2511
2778
|
}
|
|
2512
2779
|
return elements;
|
|
@@ -2606,7 +2873,7 @@ catch (e) {
|
|
|
2606
2873
|
// rare, so the extra Object.keys / spread cost is negligible.
|
|
2607
2874
|
var diagScope;
|
|
2608
2875
|
try {
|
|
2609
|
-
var diagMerged = { ...passedScope, ...(capturedScope || {})
|
|
2876
|
+
var diagMerged = { ...globalHelpers, ...passedScope, ...(capturedScope || {}) };
|
|
2610
2877
|
var firstIdent = listExpr.match(/^[A-Za-z_$][\\w$]*/);
|
|
2611
2878
|
var rootName = firstIdent ? firstIdent[0] : '?';
|
|
2612
2879
|
var rootInScope = Object.prototype.hasOwnProperty.call(diagMerged, rootName);
|
|
@@ -2676,8 +2943,13 @@ catch (e) {
|
|
|
2676
2943
|
if (existing && existing.length > 0 && !usedKeys.has(key)) {
|
|
2677
2944
|
// Reuse this item's nodes \u2014 move the whole group into position, in
|
|
2678
2945
|
// order, so a multi-node row stays contiguous and correctly ordered.
|
|
2946
|
+
// A structural directive may intentionally have detached one of the
|
|
2947
|
+
// group's nodes. Its own placeholder owns reinsertion, so keyed reuse
|
|
2948
|
+
// must not resurrect that hidden node.
|
|
2679
2949
|
const group = existing.shift();
|
|
2680
|
-
group.forEach(el =>
|
|
2950
|
+
group.forEach(el => {
|
|
2951
|
+
if (el.parentNode === parent) parent.insertBefore(el, placeholder);
|
|
2952
|
+
});
|
|
2681
2953
|
newElements.push(...group);
|
|
2682
2954
|
newGroups.push(group);
|
|
2683
2955
|
usedKeys.add(key);
|
|
@@ -2692,7 +2964,17 @@ catch (e) {
|
|
|
2692
2964
|
} else {
|
|
2693
2965
|
// New item \u2014 create DOM elements with a fresh item signal
|
|
2694
2966
|
const elements = createItemElements(list[i], i, key);
|
|
2695
|
-
elements.forEach(el => {
|
|
2967
|
+
elements.forEach(el => {
|
|
2968
|
+
parent.insertBefore(el, placeholder);
|
|
2969
|
+
if (el.nodeType === 1) {
|
|
2970
|
+
var itemScope = el.__stx_for_scope || passedScope;
|
|
2971
|
+
delete el.__stx_for_scope;
|
|
2972
|
+
processElement(el, itemScope);
|
|
2973
|
+
el.removeAttribute('x-cloak');
|
|
2974
|
+
el.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
2975
|
+
}
|
|
2976
|
+
if (tgReady && el.isConnected) tgEnter(el, tgName);
|
|
2977
|
+
});
|
|
2696
2978
|
newElements.push(...elements);
|
|
2697
2979
|
newGroups.push(elements);
|
|
2698
2980
|
}
|
|
@@ -2785,6 +3067,7 @@ catch (e) {
|
|
|
2785
3067
|
// branches instead of bringing their content to life. The active branch
|
|
2786
3068
|
// is re-inserted below and IS connected, so it processes normally.
|
|
2787
3069
|
chain.forEach(function(b) {
|
|
3070
|
+
preserveConditionalComponentScopes(b.el, capturedComponentScope);
|
|
2788
3071
|
b.el.__stx_if_bound = true;
|
|
2789
3072
|
b.el.__stx_chain_member = true;
|
|
2790
3073
|
b.capturedElementScope = findElementScope(b.el);
|
|
@@ -2823,9 +3106,9 @@ catch (e) {
|
|
|
2823
3106
|
function evalBranch(b) {
|
|
2824
3107
|
var expression = b.expr;
|
|
2825
3108
|
if (/^__[A-Z_]+__$/.test(expression.trim())) return expression;
|
|
2826
|
-
var scope = { ...capturedComponentScope, ...(b.capturedElementScope || {})
|
|
3109
|
+
var scope = { ...globalHelpers, ...capturedComponentScope, ...(b.capturedElementScope || {}) };
|
|
2827
3110
|
try {
|
|
2828
|
-
var unwrapScope =
|
|
3111
|
+
var unwrapScope = createExpressionAutoUnwrapProxy(scope, expression);
|
|
2829
3112
|
// new Function body is non-strict, so with() works.
|
|
2830
3113
|
var fn = new Function('__scope__', 'with(__scope__) { return ' + expression + ' }');
|
|
2831
3114
|
return fn(unwrapScope);
|
|
@@ -2904,7 +3187,7 @@ catch (e2) {
|
|
|
2904
3187
|
// effect's tracked signals.
|
|
2905
3188
|
(function (branch) {
|
|
2906
3189
|
setTimeout(function () {
|
|
2907
|
-
var childScope = { ...capturedComponentScope, ...(branch.capturedElementScope || {})
|
|
3190
|
+
var childScope = { ...globalHelpers, ...capturedComponentScope, ...(branch.capturedElementScope || {}) };
|
|
2908
3191
|
branch.nodes.forEach(function (n) {
|
|
2909
3192
|
if (n.nodeType !== 1) return; // whitespace/text between template children
|
|
2910
3193
|
processElement(n, childScope);
|
|
@@ -2946,6 +3229,7 @@ catch (e2) {
|
|
|
2946
3229
|
const capturedElementScope = findElementScope(el);
|
|
2947
3230
|
const capturedComponentScope = { ...passedScope };
|
|
2948
3231
|
|
|
3232
|
+
preserveConditionalComponentScopes(el, capturedComponentScope);
|
|
2949
3233
|
parent.insertBefore(placeholder, el);
|
|
2950
3234
|
el.removeAttribute(attrName);
|
|
2951
3235
|
|
|
@@ -2975,15 +3259,15 @@ catch (e2) {
|
|
|
2975
3259
|
}
|
|
2976
3260
|
try {
|
|
2977
3261
|
// Use captured componentScope (with @for vars) merged with element scope
|
|
2978
|
-
const scope = { ...capturedComponentScope, ...(capturedElementScope || {})
|
|
2979
|
-
const unwrapScope =
|
|
3262
|
+
const scope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3263
|
+
const unwrapScope = createExpressionAutoUnwrapProxy(scope, expression);
|
|
2980
3264
|
const fn = new Function(...Object.keys(scope), 'return ' + expression);
|
|
2981
3265
|
return fn(...Object.values(unwrapScope));
|
|
2982
3266
|
}
|
|
2983
3267
|
catch (e1) {
|
|
2984
3268
|
try {
|
|
2985
3269
|
// Retry without the unwrap proxy so call-syntax (signal()) works.
|
|
2986
|
-
const scope2 = { ...capturedComponentScope, ...(capturedElementScope || {})
|
|
3270
|
+
const scope2 = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
2987
3271
|
const fn2 = new Function(...Object.keys(scope2), 'return ' + expression);
|
|
2988
3272
|
return fn2(...Object.values(scope2));
|
|
2989
3273
|
}
|
|
@@ -3001,7 +3285,7 @@ catch (e2) {
|
|
|
3001
3285
|
const processChildrenWithScope = () => {
|
|
3002
3286
|
// Build the combined scope for children - no need to modify global componentScope
|
|
3003
3287
|
// Just pass the scope explicitly to processElement
|
|
3004
|
-
const childScope = { ...capturedComponentScope, ...(capturedElementScope || {})
|
|
3288
|
+
const childScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3005
3289
|
|
|
3006
3290
|
|
|
3007
3291
|
Array.from(el.childNodes).forEach(child => processElement(child, childScope));
|
|
@@ -3011,7 +3295,7 @@ catch (e2) {
|
|
|
3011
3295
|
|
|
3012
3296
|
// Evaluate the :if expression \u2014 use direct signal read for simple refs,
|
|
3013
3297
|
// falling back to evalExpr for complex expressions
|
|
3014
|
-
const fullScope = { ...capturedComponentScope, ...(capturedElementScope || {})
|
|
3298
|
+
const fullScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3015
3299
|
const directSignal = fullScope[expr];
|
|
3016
3300
|
|
|
3017
3301
|
if (directSignal) {
|
|
@@ -3091,7 +3375,7 @@ else if (!value && isInserted) {
|
|
|
3091
3375
|
if (value && isInserted && !childrenProcessed) {
|
|
3092
3376
|
childrenProcessed = true;
|
|
3093
3377
|
setTimeout(function() {
|
|
3094
|
-
var childScope = { ...capturedComponentScope, ...(capturedElementScope || {})
|
|
3378
|
+
var childScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3095
3379
|
processElement(el, childScope);
|
|
3096
3380
|
// Remove x-cloak from the inserted subtree \u2014 the initial
|
|
3097
3381
|
// cloak removal (after processElement on the root) already
|
|
@@ -3506,16 +3790,35 @@ catch (e) {}
|
|
|
3506
3790
|
|
|
3507
3791
|
function useDark(options) {
|
|
3508
3792
|
var cm = useColorMode(options);
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
};
|
|
3793
|
+
var dark = state(cm.isDark);
|
|
3794
|
+
var setSignal = dark.set;
|
|
3795
|
+
cm.subscribe(function(mode) { setSignal(mode === 'dark'); });
|
|
3796
|
+
dark.toggle = function() { cm.toggle(); };
|
|
3797
|
+
dark.set = function(value) { cm.set(value ? 'dark' : 'light'); };
|
|
3798
|
+
Object.defineProperty(dark, 'isDark', { get: function() { return dark(); } });
|
|
3799
|
+
return dark;
|
|
3517
3800
|
}
|
|
3518
3801
|
|
|
3802
|
+
function useMediaQuery(query) {
|
|
3803
|
+
var mql = window.matchMedia(query);
|
|
3804
|
+
var matches = state(mql.matches);
|
|
3805
|
+
var onChange = function(event) { matches.set(event.matches); };
|
|
3806
|
+
if (mql.addEventListener) mql.addEventListener('change', onChange);
|
|
3807
|
+
else if (mql.addListener) mql.addListener(onChange);
|
|
3808
|
+
onDestroy(function() {
|
|
3809
|
+
if (mql.removeEventListener) mql.removeEventListener('change', onChange);
|
|
3810
|
+
else if (mql.removeListener) mql.removeListener(onChange);
|
|
3811
|
+
});
|
|
3812
|
+
Object.defineProperty(matches, 'matches', { get: function() { return matches(); } });
|
|
3813
|
+
Object.defineProperty(matches, 'value', { get: function() { return matches(); } });
|
|
3814
|
+
return matches;
|
|
3815
|
+
}
|
|
3816
|
+
|
|
3817
|
+
function usePreferredDark() { return useMediaQuery('(prefers-color-scheme: dark)'); }
|
|
3818
|
+
function usePreferredLight() { return useMediaQuery('(prefers-color-scheme: light)'); }
|
|
3819
|
+
function usePreferredReducedMotion() { return useMediaQuery('(prefers-reduced-motion: reduce)'); }
|
|
3820
|
+
function usePreferredContrast() { return useMediaQuery('(prefers-contrast: more)'); }
|
|
3821
|
+
|
|
3519
3822
|
// Vue-compat aliases
|
|
3520
3823
|
var ref = state;
|
|
3521
3824
|
var reactive = state;
|
|
@@ -3618,26 +3921,76 @@ catch (e) {}
|
|
|
3618
3921
|
function useReactiveProp(name, defaultValue, opts) {
|
|
3619
3922
|
opts = opts || {};
|
|
3620
3923
|
var parse = opts.parse || function(v) {
|
|
3924
|
+
// Infer the prop's intended scalar type from its default. A string prop
|
|
3925
|
+
// with an empty value must stay "", not become the boolean true. This
|
|
3926
|
+
// mirrors typed component props and keeps form/error text stable when a
|
|
3927
|
+
// parent forwards an empty signal value through a DOM attribute.
|
|
3928
|
+
if (typeof defaultValue === 'string') return v == null ? '' : String(v);
|
|
3929
|
+
if (typeof defaultValue === 'boolean') {
|
|
3930
|
+
if (v === '' || v === 'true') return true;
|
|
3931
|
+
if (v === 'false' || v === null || v === undefined) return false;
|
|
3932
|
+
return Boolean(v);
|
|
3933
|
+
}
|
|
3621
3934
|
if (v === '' || v === 'true') return true;
|
|
3622
3935
|
if (v === 'false') return false;
|
|
3936
|
+
if (typeof v === 'string' && (v.charAt(0) === '[' || v.charAt(0) === '{')) {
|
|
3937
|
+
try { return JSON.parse(v); }
|
|
3938
|
+
catch (e) {}
|
|
3939
|
+
}
|
|
3940
|
+
if (typeof defaultValue === 'number')
|
|
3941
|
+
return v != null && v !== '' && !isNaN(Number(v)) ? Number(v) : defaultValue;
|
|
3623
3942
|
if (v != null && v !== '' && !isNaN(Number(v))) return Number(v);
|
|
3624
3943
|
return v;
|
|
3625
3944
|
};
|
|
3626
3945
|
var root = window.__STX_CURRENT_ELEMENT__;
|
|
3946
|
+
var kebabName = name.replace(/([A-Z])/g, function(_, c) { return '-' + c.toLowerCase(); });
|
|
3947
|
+
var lowerName = name.toLowerCase();
|
|
3948
|
+
var attributeNames = [name];
|
|
3949
|
+
if (kebabName !== name) attributeNames.push(kebabName);
|
|
3950
|
+
if (lowerName !== name && lowerName !== kebabName) attributeNames.push(lowerName);
|
|
3951
|
+
function readAttribute() {
|
|
3952
|
+
if (!root || !root.hasAttribute) return { has: false, raw: null };
|
|
3953
|
+
for (var i = 0; i < attributeNames.length; i++) {
|
|
3954
|
+
var candidate = attributeNames[i];
|
|
3955
|
+
if (root.hasAttribute(candidate))
|
|
3956
|
+
return { has: true, raw: root.getAttribute(candidate) };
|
|
3957
|
+
}
|
|
3958
|
+
return { has: false, raw: null };
|
|
3959
|
+
}
|
|
3960
|
+
var staticValue = defaultValue;
|
|
3961
|
+
if (root && root.getAttribute) {
|
|
3962
|
+
var serializedProps = root.getAttribute('data-stx-props');
|
|
3963
|
+
if (serializedProps) {
|
|
3964
|
+
try {
|
|
3965
|
+
var parsedProps = JSON.parse(serializedProps);
|
|
3966
|
+
var camelName = name.replace(/-([a-z0-9])/g, function(_, c) { return c.toUpperCase(); });
|
|
3967
|
+
if (Object.prototype.hasOwnProperty.call(parsedProps, name)) staticValue = parsedProps[name];
|
|
3968
|
+
else if (Object.prototype.hasOwnProperty.call(parsedProps, camelName)) staticValue = parsedProps[camelName];
|
|
3969
|
+
}
|
|
3970
|
+
catch (e) {}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3627
3973
|
var initial;
|
|
3628
|
-
|
|
3629
|
-
|
|
3974
|
+
var lastRaw = null;
|
|
3975
|
+
var initialAttribute = readAttribute();
|
|
3976
|
+
if (initialAttribute.has) {
|
|
3977
|
+
lastRaw = initialAttribute.raw;
|
|
3978
|
+
initial = parse(lastRaw);
|
|
3630
3979
|
} else {
|
|
3631
|
-
initial =
|
|
3980
|
+
initial = staticValue;
|
|
3632
3981
|
}
|
|
3633
3982
|
var s = state(initial);
|
|
3634
3983
|
if (!root) return s;
|
|
3635
3984
|
var observer = new MutationObserver(function() {
|
|
3636
|
-
var
|
|
3637
|
-
var
|
|
3985
|
+
var attribute = readAttribute();
|
|
3986
|
+
var hasAttr = attribute.has;
|
|
3987
|
+
var raw = attribute.raw;
|
|
3988
|
+
if (raw === lastRaw) return;
|
|
3989
|
+
lastRaw = raw;
|
|
3990
|
+
var next = hasAttr ? parse(raw) : staticValue;
|
|
3638
3991
|
if (next !== s()) s.set(next);
|
|
3639
3992
|
});
|
|
3640
|
-
observer.observe(root, { attributes: true, attributeFilter:
|
|
3993
|
+
observer.observe(root, { attributes: true, attributeFilter: attributeNames });
|
|
3641
3994
|
onDestroy(function() { observer.disconnect(); });
|
|
3642
3995
|
return s;
|
|
3643
3996
|
}
|
|
@@ -4109,6 +4462,8 @@ catch (e) {}
|
|
|
4109
4462
|
var scopeId = el.getAttribute('data-stx-scope');
|
|
4110
4463
|
var scopeVars = window.stx._scopes && window.stx._scopes[scopeId];
|
|
4111
4464
|
if (!scopeVars) return;
|
|
4465
|
+
el.__stx_parent_scope = { ...componentScope };
|
|
4466
|
+
bindParentComponentProps(el, el.__stx_parent_scope);
|
|
4112
4467
|
Object.assign(componentScope, scopeVars);
|
|
4113
4468
|
el.__stx_disposers = trackEffects(function() { processElement(el, componentScope); });
|
|
4114
4469
|
if (scopeVars.__mountCallbacks && !scopeVars.__mounted) {
|
|
@@ -4143,6 +4498,9 @@ catch (e) {}
|
|
|
4143
4498
|
peek,
|
|
4144
4499
|
onMount,
|
|
4145
4500
|
onDestroy,
|
|
4501
|
+
onMounted: onMount,
|
|
4502
|
+
onBeforeUnmount: onDestroy,
|
|
4503
|
+
onUnmounted: onDestroy,
|
|
4146
4504
|
useFetch,
|
|
4147
4505
|
useRef,
|
|
4148
4506
|
navigate,
|
|
@@ -4180,6 +4538,11 @@ catch (e) {}
|
|
|
4180
4538
|
useWebSocket,
|
|
4181
4539
|
useColorMode,
|
|
4182
4540
|
useDark,
|
|
4541
|
+
useMediaQuery,
|
|
4542
|
+
usePreferredDark,
|
|
4543
|
+
usePreferredLight,
|
|
4544
|
+
usePreferredReducedMotion,
|
|
4545
|
+
usePreferredContrast,
|
|
4183
4546
|
useHead,
|
|
4184
4547
|
useSeoMeta,
|
|
4185
4548
|
// Client-side no-op for definePageMeta \u2014 the real implementation registers
|
|
@@ -4242,7 +4605,7 @@ catch (e) {}
|
|
|
4242
4605
|
_tg: { enter: tgEnter, leave: tgLeave, flip: tgFlip, snapshot: tgSnapshot }, // <TransitionGroup> helpers (#1742)
|
|
4243
4606
|
_scopes: {}, // Component-level scopes
|
|
4244
4607
|
_scopeCounter: 0, // Counter for generating unique scope IDs during SPA navigation
|
|
4245
|
-
_latestSetup: null, // Latest SFC setup function (
|
|
4608
|
+
_latestSetup: window.__stx_latestSetup || null, // Latest SFC setup function (including scripts parsed before the runtime)
|
|
4246
4609
|
_stores: new Map(), // Global store registry \u2014 survives SPA navigation
|
|
4247
4610
|
|
|
4248
4611
|
// \u2500\u2500 Store System \u2500\u2500
|
|
@@ -4447,6 +4810,16 @@ catch (e) {}
|
|
|
4447
4810
|
return result;
|
|
4448
4811
|
},
|
|
4449
4812
|
|
|
4813
|
+
registerStoresClient: function(stores) {
|
|
4814
|
+
window.__STX_STORES__ = window.__STX_STORES__ || {};
|
|
4815
|
+
var names = Object.keys(stores || {});
|
|
4816
|
+
for (var i = 0; i < names.length; i++) {
|
|
4817
|
+
var name = names[i];
|
|
4818
|
+
window.__STX_STORES__[name] = stores[name];
|
|
4819
|
+
}
|
|
4820
|
+
window.dispatchEvent(new CustomEvent('stx:stores-ready', { detail: names }));
|
|
4821
|
+
},
|
|
4822
|
+
|
|
4450
4823
|
useStore: function(id) {
|
|
4451
4824
|
var store = window.stx._stores.get(id);
|
|
4452
4825
|
if (!store && window.__STX_STORES__) {
|
|
@@ -4491,7 +4864,7 @@ catch (e) {}
|
|
|
4491
4864
|
}
|
|
4492
4865
|
|
|
4493
4866
|
var disposeEffects = trackEffects(function() {
|
|
4494
|
-
processElement(root, scope ||
|
|
4867
|
+
processElement(root, { ...componentScope, ...(scope || {}) });
|
|
4495
4868
|
});
|
|
4496
4869
|
root.__stx_disposers = disposeEffects;
|
|
4497
4870
|
|
|
@@ -4522,9 +4895,26 @@ else {
|
|
|
4522
4895
|
var scriptEl = document.currentScript;
|
|
4523
4896
|
|
|
4524
4897
|
function doMount() {
|
|
4525
|
-
//
|
|
4526
|
-
|
|
4527
|
-
|
|
4898
|
+
// Auto-mount scripts are emitted after their component template. Other
|
|
4899
|
+
// generated helper scripts can sit between the rendered root and this
|
|
4900
|
+
// script, so walk past script/style siblings before selecting the root.
|
|
4901
|
+
// Router-injected page scripts are marked data-stx-page and must use the
|
|
4902
|
+
// content-container fallback below instead.
|
|
4903
|
+
var isSpaPageScript = scriptEl && scriptEl.hasAttribute
|
|
4904
|
+
&& scriptEl.hasAttribute('data-stx-page')
|
|
4905
|
+
&& !scriptEl.hasAttribute('data-stx-positioned');
|
|
4906
|
+
var root = null;
|
|
4907
|
+
if (scriptEl && !isSpaPageScript) {
|
|
4908
|
+
var previous = scriptEl.previousElementSibling;
|
|
4909
|
+
while (previous && (previous.tagName === 'SCRIPT' || previous.tagName === 'STYLE'))
|
|
4910
|
+
previous = previous.previousElementSibling;
|
|
4911
|
+
|
|
4912
|
+
var next = scriptEl.nextElementSibling;
|
|
4913
|
+
while (next && (next.tagName === 'SCRIPT' || next.tagName === 'STYLE'))
|
|
4914
|
+
next = next.nextElementSibling;
|
|
4915
|
+
|
|
4916
|
+
root = previous || next || scriptEl.parentElement;
|
|
4917
|
+
}
|
|
4528
4918
|
|
|
4529
4919
|
// SPA fallback: during router navigation, script is appended to body>
|
|
4530
4920
|
// (not inside the content container), so nextElementSibling won't find the page content.
|
|
@@ -4593,7 +4983,7 @@ else {
|
|
|
4593
4983
|
|
|
4594
4984
|
// Walk DOM and bind directives, tracking effects for cleanup
|
|
4595
4985
|
var disposeEffects = trackEffects(function() {
|
|
4596
|
-
processElement(root, scope ||
|
|
4986
|
+
processElement(root, { ...componentScope, ...(scope || {}) });
|
|
4597
4987
|
});
|
|
4598
4988
|
root.__stx_disposers = disposeEffects;
|
|
4599
4989
|
|
|
@@ -4614,7 +5004,9 @@ catch (e) { console.error('[stx] onMount error:', e); }
|
|
|
4614
5004
|
root.__stx_destroy = localDestroyHooks;
|
|
4615
5005
|
}
|
|
4616
5006
|
|
|
4617
|
-
|
|
5007
|
+
var isRoutedMount = scriptEl && scriptEl.hasAttribute
|
|
5008
|
+
&& scriptEl.hasAttribute('data-stx-page');
|
|
5009
|
+
if (document.readyState === 'loading' || isRoutedMount) {
|
|
4618
5010
|
mountQueue.push(doMount);
|
|
4619
5011
|
}
|
|
4620
5012
|
else {
|
|
@@ -4819,14 +5211,31 @@ else {
|
|
|
4819
5211
|
console.log('[stx] registering DOMContentLoaded handler');
|
|
4820
5212
|
document.addEventListener('DOMContentLoaded', () => {
|
|
4821
5213
|
console.log('[stx] DOMContentLoaded fired \u2014 processing scopes');
|
|
4822
|
-
// Process mount queue (from stx.mount() calls during loading)
|
|
4823
|
-
mountQueue.forEach(function(fn) { fn(); });
|
|
4824
|
-
mountQueue = [];
|
|
4825
|
-
|
|
4826
5214
|
// Track which scoped elements have been processed
|
|
4827
5215
|
const processedScopes = new Set();
|
|
4828
5216
|
|
|
4829
|
-
//
|
|
5217
|
+
// Layout expansion can remove the data-stx marker from a page's first
|
|
5218
|
+
// element while leaving markers owned by the layout itself. The generated
|
|
5219
|
+
// page setup is still queued explicitly, so adopt it when its own marker
|
|
5220
|
+
// did not survive.
|
|
5221
|
+
const latestSetup = window.stx._latestSetup;
|
|
5222
|
+
const latestSetupName = typeof latestSetup === 'function' ? latestSetup.name : '';
|
|
5223
|
+
const hasLatestSetupMarker = latestSetupName && Array.from(document.querySelectorAll('[data-stx]'))
|
|
5224
|
+
.some(el => el.getAttribute('data-stx') === latestSetupName);
|
|
5225
|
+
if (latestSetup && !hasLatestSetupMarker) {
|
|
5226
|
+
try {
|
|
5227
|
+
const result = latestSetup();
|
|
5228
|
+
if (typeof result === 'object' && result !== null)
|
|
5229
|
+
Object.assign(componentScope, result);
|
|
5230
|
+
}
|
|
5231
|
+
catch (e) {
|
|
5232
|
+
console.error('[stx] initial setup error:', e);
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
|
|
5236
|
+
// Register page setup before mounting child components. Projected slot
|
|
5237
|
+
// content belongs to the caller, so child mounts need the caller's signals
|
|
5238
|
+
// available when they walk their own rendered roots.
|
|
4830
5239
|
document.querySelectorAll('[data-stx]').forEach(el => {
|
|
4831
5240
|
const setupName = el.getAttribute('data-stx');
|
|
4832
5241
|
console.log('[DOMContentLoaded] data-stx:', setupName, 'el:', el.tagName, 'fn exists:', !!(setupName && window[setupName]));
|
|
@@ -4837,6 +5246,16 @@ else {
|
|
|
4837
5246
|
Object.assign(componentScope, result);
|
|
4838
5247
|
}
|
|
4839
5248
|
}
|
|
5249
|
+
});
|
|
5250
|
+
var pageScopeSnapshot = { ...componentScope };
|
|
5251
|
+
|
|
5252
|
+
// Process mount queue (from stx.mount() calls during loading) only after
|
|
5253
|
+
// caller setup is available for Vue-style projected slot ownership.
|
|
5254
|
+
mountQueue.forEach(function(fn) { fn(); });
|
|
5255
|
+
mountQueue = [];
|
|
5256
|
+
|
|
5257
|
+
// Bind page roots after child components have registered their local scope.
|
|
5258
|
+
document.querySelectorAll('[data-stx]').forEach(el => {
|
|
4840
5259
|
console.log('[DOMContentLoaded] processElement on:', el.tagName, 'scope keys:', Object.keys(componentScope).slice(0, 10));
|
|
4841
5260
|
var disposeEffects = trackEffects(function() { processElement(el); });
|
|
4842
5261
|
el.__stx_disposers = disposeEffects;
|
|
@@ -4902,6 +5321,12 @@ else {
|
|
|
4902
5321
|
|
|
4903
5322
|
if (!scopeVars) return;
|
|
4904
5323
|
|
|
5324
|
+
// Forwarded props and events belong to the caller. Capture its scope
|
|
5325
|
+
// before this component's locals are merged, so identical child names
|
|
5326
|
+
// cannot shadow expressions such as :total-items="totalItems".
|
|
5327
|
+
el.__stx_parent_scope = resolveComponentCallerScope(el, pageScopeSnapshot);
|
|
5328
|
+
bindParentComponentProps(el, el.__stx_parent_scope);
|
|
5329
|
+
|
|
4905
5330
|
// Merge component scope vars into componentScope (don't restore - keep for head elements)
|
|
4906
5331
|
// This ensures expressions can access component variables even for elements
|
|
4907
5332
|
// where findElementScope might not work (e.g., cloned elements not yet in DOM)
|
|
@@ -5184,10 +5609,6 @@ catch (e) { console.warn('[stx] destroy callback error:', e); }
|
|
|
5184
5609
|
});
|
|
5185
5610
|
destroyCallbacks.length = 0;
|
|
5186
5611
|
|
|
5187
|
-
// Process mount queue (scripts in swapped content may have called stx.mount())
|
|
5188
|
-
mountQueue.forEach(function(fn) { fn(); });
|
|
5189
|
-
mountQueue = [];
|
|
5190
|
-
|
|
5191
5612
|
// Re-process scoped components in new content
|
|
5192
5613
|
var routerOpts = window.STX_ROUTER_OPTIONS || window.__stxRouterConfig || {};
|
|
5193
5614
|
var container = document.querySelector('[data-stx-content]')
|
|
@@ -5228,6 +5649,13 @@ catch (e) { console.warn('[stx] destroy callback error:', e); }
|
|
|
5228
5649
|
}
|
|
5229
5650
|
});
|
|
5230
5651
|
}
|
|
5652
|
+
var spaPageScopeSnapshot = { ...componentScope };
|
|
5653
|
+
|
|
5654
|
+
// Routed component mount scripts execute after DOMContentLoaded. Queue them
|
|
5655
|
+
// until the destination page setup is active so projected props and parent
|
|
5656
|
+
// event handlers resolve against the caller, matching initial hydration.
|
|
5657
|
+
mountQueue.forEach(function(fn) { fn(); });
|
|
5658
|
+
mountQueue = [];
|
|
5231
5659
|
|
|
5232
5660
|
// Initialize x-data scopes after SPA fragment swap.
|
|
5233
5661
|
// On full page load, the reactive bridge <script> calls initScope(). But after SPA
|
|
@@ -5277,6 +5705,8 @@ catch (e) { console.warn('[stx] destroy callback error:', e); }
|
|
|
5277
5705
|
return;
|
|
5278
5706
|
}
|
|
5279
5707
|
if (!scopeVars) return;
|
|
5708
|
+
el.__stx_parent_scope = resolveComponentCallerScope(el, spaPageScopeSnapshot);
|
|
5709
|
+
bindParentComponentProps(el, el.__stx_parent_scope);
|
|
5280
5710
|
Object.assign(componentScope, scopeVars);
|
|
5281
5711
|
if (el.__stx_disposers) return;
|
|
5282
5712
|
var disposeEffects = trackEffects(function() { processElement(el, componentScope); });
|
|
@@ -5353,9 +5783,9 @@ catch (e) { console.warn('[stx] mount callback error:', e); }
|
|
|
5353
5783
|
var sa = el.hasAttribute(':show') ? ':show' : el.hasAttribute('x-show') ? 'x-show' : '@show';
|
|
5354
5784
|
bindShow(el, el.getAttribute(sa), componentScope, sa);
|
|
5355
5785
|
}
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
bindModel(el,
|
|
5786
|
+
var mb = getModelBinding(el);
|
|
5787
|
+
if (mb) {
|
|
5788
|
+
bindModel(el, mb.expression, componentScope, mb.name);
|
|
5359
5789
|
}
|
|
5360
5790
|
// Process children, skipping scoped containers and script/style elements
|
|
5361
5791
|
if (el.tagName !== 'SCRIPT' && el.tagName !== 'STYLE') {
|