@stacksjs/stx 0.2.119 → 0.2.120
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/builtins/attrs.d.ts +16 -0
- package/dist/builtins/attrs.js +16 -0
- package/dist/builtins/safe-image.js +2 -9
- package/dist/builtins/stx-image.js +2 -9
- package/dist/builtins/stx-link.js +2 -9
- package/dist/cli.js +1851 -824
- package/dist/client-script-bundler.d.ts +0 -11
- package/dist/client-script-bundler.js +26 -4
- package/dist/client-script.d.ts +24 -0
- package/dist/client-script.js +59 -5
- package/dist/component-registry.d.ts +1 -0
- package/dist/component-renderer.js +13 -5
- package/dist/composables/index.d.ts +1 -0
- package/dist/composables/index.js +1 -0
- package/dist/composables/use-scroll-lock.d.ts +13 -0
- package/dist/composables/use-scroll-lock.js +55 -0
- package/dist/composition-api.d.ts +1 -1
- package/dist/composition-api.js +13 -4
- package/dist/craft-bridge.d.ts +80 -1
- package/dist/craft-bridge.js +80 -1
- package/dist/craft-components.d.ts +77 -3
- package/dist/craft-components.js +138 -9
- package/dist/craft.js +481 -73
- package/dist/dev-server/crosswind.d.ts +15 -1
- package/dist/dev-server/crosswind.js +12 -3
- package/dist/dev-server/serve-app.js +3 -3
- package/dist/document-shell.d.ts +17 -0
- package/dist/document-shell.js +72 -2
- package/dist/expressions.js +14 -7
- package/dist/head.d.ts +8 -0
- package/dist/head.js +11 -8
- package/dist/html-masking.d.ts +12 -0
- package/dist/html-masking.js +50 -15
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/process.js +11 -3
- package/dist/pwa.js +1154 -431
- package/dist/runtime-globals.d.ts +7 -4
- package/dist/runtime-globals.js +10 -2
- package/dist/runtime-injection.js +15 -1
- package/dist/signal-processing.js +19 -28
- package/dist/signals-api.d.ts +2 -0
- package/dist/signals-api.js +9 -0
- package/dist/signals.js +784 -62
- package/dist/ssg.js +1143 -420
- package/dist/state-management.d.ts +1 -1
- package/dist/state-management.js +1 -1
- package/dist/strict-lint.d.ts +1 -1
- package/dist/strict-lint.js +1 -1
- package/dist/stx.d.ts +6 -0
- package/dist/style-scoping.js +10 -0
- package/dist/utils.js +5 -3
- package/dist/variable-extractor.js +14 -22
- package/dist/vue-template.js +3 -2
- package/dist/x-element.d.ts +2 -2
- package/dist/x-element.js +9 -8
- package/package.json +5 -5
- package/stx.d.ts +6 -0
package/dist/signals.js
CHANGED
|
@@ -270,6 +270,12 @@ else {
|
|
|
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
|
+
// Loop variables are implementation-detail signals so keyed rows can
|
|
274
|
+
// update reactively. In template expressions they must behave like
|
|
275
|
+
// their item value, including for objects with fields named value,
|
|
276
|
+
// set, or subscribe. Otherwise item.value is mistaken for the
|
|
277
|
+
// signal compatibility API and resolves to the whole item object.
|
|
278
|
+
if (val._isStxLoopItem) return val();
|
|
273
279
|
if (preserveSignal && preserveSignal(prop, val)) return val;
|
|
274
280
|
return val();
|
|
275
281
|
}
|
|
@@ -1274,7 +1280,7 @@ finally {
|
|
|
1274
1280
|
return null;
|
|
1275
1281
|
}
|
|
1276
1282
|
|
|
1277
|
-
function toValue(expr, el, enableAutoUnwrap = true) {
|
|
1283
|
+
function toValue(expr, el, enableAutoUnwrap = true, extraScope = {}) {
|
|
1278
1284
|
try {
|
|
1279
1285
|
// Skip placeholder expressions like __TITLE__ (build-time placeholders)
|
|
1280
1286
|
if (/^__[A-Z_]+__$/.test(expr.trim())) {
|
|
@@ -1282,7 +1288,7 @@ finally {
|
|
|
1282
1288
|
}
|
|
1283
1289
|
// First try component-level scope
|
|
1284
1290
|
const elementScope = findElementScope(el || currentElement);
|
|
1285
|
-
const baseScope = { ...globalHelpers, ...componentScope, ...(elementScope || {}) };
|
|
1291
|
+
const baseScope = { ...globalHelpers, ...componentScope, ...(elementScope || {}), ...extraScope };
|
|
1286
1292
|
|
|
1287
1293
|
// Check for pipe syntax (Feature #2)
|
|
1288
1294
|
const pipeResult = parsePipeExpression(expr, baseScope);
|
|
@@ -1361,8 +1367,8 @@ catch (e) {
|
|
|
1361
1367
|
const [, varName, op, valueExpr] = assignMatch;
|
|
1362
1368
|
const signal = scope[varName];
|
|
1363
1369
|
if (signal && signal._isSignal) {
|
|
1364
|
-
return () => {
|
|
1365
|
-
const addValue = toValue(valueExpr, null, true);
|
|
1370
|
+
return ($event) => {
|
|
1371
|
+
const addValue = toValue(valueExpr, null, true, { ...scope, $event: $event });
|
|
1366
1372
|
signal.update(n => {
|
|
1367
1373
|
switch (op) {
|
|
1368
1374
|
case '+': return n + addValue;
|
|
@@ -1383,8 +1389,8 @@ catch (e) {
|
|
|
1383
1389
|
const [, varName, valueExpr] = simpleAssignMatch;
|
|
1384
1390
|
const signal = scope[varName];
|
|
1385
1391
|
if (signal && signal._isSignal) {
|
|
1386
|
-
return () => {
|
|
1387
|
-
const newValue = toValue(valueExpr, null, true);
|
|
1392
|
+
return ($event) => {
|
|
1393
|
+
const newValue = toValue(valueExpr, null, true, { ...scope, $event: $event });
|
|
1388
1394
|
signal.set(newValue);
|
|
1389
1395
|
};
|
|
1390
1396
|
}
|
|
@@ -1467,8 +1473,16 @@ catch (e) {
|
|
|
1467
1473
|
return;
|
|
1468
1474
|
}
|
|
1469
1475
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1476
|
+
// Loop items arrive as signals (see createItemElements). Unwrap them for
|
|
1477
|
+
// the same reason the inline @event path does: \`item.value\` in a handler
|
|
1478
|
+
// means the item's field, not the signal API.
|
|
1479
|
+
const handlerScope = {};
|
|
1480
|
+
Object.keys(scope).forEach(function(k) {
|
|
1481
|
+
const v = scope[k];
|
|
1482
|
+
handlerScope[k] = (v && typeof v === 'function' && v._isStxLoopItem) ? v() : v;
|
|
1483
|
+
});
|
|
1484
|
+
const fn = new Function(...Object.keys(handlerScope), '$event', expr);
|
|
1485
|
+
fn(...Object.values(handlerScope), event);
|
|
1472
1486
|
}
|
|
1473
1487
|
catch (e) {
|
|
1474
1488
|
// Event handlers fire on user interaction, not reactive re-runs \u2014 there's no
|
|
@@ -1625,10 +1639,22 @@ catch (e) {
|
|
|
1625
1639
|
&& /^\\s*\\(?\\s*\\w+(?:\\s*,\\s*\\w+)?\\s*\\)?\\s+(?:in|of)\\s+.+\\s*$/.test(expr);
|
|
1626
1640
|
}
|
|
1627
1641
|
|
|
1642
|
+
function getElementAttributes(el) {
|
|
1643
|
+
if (!el || !el.attributes) return [];
|
|
1644
|
+
return Array.from(el.attributes).map(function(attr) {
|
|
1645
|
+
// Browser DOM exposes Attr records through NamedNodeMap. HappyDOM and
|
|
1646
|
+
// other standards-oriented DOM implementations may expose Map entries
|
|
1647
|
+
// for cloned nodes instead. Normalize both shapes so reactive nodes
|
|
1648
|
+
// created by :for hydrate exactly like their browser counterparts.
|
|
1649
|
+
if (Array.isArray(attr)) return { name: attr[0], value: attr[1] };
|
|
1650
|
+
return attr;
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1628
1654
|
function getModelBinding(el) {
|
|
1629
1655
|
if (!el || !el.attributes) return null;
|
|
1630
1656
|
var prefixes = ['x-model', ':model', '@model'];
|
|
1631
|
-
var attrs =
|
|
1657
|
+
var attrs = getElementAttributes(el);
|
|
1632
1658
|
for (var i = 0; i < attrs.length; i++) {
|
|
1633
1659
|
var name = attrs[i] && attrs[i].name;
|
|
1634
1660
|
if (!name) continue;
|
|
@@ -1640,17 +1666,112 @@ catch (e) {
|
|
|
1640
1666
|
return null;
|
|
1641
1667
|
}
|
|
1642
1668
|
|
|
1669
|
+
function readDeferredParentComponentProps(el) {
|
|
1670
|
+
var serialized = el.getAttribute('data-stx-deferred-parent-bindings');
|
|
1671
|
+
if (!serialized) return {};
|
|
1672
|
+
try {
|
|
1673
|
+
var parsed = JSON.parse(serialized);
|
|
1674
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
1675
|
+
}
|
|
1676
|
+
catch (e) {
|
|
1677
|
+
return {};
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function preserveDeferredParentComponentProps(el) {
|
|
1682
|
+
var names = (el.getAttribute('data-stx-parent-bindings') || '').split(/\\s+/).filter(Boolean);
|
|
1683
|
+
if (!names.length) return;
|
|
1684
|
+
var deferred = readDeferredParentComponentProps(el);
|
|
1685
|
+
names.forEach(function(name) {
|
|
1686
|
+
var sourceName = ':' + name;
|
|
1687
|
+
if (el.hasAttribute(sourceName))
|
|
1688
|
+
deferred[name] = el.getAttribute(sourceName);
|
|
1689
|
+
});
|
|
1690
|
+
if (Object.keys(deferred).length)
|
|
1691
|
+
el.setAttribute('data-stx-deferred-parent-bindings', JSON.stringify(deferred));
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
function hasPendingLoopBoundary(el) {
|
|
1695
|
+
var loopBoundary = el;
|
|
1696
|
+
while (loopBoundary && loopBoundary !== document) {
|
|
1697
|
+
var loopExpression = loopBoundary.getAttribute && (
|
|
1698
|
+
loopBoundary.getAttribute('@for')
|
|
1699
|
+
|| loopBoundary.getAttribute(':for')
|
|
1700
|
+
|| loopBoundary.getAttribute('x-for')
|
|
1701
|
+
);
|
|
1702
|
+
if (loopExpression && isForDirectiveExpression(loopExpression))
|
|
1703
|
+
return true;
|
|
1704
|
+
loopBoundary = loopBoundary.parentElement || loopBoundary.parentNode;
|
|
1705
|
+
}
|
|
1706
|
+
return false;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function hasPendingAncestorLoopBoundary(el) {
|
|
1710
|
+
var loopBoundary = el && (el.parentElement || el.parentNode);
|
|
1711
|
+
while (loopBoundary && loopBoundary !== document) {
|
|
1712
|
+
var loopExpression = loopBoundary.getAttribute && (
|
|
1713
|
+
loopBoundary.getAttribute('@for')
|
|
1714
|
+
|| loopBoundary.getAttribute(':for')
|
|
1715
|
+
|| loopBoundary.getAttribute('x-for')
|
|
1716
|
+
);
|
|
1717
|
+
if (loopExpression && isForDirectiveExpression(loopExpression))
|
|
1718
|
+
return true;
|
|
1719
|
+
loopBoundary = loopBoundary.parentElement || loopBoundary.parentNode;
|
|
1720
|
+
}
|
|
1721
|
+
return false;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
function syncComponentPropSignal(el, name, value) {
|
|
1725
|
+
if (!el || !el.getAttribute || value === undefined) return;
|
|
1726
|
+
var scopeId = el.getAttribute('data-stx-scope');
|
|
1727
|
+
var scopeVars = scopeId && window.stx._scopes && window.stx._scopes[scopeId];
|
|
1728
|
+
if (!scopeVars) return;
|
|
1729
|
+
var camelName = name.replace(/-([a-z0-9])/g, function(_, character) {
|
|
1730
|
+
return character.toUpperCase();
|
|
1731
|
+
});
|
|
1732
|
+
var propSignal = scopeVars[name] || scopeVars[camelName];
|
|
1733
|
+
if (!propSignal || !propSignal._isSignal || typeof propSignal.set !== 'function') return;
|
|
1734
|
+
var directValues = el.__stx_direct_prop_values || (el.__stx_direct_prop_values = {});
|
|
1735
|
+
var directValue = {
|
|
1736
|
+
raw: el.hasAttribute(name) ? el.getAttribute(name) : null,
|
|
1737
|
+
value: value,
|
|
1738
|
+
};
|
|
1739
|
+
directValues[name] = directValue;
|
|
1740
|
+
directValues[camelName] = directValue;
|
|
1741
|
+
// This runs inside the parent binding effect. Reading the child prop
|
|
1742
|
+
// signal normally would subscribe that effect to its own output. Parent
|
|
1743
|
+
// expressions that return a fresh array or object would then recurse:
|
|
1744
|
+
// syncing the child retriggers the parent, which creates another value.
|
|
1745
|
+
// Compare without dependency tracking so the effect only follows the
|
|
1746
|
+
// caller-owned signals used by the binding expression.
|
|
1747
|
+
if (!Object.is(peek(function() { return propSignal(); }), value))
|
|
1748
|
+
propSignal.set(value);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1643
1751
|
function bindParentComponentProps(el, callerScope) {
|
|
1644
1752
|
if (!el || !el.getAttribute || el.__stx_parent_props_bound) return;
|
|
1645
1753
|
var names = (el.getAttribute('data-stx-parent-bindings') || '').split(/\\s+/).filter(Boolean);
|
|
1646
1754
|
if (!names.length) return;
|
|
1755
|
+
if (hasPendingLoopBoundary(el)) {
|
|
1756
|
+
preserveDeferredParentComponentProps(el);
|
|
1757
|
+
return;
|
|
1758
|
+
}
|
|
1759
|
+
var deferred = readDeferredParentComponentProps(el);
|
|
1760
|
+
var bindings = names.map(function(name) {
|
|
1761
|
+
var sourceName = ':' + name;
|
|
1762
|
+
var expression = el.hasAttribute(sourceName) ? el.getAttribute(sourceName) : deferred[name];
|
|
1763
|
+
return typeof expression === 'string'
|
|
1764
|
+
? { name: name, sourceName: sourceName, expression: expression }
|
|
1765
|
+
: null;
|
|
1766
|
+
}).filter(Boolean);
|
|
1767
|
+
if (!bindings.length) return;
|
|
1647
1768
|
el.__stx_parent_props_bound = true;
|
|
1648
1769
|
var scope = { ...globalHelpers, ...callerScope };
|
|
1649
1770
|
|
|
1650
|
-
|
|
1651
|
-
var
|
|
1652
|
-
|
|
1653
|
-
var expression =
|
|
1771
|
+
bindings.forEach(function(binding) {
|
|
1772
|
+
var name = binding.name;
|
|
1773
|
+
var sourceName = binding.sourceName;
|
|
1774
|
+
var expression = binding.expression;
|
|
1654
1775
|
effect(function() {
|
|
1655
1776
|
var value;
|
|
1656
1777
|
try {
|
|
@@ -1687,9 +1808,21 @@ catch (e) {
|
|
|
1687
1808
|
}
|
|
1688
1809
|
el.setAttribute(name, serialized);
|
|
1689
1810
|
}
|
|
1811
|
+
// The component setup scope is already registered for normal and
|
|
1812
|
+
// cloned signal components. Update its prop signal directly so object
|
|
1813
|
+
// identity and synchronous Vue-like prop delivery do not depend on a
|
|
1814
|
+
// MutationObserver round trip through a serialized DOM attribute.
|
|
1815
|
+
// The attribute remains the public DOM reflection and observer fallback
|
|
1816
|
+
// for scopes that register after this binding pass.
|
|
1817
|
+
syncComponentPropSignal(el, name, value);
|
|
1690
1818
|
});
|
|
1691
1819
|
el.removeAttribute(sourceName);
|
|
1820
|
+
delete deferred[name];
|
|
1692
1821
|
});
|
|
1822
|
+
if (Object.keys(deferred).length)
|
|
1823
|
+
el.setAttribute('data-stx-deferred-parent-bindings', JSON.stringify(deferred));
|
|
1824
|
+
else
|
|
1825
|
+
el.removeAttribute('data-stx-deferred-parent-bindings');
|
|
1693
1826
|
}
|
|
1694
1827
|
|
|
1695
1828
|
function resolveComponentCallerScope(el, pageScope) {
|
|
@@ -1720,11 +1853,69 @@ catch (e) {
|
|
|
1720
1853
|
root.querySelectorAll('[data-stx-scope]').forEach(function(scopeEl) { scopeEls.push(scopeEl); });
|
|
1721
1854
|
scopeEls.forEach(function(scopeEl) {
|
|
1722
1855
|
if (scopeEl.__stx_parent_props_bound) return;
|
|
1856
|
+
// A component inside a descendant loop cannot resolve its caller props
|
|
1857
|
+
// until bindFor creates the iteration scope. Binding it while the outer
|
|
1858
|
+
// conditional is being detached would consume expressions such as
|
|
1859
|
+
// :status="deployment.status" before the deployment item exists, leaving every
|
|
1860
|
+
// later clone with the child's default prop. bindFor hydrates these
|
|
1861
|
+
// component scopes after insertion with the correct per-item scope.
|
|
1862
|
+
var current = scopeEl;
|
|
1863
|
+
while (current && current !== root) {
|
|
1864
|
+
if (current.hasAttribute
|
|
1865
|
+
&& (current.hasAttribute('@for') || current.hasAttribute(':for') || current.hasAttribute('x-for'))) {
|
|
1866
|
+
preserveDeferredParentComponentProps(scopeEl);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
current = current.parentElement || current.parentNode;
|
|
1870
|
+
}
|
|
1723
1871
|
scopeEl.__stx_parent_scope = resolveComponentCallerScope(scopeEl, callerScope);
|
|
1724
1872
|
bindParentComponentProps(scopeEl, scopeEl.__stx_parent_scope);
|
|
1725
1873
|
});
|
|
1726
1874
|
}
|
|
1727
1875
|
|
|
1876
|
+
// Component setup scripts are emitted immediately after their scoped root.
|
|
1877
|
+
// When that root itself carries :if, detaching only the element leaves the
|
|
1878
|
+
// sibling setup script outside the conditional node group. Scripts inserted
|
|
1879
|
+
// through innerHTML are inert, so the later hydration pass has no script to
|
|
1880
|
+
// execute and the component keeps raw directives and interpolations.
|
|
1881
|
+
function findConditionalComponentSetupSiblings(root) {
|
|
1882
|
+
if (!root || !root.hasAttribute || !root.hasAttribute('data-stx-scope')) return [];
|
|
1883
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
1884
|
+
var templateScopeId = root.getAttribute('data-stx-template-scope') || scopeId;
|
|
1885
|
+
if (!scopeId) return [];
|
|
1886
|
+
|
|
1887
|
+
var scripts = [];
|
|
1888
|
+
var sibling = root.nextSibling;
|
|
1889
|
+
while (sibling) {
|
|
1890
|
+
var next = sibling.nextSibling;
|
|
1891
|
+
if (sibling.nodeType === Node.TEXT_NODE && !(sibling.textContent || '').trim()) {
|
|
1892
|
+
sibling = next;
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
if (sibling.nodeType === Node.COMMENT_NODE) {
|
|
1896
|
+
sibling = next;
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (sibling.nodeType === Node.ELEMENT_NODE
|
|
1900
|
+
&& sibling.tagName === 'STYLE'
|
|
1901
|
+
&& sibling.hasAttribute('data-stx-vendor')) {
|
|
1902
|
+
sibling = next;
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
if (sibling.nodeType === Node.ELEMENT_NODE
|
|
1906
|
+
&& sibling.tagName === 'SCRIPT'
|
|
1907
|
+
&& sibling.hasAttribute('data-stx-scoped')
|
|
1908
|
+
&& ((sibling.textContent || '').indexOf(scopeId) !== -1
|
|
1909
|
+
|| (templateScopeId && (sibling.textContent || '').indexOf(templateScopeId) !== -1))) {
|
|
1910
|
+
scripts.push(sibling);
|
|
1911
|
+
sibling = next;
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
break;
|
|
1915
|
+
}
|
|
1916
|
+
return scripts;
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1728
1919
|
function processElement(el, scope = componentScope) {
|
|
1729
1920
|
// Debug: log every element with x-class or @click
|
|
1730
1921
|
if (el.nodeType === Node.ELEMENT_NODE && el.hasAttribute) {
|
|
@@ -1739,6 +1930,17 @@ catch (e) {
|
|
|
1739
1930
|
deferHydration(el, trigger, scope);
|
|
1740
1931
|
return;
|
|
1741
1932
|
}
|
|
1933
|
+
if (el.nodeType === Node.ELEMENT_NODE
|
|
1934
|
+
&& el.hasAttribute
|
|
1935
|
+
&& el.hasAttribute('data-stx-scope')
|
|
1936
|
+
&& hasPendingAncestorLoopBoundary(el)) {
|
|
1937
|
+
preserveDeferredParentComponentProps(el);
|
|
1938
|
+
// Even when the component is itself a loop root, an ancestor loop must
|
|
1939
|
+
// create its caller item scope first. The ancestor's bindFor pass will
|
|
1940
|
+
// clone and hydrate this component with that scope, then its own loop can
|
|
1941
|
+
// evaluate expressions such as group.resources.
|
|
1942
|
+
return;
|
|
1943
|
+
}
|
|
1742
1944
|
|
|
1743
1945
|
if (el.nodeType === Node.ELEMENT_NODE && el.tagName === 'BUTTON' && el.hasAttribute && el.hasAttribute('@click')) {
|
|
1744
1946
|
|
|
@@ -1964,9 +2166,9 @@ catch (e2) {
|
|
|
1964
2166
|
|
|
1965
2167
|
// Handle attributes
|
|
1966
2168
|
if (el.hasAttribute && (el.hasAttribute('x-class') || el.hasAttribute('@click'))) {
|
|
1967
|
-
console.log('[stx] attr loop entry:', el.tagName, 'attrs:',
|
|
2169
|
+
console.log('[stx] attr loop entry:', el.tagName, 'attrs:', getElementAttributes(el).map(a => a.name).join(', '));
|
|
1968
2170
|
}
|
|
1969
|
-
|
|
2171
|
+
getElementAttributes(el).forEach(attr => {
|
|
1970
2172
|
const name = attr.name;
|
|
1971
2173
|
const value = attr.value;
|
|
1972
2174
|
if (!name) return;
|
|
@@ -2008,6 +2210,7 @@ catch (e2) {
|
|
|
2008
2210
|
el.value = desiredValue;
|
|
2009
2211
|
if (desiredValue === '' && el.tagName !== 'OPTION') el.removeAttribute(attrName);
|
|
2010
2212
|
else if (el.getAttribute(attrName) !== desiredValue) el.setAttribute(attrName, desiredValue);
|
|
2213
|
+
if (typeof el.__stx_model_sync === 'function') el.__stx_model_sync();
|
|
2011
2214
|
};
|
|
2012
2215
|
applyControlValue();
|
|
2013
2216
|
|
|
@@ -2032,6 +2235,24 @@ catch (e2) {
|
|
|
2032
2235
|
}
|
|
2033
2236
|
return;
|
|
2034
2237
|
}
|
|
2238
|
+
// Checked, selected, and indeterminate are live DOM properties, just like value.
|
|
2239
|
+
// Updating only the HTML attribute leaves a user-touched checkbox or
|
|
2240
|
+
// radio in its previous state because the property becomes dirty.
|
|
2241
|
+
// Indeterminate has no corresponding effective HTML attribute at all.
|
|
2242
|
+
if ((attrName === 'checked' || attrName === 'selected' || attrName === 'indeterminate') && attrName in el) {
|
|
2243
|
+
var selectedState = Boolean(v);
|
|
2244
|
+
el[attrName] = selectedState;
|
|
2245
|
+
if (attrName === 'indeterminate') {
|
|
2246
|
+
el.removeAttribute(attrName);
|
|
2247
|
+
}
|
|
2248
|
+
else if (selectedState) {
|
|
2249
|
+
el.setAttribute(attrName, '');
|
|
2250
|
+
}
|
|
2251
|
+
else {
|
|
2252
|
+
el.removeAttribute(attrName);
|
|
2253
|
+
}
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2035
2256
|
if (v === false || v === null || v === undefined) {
|
|
2036
2257
|
el.removeAttribute(attrName);
|
|
2037
2258
|
}
|
|
@@ -2111,11 +2332,15 @@ else if (name === 'ref' || name === ':ref' || name === 'x-ref' || name === 'data
|
|
|
2111
2332
|
console.log('[stx] binding event:', eventName, 'on', el.tagName, 'expr:', value.substring(0, 40));
|
|
2112
2333
|
el.addEventListener(eventName, (event) => {
|
|
2113
2334
|
console.log('[stx] event fired:', eventName, 'on', el.tagName);
|
|
2114
|
-
//
|
|
2115
|
-
//
|
|
2116
|
-
//
|
|
2117
|
-
//
|
|
2118
|
-
|
|
2335
|
+
// Vue-style component listeners fall through to the rendered native
|
|
2336
|
+
// root unless the child emits the same event. Signal components use
|
|
2337
|
+
// a scope wrapper, so native events arrive here from a descendant.
|
|
2338
|
+
// Ignore that native event only after defineEmits() has declared or
|
|
2339
|
+
// emitted the same name, preventing submit/click handlers from
|
|
2340
|
+
// firing once for the native event and again for the CustomEvent.
|
|
2341
|
+
var componentEmits = el.__stx_component_emits;
|
|
2342
|
+
if (isForwardedComponentEvent && event.target !== el
|
|
2343
|
+
&& componentEmits && componentEmits[eventName]) return;
|
|
2119
2344
|
|
|
2120
2345
|
// Vue-style component handlers receive the emitted payload directly.
|
|
2121
2346
|
// Native root events still receive the Event object so server-only
|
|
@@ -2180,7 +2405,16 @@ else if (name === 'ref' || name === ':ref' || name === 'x-ref' || name === 'data
|
|
|
2180
2405
|
// primitive reads.
|
|
2181
2406
|
var unwrapVars = Object.keys(eventCapturedScope).map(function(k) {
|
|
2182
2407
|
var v = eventCapturedScope[k];
|
|
2183
|
-
|
|
2408
|
+
// A :for item is a signal only as an implementation detail, so
|
|
2409
|
+
// that keyed rows can update in place. Its \`.value\` is a field
|
|
2410
|
+
// on the item, never the signal compatibility API \u2014 the same
|
|
2411
|
+
// rule createAutoUnwrapProxy states for binding expressions.
|
|
2412
|
+
// Without the carve-out, expressionUsesSignalApi sees
|
|
2413
|
+
// \`option.value\` and hands the handler the raw signal, so a
|
|
2414
|
+
// handler like \`pick(option.value)\` receives the whole item.
|
|
2415
|
+
// It is not an error, so it lands silently: the wrong value is
|
|
2416
|
+
// stored and only surfaces much later.
|
|
2417
|
+
if (v && typeof v === 'function' && v._isSignal && !v._isStxLoopItem && expressionUsesSignalApi(value, k)) {
|
|
2184
2418
|
return 'var ' + k + ' = __s["' + k + '"]';
|
|
2185
2419
|
}
|
|
2186
2420
|
return 'var ' + k + ' = __s["' + k + '"] && typeof __s["' + k + '"] === "function" && __s["' + k + '"]._isSignal ? __s["' + k + '"]() : __s["' + k + '"]';
|
|
@@ -2387,12 +2621,54 @@ catch (e) {
|
|
|
2387
2621
|
}
|
|
2388
2622
|
};
|
|
2389
2623
|
|
|
2390
|
-
if (tag === 'input' &&
|
|
2391
|
-
|
|
2392
|
-
|
|
2624
|
+
if (tag === 'input' && type === 'checkbox') {
|
|
2625
|
+
var syncCheckbox = () => {
|
|
2626
|
+
var current = getValue();
|
|
2627
|
+
el.checked = Array.isArray(current)
|
|
2628
|
+
? current.some(function(item) { return String(item) === String(el.value); })
|
|
2629
|
+
: Boolean(current);
|
|
2630
|
+
};
|
|
2631
|
+
el.__stx_model_sync = syncCheckbox;
|
|
2632
|
+
effect(syncCheckbox);
|
|
2633
|
+
el.addEventListener('change', () => {
|
|
2634
|
+
var current = getValue();
|
|
2635
|
+
if (!Array.isArray(current)) {
|
|
2636
|
+
setValue(el.checked);
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
var checkboxValue = coerceValue(el.value);
|
|
2641
|
+
var next = current.filter(function(item) { return String(item) !== String(checkboxValue); });
|
|
2642
|
+
if (el.checked) next.push(checkboxValue);
|
|
2643
|
+
setValue(next);
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
else if (tag === 'input' && type === 'radio') {
|
|
2647
|
+
var syncRadio = () => { el.checked = String(getValue()) === String(el.value); };
|
|
2648
|
+
el.__stx_model_sync = syncRadio;
|
|
2649
|
+
effect(syncRadio);
|
|
2650
|
+
el.addEventListener('change', () => {
|
|
2651
|
+
if (el.checked) setValue(coerceValue(el.value));
|
|
2652
|
+
});
|
|
2393
2653
|
}
|
|
2394
2654
|
else if (tag === 'select') {
|
|
2395
|
-
|
|
2655
|
+
var syncSelect = () => { el.value = getValue() ?? ''; };
|
|
2656
|
+
el.__stx_model_sync = syncSelect;
|
|
2657
|
+
effect(syncSelect);
|
|
2658
|
+
if (!el.__stx_model_observer && typeof window.MutationObserver !== 'undefined') {
|
|
2659
|
+
el.__stx_model_observer = new window.MutationObserver(syncSelect);
|
|
2660
|
+
el.__stx_model_observer.observe(el, {
|
|
2661
|
+
childList: true,
|
|
2662
|
+
subtree: true,
|
|
2663
|
+
attributes: true,
|
|
2664
|
+
attributeFilter: ['value'],
|
|
2665
|
+
});
|
|
2666
|
+
el.__stx_destroy = el.__stx_destroy || [];
|
|
2667
|
+
el.__stx_destroy.push(function() {
|
|
2668
|
+
if (el.__stx_model_observer) el.__stx_model_observer.disconnect();
|
|
2669
|
+
el.__stx_model_observer = null;
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2396
2672
|
el.addEventListener('change', () => setValue(coerceValue(el.value)));
|
|
2397
2673
|
}
|
|
2398
2674
|
else {
|
|
@@ -2599,6 +2875,179 @@ else if (typeof value === 'string') {
|
|
|
2599
2875
|
});
|
|
2600
2876
|
}
|
|
2601
2877
|
|
|
2878
|
+
var __forComponentScopeCounter = 0;
|
|
2879
|
+
var __forComponentSetupFactories = new Map();
|
|
2880
|
+
|
|
2881
|
+
function forEachGroupElement(nodes, callback) {
|
|
2882
|
+
nodes.forEach(function(node) {
|
|
2883
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
|
|
2884
|
+
callback(node);
|
|
2885
|
+
if (node.querySelectorAll) {
|
|
2886
|
+
node.querySelectorAll('*').forEach(function(child) { callback(child); });
|
|
2887
|
+
}
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
// A signal component expanded inside <template :for> carries a compiled
|
|
2892
|
+
// data-stx-scope id and setup script. cloneNode() used to duplicate that id
|
|
2893
|
+
// for every row, so all instances shared the first child's props and state.
|
|
2894
|
+
// Give each cloned component tree its own ids before insertion, and rewrite
|
|
2895
|
+
// the setup scripts that target those ids.
|
|
2896
|
+
function remapForComponentScopes(nodes) {
|
|
2897
|
+
var scopeMap = {};
|
|
2898
|
+
forEachGroupElement(nodes, function(node) {
|
|
2899
|
+
if (!node.hasAttribute || !node.hasAttribute('data-stx-scope')) return;
|
|
2900
|
+
var oldId = node.getAttribute('data-stx-scope');
|
|
2901
|
+
if (!oldId) return;
|
|
2902
|
+
if (!scopeMap[oldId])
|
|
2903
|
+
scopeMap[oldId] = oldId + '_for_' + (++__forComponentScopeCounter);
|
|
2904
|
+
// Keep the compiler's original id through any additional structural
|
|
2905
|
+
// clones. DOM cloneNode() preserves attributes but not expando fields.
|
|
2906
|
+
if (!node.hasAttribute('data-stx-template-scope'))
|
|
2907
|
+
node.setAttribute('data-stx-template-scope', oldId);
|
|
2908
|
+
node.setAttribute('data-stx-scope', scopeMap[oldId]);
|
|
2909
|
+
});
|
|
2910
|
+
|
|
2911
|
+
var oldIds = Object.keys(scopeMap);
|
|
2912
|
+
if (!oldIds.length) return;
|
|
2913
|
+
|
|
2914
|
+
forEachGroupElement(nodes, function(node) {
|
|
2915
|
+
if (node.attributes) {
|
|
2916
|
+
Array.from(node.attributes).forEach(function(rawAttribute) {
|
|
2917
|
+
var attribute = Array.isArray(rawAttribute)
|
|
2918
|
+
? { name: rawAttribute[0], value: rawAttribute[1] }
|
|
2919
|
+
: rawAttribute;
|
|
2920
|
+
if (!attribute || attribute.name === 'data-stx-scope' || attribute.name === 'data-stx-template-scope') return;
|
|
2921
|
+
var value = attribute.value;
|
|
2922
|
+
if (typeof value !== 'string') return;
|
|
2923
|
+
oldIds.forEach(function(oldId) {
|
|
2924
|
+
if (value.indexOf(oldId) !== -1)
|
|
2925
|
+
value = value.split(oldId).join(scopeMap[oldId]);
|
|
2926
|
+
});
|
|
2927
|
+
if (value !== attribute.value) node.setAttribute(attribute.name, value);
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
});
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
function hydrateComponentScopes(nodes, callerScope) {
|
|
2934
|
+
var roots = [];
|
|
2935
|
+
var scripts = [];
|
|
2936
|
+
forEachGroupElement(nodes, function(node) {
|
|
2937
|
+
if (node.hasAttribute && node.hasAttribute('data-stx-scope')) roots.push(node);
|
|
2938
|
+
if (node.tagName === 'SCRIPT' && node.hasAttribute('data-stx-scoped')) scripts.push(node);
|
|
2939
|
+
});
|
|
2940
|
+
if (!roots.length) return;
|
|
2941
|
+
|
|
2942
|
+
function hasPendingStructuralAncestor(root) {
|
|
2943
|
+
var cursor = root && root.parentElement;
|
|
2944
|
+
while (cursor && cursor.nodeType === Node.ELEMENT_NODE) {
|
|
2945
|
+
if (cursor.hasAttribute('@for')
|
|
2946
|
+
|| cursor.hasAttribute(':for')
|
|
2947
|
+
|| cursor.hasAttribute('x-for')
|
|
2948
|
+
|| cursor.hasAttribute('@if')
|
|
2949
|
+
|| cursor.hasAttribute(':if')
|
|
2950
|
+
|| cursor.hasAttribute('x-if')
|
|
2951
|
+
|| cursor.hasAttribute('@else-if')
|
|
2952
|
+
|| cursor.hasAttribute(':else-if')
|
|
2953
|
+
|| cursor.hasAttribute('x-else-if')
|
|
2954
|
+
|| cursor.hasAttribute('@else')
|
|
2955
|
+
|| cursor.hasAttribute(':else')
|
|
2956
|
+
|| cursor.hasAttribute('x-else'))
|
|
2957
|
+
return true;
|
|
2958
|
+
cursor = cursor.parentElement;
|
|
2959
|
+
}
|
|
2960
|
+
return false;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// Scripts cloned from template.content are inert in browsers. Compile each
|
|
2964
|
+
// distinct component setup body once, then run that cached function for
|
|
2965
|
+
// every loop instance. Temporarily restoring the template's original scope
|
|
2966
|
+
// id lets existing compiled setup code locate the correct root without
|
|
2967
|
+
// rewriting and reparsing the full script for every row.
|
|
2968
|
+
scripts.forEach(function(old) {
|
|
2969
|
+
var text = old.textContent || '';
|
|
2970
|
+
var pendingRoots = roots.filter(function(root) {
|
|
2971
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
2972
|
+
var originalScopeId = root.getAttribute('data-stx-template-scope') || scopeId;
|
|
2973
|
+
return scopeId
|
|
2974
|
+
&& originalScopeId
|
|
2975
|
+
&& text.indexOf(originalScopeId) !== -1
|
|
2976
|
+
&& !hasPendingStructuralAncestor(root)
|
|
2977
|
+
&& !(window.stx._scopes && window.stx._scopes[scopeId]);
|
|
2978
|
+
});
|
|
2979
|
+
if (!pendingRoots.length) return;
|
|
2980
|
+
|
|
2981
|
+
var setup = __forComponentSetupFactories.get(text);
|
|
2982
|
+
if (!setup) {
|
|
2983
|
+
try {
|
|
2984
|
+
setup = new Function(text);
|
|
2985
|
+
__forComponentSetupFactories.set(text, setup);
|
|
2986
|
+
}
|
|
2987
|
+
catch (e) {
|
|
2988
|
+
console.error('[stx] cloned component setup compile error:', e);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
pendingRoots.forEach(function(root) {
|
|
2994
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
2995
|
+
var originalScopeId = root.getAttribute('data-stx-template-scope') || scopeId;
|
|
2996
|
+
if (!scopeId || !originalScopeId) return;
|
|
2997
|
+
|
|
2998
|
+
root.setAttribute('data-stx-scope', originalScopeId);
|
|
2999
|
+
try {
|
|
3000
|
+
setup();
|
|
3001
|
+
var registeredScope = window.stx._scopes && (window.stx._scopes[originalScopeId] || window.stx._scopes[scopeId]);
|
|
3002
|
+
if (registeredScope && originalScopeId !== scopeId) {
|
|
3003
|
+
delete window.stx._scopes[originalScopeId];
|
|
3004
|
+
window.stx._scopes[scopeId] = registeredScope;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
catch (e) {
|
|
3008
|
+
console.error('[stx] cloned component setup error:', e);
|
|
3009
|
+
}
|
|
3010
|
+
finally {
|
|
3011
|
+
root.setAttribute('data-stx-scope', scopeId);
|
|
3012
|
+
}
|
|
3013
|
+
});
|
|
3014
|
+
|
|
3015
|
+
// A component that is itself a structural source still needs its setup
|
|
3016
|
+
// sibling when bindFor/bindIf turns that source into a clone template.
|
|
3017
|
+
// Final row instances have no structural attribute and can discard it.
|
|
3018
|
+
var isStructuralSource = pendingRoots.some(function(root) {
|
|
3019
|
+
return root.hasAttribute('@for')
|
|
3020
|
+
|| root.hasAttribute(':for')
|
|
3021
|
+
|| root.hasAttribute('x-for')
|
|
3022
|
+
|| root.hasAttribute('@if')
|
|
3023
|
+
|| root.hasAttribute(':if')
|
|
3024
|
+
|| root.hasAttribute('x-if');
|
|
3025
|
+
});
|
|
3026
|
+
if (!isStructuralSource && old.parentNode) old.parentNode.removeChild(old);
|
|
3027
|
+
});
|
|
3028
|
+
|
|
3029
|
+
roots.forEach(function(root) {
|
|
3030
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
3031
|
+
var scopeVars = scopeId && window.stx._scopes && window.stx._scopes[scopeId];
|
|
3032
|
+
var resolvedCallerScope = resolveComponentCallerScope(root, callerScope);
|
|
3033
|
+
root.__stx_parent_scope = resolvedCallerScope;
|
|
3034
|
+
bindParentComponentProps(root, resolvedCallerScope);
|
|
3035
|
+
if (!scopeVars || root.__stx_disposers) return;
|
|
3036
|
+
scopeVars.__el = root;
|
|
3037
|
+
var childScope = { ...resolvedCallerScope, ...scopeVars };
|
|
3038
|
+
root.__stx_disposers = trackEffects(function() {
|
|
3039
|
+
processElement(root, childScope);
|
|
3040
|
+
});
|
|
3041
|
+
if (scopeVars.__mountCallbacks && !scopeVars.__mounted) {
|
|
3042
|
+
scopeVars.__mounted = true;
|
|
3043
|
+
scopeVars.__mountCallbacks.forEach(function(fn) {
|
|
3044
|
+
try { fn(); }
|
|
3045
|
+
catch (e) { console.error('[stx] onMount error:', e); }
|
|
3046
|
+
});
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3049
|
+
}
|
|
3050
|
+
|
|
2602
3051
|
function bindFor(el, passedScope = componentScope, attrName = '@for') {
|
|
2603
3052
|
if (el.__stx_for_bound) return;
|
|
2604
3053
|
el.__stx_for_bound = true;
|
|
@@ -2622,6 +3071,34 @@ else if (typeof value === 'string') {
|
|
|
2622
3071
|
|
|
2623
3072
|
const placeholder = document.createComment('stx-for');
|
|
2624
3073
|
const isTemplate = el.tagName === 'TEMPLATE';
|
|
3074
|
+
const componentSetupTemplates = [];
|
|
3075
|
+
if (!isTemplate && el.hasAttribute('data-stx-scope')) {
|
|
3076
|
+
const componentScopeId = el.getAttribute('data-stx-scope');
|
|
3077
|
+
const componentTemplateScopeId = el.getAttribute('data-stx-template-scope') || componentScopeId;
|
|
3078
|
+
let setupSibling = el.nextSibling;
|
|
3079
|
+
while (setupSibling) {
|
|
3080
|
+
const nextSetupSibling = setupSibling.nextSibling;
|
|
3081
|
+
if (setupSibling.nodeType === Node.TEXT_NODE && !(setupSibling.textContent || '').trim()) {
|
|
3082
|
+
setupSibling = nextSetupSibling;
|
|
3083
|
+
continue;
|
|
3084
|
+
}
|
|
3085
|
+
if (setupSibling.nodeType === Node.COMMENT_NODE) {
|
|
3086
|
+
setupSibling = nextSetupSibling;
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
if (setupSibling.nodeType === Node.ELEMENT_NODE
|
|
3090
|
+
&& setupSibling.tagName === 'SCRIPT'
|
|
3091
|
+
&& setupSibling.hasAttribute('data-stx-scoped')
|
|
3092
|
+
&& ((setupSibling.textContent || '').indexOf(componentScopeId) !== -1
|
|
3093
|
+
|| (componentTemplateScopeId && (setupSibling.textContent || '').indexOf(componentTemplateScopeId) !== -1))) {
|
|
3094
|
+
componentSetupTemplates.push(setupSibling.cloneNode(true));
|
|
3095
|
+
setupSibling.remove();
|
|
3096
|
+
setupSibling = nextSetupSibling;
|
|
3097
|
+
continue;
|
|
3098
|
+
}
|
|
3099
|
+
break;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
2625
3102
|
|
|
2626
3103
|
// Check if element also has @if / :if - need to handle together
|
|
2627
3104
|
const ifExpr = el.getAttribute('@if') || el.getAttribute(':if');
|
|
@@ -2658,6 +3135,11 @@ else if (typeof value === 'string') {
|
|
|
2658
3135
|
const capturedScope = findElementScope(el) || findElementScope(parent);
|
|
2659
3136
|
|
|
2660
3137
|
parent.insertBefore(placeholder, el);
|
|
3138
|
+
// Component setup scripts execute while the server-rendered loop source is
|
|
3139
|
+
// still in the document. That source becomes a template, not a live
|
|
3140
|
+
// instance, so reclaim all registered scopes before removing it. Every
|
|
3141
|
+
// rendered iteration receives remapped setup scripts and fresh scopes.
|
|
3142
|
+
disposeSubtreeScopes(el);
|
|
2661
3143
|
parent.removeChild(el);
|
|
2662
3144
|
|
|
2663
3145
|
// Capture :key expression BEFORE removing the attribute
|
|
@@ -2694,6 +3176,49 @@ else {
|
|
|
2694
3176
|
let loadingElement = null;
|
|
2695
3177
|
let emptyElement = null;
|
|
2696
3178
|
|
|
3179
|
+
// A <template :for> can contain another structural directive. That
|
|
3180
|
+
// nested directive replaces its source node with a placeholder and owns
|
|
3181
|
+
// additional live siblings around that placeholder. Track an explicit
|
|
3182
|
+
// boundary for every template iteration so keyed reuse moves the complete
|
|
3183
|
+
// live range, including nodes created by nested :for / :if bindings.
|
|
3184
|
+
// Moving only the original cloned nodes pulled static siblings past the
|
|
3185
|
+
// nested range on the next outer update (header, then rows became rows,
|
|
3186
|
+
// then header).
|
|
3187
|
+
const liveGroupNodes = (group) => {
|
|
3188
|
+
if (!isTemplate || !group || group.length < 2) return group || [];
|
|
3189
|
+
const start = group[0];
|
|
3190
|
+
const end = group[group.length - 1];
|
|
3191
|
+
if (!start || !end || start.parentNode !== parent || end.parentNode !== parent)
|
|
3192
|
+
return group.filter(groupNode => groupNode && groupNode.parentNode === parent);
|
|
3193
|
+
|
|
3194
|
+
const nodes = [];
|
|
3195
|
+
let currentNode = start;
|
|
3196
|
+
while (currentNode) {
|
|
3197
|
+
nodes.push(currentNode);
|
|
3198
|
+
if (currentNode === end) break;
|
|
3199
|
+
currentNode = currentNode.nextSibling;
|
|
3200
|
+
}
|
|
3201
|
+
return nodes;
|
|
3202
|
+
};
|
|
3203
|
+
|
|
3204
|
+
const removeGroup = (group) => {
|
|
3205
|
+
const liveNodes = liveGroupNodes(group);
|
|
3206
|
+
const liveSet = new Set(liveNodes);
|
|
3207
|
+
liveNodes.forEach(e => { disposeSubtreeScopes(e); e.remove(); });
|
|
3208
|
+
group.forEach(groupNode => {
|
|
3209
|
+
if (liveSet.has(groupNode)) return;
|
|
3210
|
+
disposeSubtreeScopes(groupNode);
|
|
3211
|
+
groupNode.remove();
|
|
3212
|
+
});
|
|
3213
|
+
};
|
|
3214
|
+
|
|
3215
|
+
const clearRenderedItems = () => {
|
|
3216
|
+
currentGroups.forEach(removeGroup);
|
|
3217
|
+
currentElements = [];
|
|
3218
|
+
currentGroups = [];
|
|
3219
|
+
currentKeys = [];
|
|
3220
|
+
};
|
|
3221
|
+
|
|
2697
3222
|
// Helper to evaluate with captured scope (auto-unwraps signals)
|
|
2698
3223
|
// NOTE: This eagerly reads ALL signals in scope \u2014 use evalLazy inside effects
|
|
2699
3224
|
// to avoid over-broad dependency tracking.
|
|
@@ -2787,6 +3312,7 @@ catch (e) {
|
|
|
2787
3312
|
// The item is wrapped in a signal so bindings track it reactively.
|
|
2788
3313
|
const createItemElements = (item, index, key) => {
|
|
2789
3314
|
const itemSignal = state(item);
|
|
3315
|
+
itemSignal._isStxLoopItem = true;
|
|
2790
3316
|
const indexSignal = state(index);
|
|
2791
3317
|
if (key) itemSignalMap.set(key, { item: itemSignal, index: indexSignal });
|
|
2792
3318
|
|
|
@@ -2796,6 +3322,7 @@ catch (e) {
|
|
|
2796
3322
|
|
|
2797
3323
|
const elements = [];
|
|
2798
3324
|
if (isTemplate) {
|
|
3325
|
+
elements.push(document.createComment('stx-for-item-start'));
|
|
2799
3326
|
Array.from(templateContent.childNodes).forEach(node => {
|
|
2800
3327
|
const clone = node.cloneNode(true);
|
|
2801
3328
|
if (clone.nodeType === 1) {
|
|
@@ -2805,11 +3332,16 @@ catch (e) {
|
|
|
2805
3332
|
}
|
|
2806
3333
|
elements.push(clone);
|
|
2807
3334
|
});
|
|
3335
|
+
elements.push(document.createComment('stx-for-item-end'));
|
|
2808
3336
|
} else {
|
|
2809
3337
|
const clone = templateContent.cloneNode(true);
|
|
2810
3338
|
clone.__stx_for_scope = itemScope;
|
|
2811
3339
|
elements.push(clone);
|
|
3340
|
+
componentSetupTemplates.forEach(function(setupTemplate) {
|
|
3341
|
+
elements.push(setupTemplate.cloneNode(true));
|
|
3342
|
+
});
|
|
2812
3343
|
}
|
|
3344
|
+
remapForComponentScopes(elements);
|
|
2813
3345
|
return elements;
|
|
2814
3346
|
};
|
|
2815
3347
|
|
|
@@ -2835,10 +3367,7 @@ catch (e) {
|
|
|
2835
3367
|
const isLoading = evalLazy(loadingExpr);
|
|
2836
3368
|
if (isLoading) {
|
|
2837
3369
|
// Dispose scopes registered by items before they leave the DOM (#1727).
|
|
2838
|
-
|
|
2839
|
-
currentElements = [];
|
|
2840
|
-
currentGroups = [];
|
|
2841
|
-
currentKeys = [];
|
|
3370
|
+
clearRenderedItems();
|
|
2842
3371
|
showLoading();
|
|
2843
3372
|
return;
|
|
2844
3373
|
}
|
|
@@ -2884,10 +3413,7 @@ catch (e) {
|
|
|
2884
3413
|
const ifValue = evalLazy(ifExpr);
|
|
2885
3414
|
if (!ifValue) {
|
|
2886
3415
|
// Dispose scopes registered by items before they leave the DOM (#1727).
|
|
2887
|
-
|
|
2888
|
-
currentElements = [];
|
|
2889
|
-
currentGroups = [];
|
|
2890
|
-
currentKeys = [];
|
|
3416
|
+
clearRenderedItems();
|
|
2891
3417
|
hideEmpty();
|
|
2892
3418
|
return;
|
|
2893
3419
|
}
|
|
@@ -2925,10 +3451,7 @@ catch (e) {
|
|
|
2925
3451
|
// Check empty state (Feature #3)
|
|
2926
3452
|
if (list.length === 0) {
|
|
2927
3453
|
// Dispose scopes registered by items before they leave the DOM (#1727).
|
|
2928
|
-
|
|
2929
|
-
currentElements = [];
|
|
2930
|
-
currentGroups = [];
|
|
2931
|
-
currentKeys = [];
|
|
3454
|
+
clearRenderedItems();
|
|
2932
3455
|
if (emptyExpr) {
|
|
2933
3456
|
const emptyContent = evalLazy(emptyExpr);
|
|
2934
3457
|
if (emptyContent && typeof emptyContent === 'string') {
|
|
@@ -2981,9 +3504,10 @@ catch (e) {
|
|
|
2981
3504
|
// group's nodes. Its own placeholder owns reinsertion, so keyed reuse
|
|
2982
3505
|
// must not resurrect that hidden node.
|
|
2983
3506
|
const group = existing.shift();
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3507
|
+
const liveNodes = liveGroupNodes(group);
|
|
3508
|
+
const fragment = document.createDocumentFragment();
|
|
3509
|
+
liveNodes.forEach(groupNode => fragment.appendChild(groupNode));
|
|
3510
|
+
parent.insertBefore(fragment, placeholder);
|
|
2987
3511
|
newElements.push(...group);
|
|
2988
3512
|
newGroups.push(group);
|
|
2989
3513
|
usedKeys.add(key);
|
|
@@ -3000,14 +3524,20 @@ catch (e) {
|
|
|
3000
3524
|
const elements = createItemElements(list[i], i, key);
|
|
3001
3525
|
elements.forEach(el => {
|
|
3002
3526
|
parent.insertBefore(el, placeholder);
|
|
3527
|
+
if (tgReady && el.isConnected) tgEnter(el, tgName);
|
|
3528
|
+
});
|
|
3529
|
+
hydrateComponentScopes(elements, elements.find(function(node) {
|
|
3530
|
+
return node && node.__stx_for_scope;
|
|
3531
|
+
})?.__stx_for_scope || passedScope);
|
|
3532
|
+
elements.forEach(el => {
|
|
3003
3533
|
if (el.nodeType === 1) {
|
|
3004
3534
|
var itemScope = el.__stx_for_scope || passedScope;
|
|
3005
3535
|
delete el.__stx_for_scope;
|
|
3006
|
-
|
|
3536
|
+
if (!el.__stx_disposers && el.tagName !== 'SCRIPT')
|
|
3537
|
+
processElement(el, itemScope);
|
|
3007
3538
|
el.removeAttribute('x-cloak');
|
|
3008
3539
|
el.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
3009
3540
|
}
|
|
3010
|
-
if (tgReady && el.isConnected) tgEnter(el, tgName);
|
|
3011
3541
|
});
|
|
3012
3542
|
newElements.push(...elements);
|
|
3013
3543
|
newGroups.push(elements);
|
|
@@ -3019,11 +3549,28 @@ catch (e) {
|
|
|
3019
3549
|
// remove() call (#1727).
|
|
3020
3550
|
for (const [key, groups] of oldKeyMap) {
|
|
3021
3551
|
if (!usedKeys.has(key)) {
|
|
3022
|
-
const elements = groups.flat();
|
|
3023
3552
|
// In a transition group, run the leave animation and let it remove the
|
|
3024
3553
|
// node; otherwise remove immediately. Scopes are disposed up-front
|
|
3025
3554
|
// either way (their destroy hooks shouldn't wait on a CSS transition).
|
|
3026
|
-
|
|
3555
|
+
groups.forEach((group) => {
|
|
3556
|
+
if (tgReady) {
|
|
3557
|
+
const liveNodes = liveGroupNodes(group);
|
|
3558
|
+
const transitioned = new Set();
|
|
3559
|
+
liveNodes.forEach((el) => {
|
|
3560
|
+
disposeSubtreeScopes(el);
|
|
3561
|
+
if (tgLeave(el, tgName)) transitioned.add(el);
|
|
3562
|
+
else el.remove();
|
|
3563
|
+
});
|
|
3564
|
+
group.forEach((el) => {
|
|
3565
|
+
if (transitioned.has(el) || liveNodes.includes(el)) return;
|
|
3566
|
+
disposeSubtreeScopes(el);
|
|
3567
|
+
el.remove();
|
|
3568
|
+
});
|
|
3569
|
+
}
|
|
3570
|
+
else {
|
|
3571
|
+
removeGroup(group);
|
|
3572
|
+
}
|
|
3573
|
+
});
|
|
3027
3574
|
itemSignalMap.delete(key);
|
|
3028
3575
|
}
|
|
3029
3576
|
}
|
|
@@ -3222,9 +3769,26 @@ catch (e2) {
|
|
|
3222
3769
|
(function (branch) {
|
|
3223
3770
|
setTimeout(function () {
|
|
3224
3771
|
var childScope = { ...globalHelpers, ...capturedComponentScope, ...(branch.capturedElementScope || {}) };
|
|
3772
|
+
// Components compiled inside any branch of an if/else-if/else
|
|
3773
|
+
// chain carry scoped setup scripts with them. Branch nodes are
|
|
3774
|
+
// detached before those scripts can execute, so simply
|
|
3775
|
+
// re-inserting the selected branch leaves component props,
|
|
3776
|
+
// events, directives, and projected slot expressions inert.
|
|
3777
|
+
// Hydrate those scopes before the normal subtree walk, matching
|
|
3778
|
+
// the single-template :if and :for insertion paths.
|
|
3779
|
+
var previouslyTrackedNodes = new Set();
|
|
3780
|
+
branch.nodes.forEach(function (n) {
|
|
3781
|
+
if (n && n.__stx_disposers) previouslyTrackedNodes.add(n);
|
|
3782
|
+
});
|
|
3783
|
+
hydrateComponentScopes(branch.nodes, childScope);
|
|
3225
3784
|
branch.nodes.forEach(function (n) {
|
|
3226
3785
|
if (n.nodeType !== 1) return; // whitespace/text between template children
|
|
3227
|
-
|
|
3786
|
+
// A scoped component receives its disposer in the outer scope
|
|
3787
|
+
// walk immediately after processElement returns from binding
|
|
3788
|
+
// this conditional. That first pass intentionally stops at
|
|
3789
|
+
// :if, so a disposer does not mean its children were hydrated.
|
|
3790
|
+
if (previouslyTrackedNodes.has(n) || !n.__stx_disposers)
|
|
3791
|
+
processElement(n, childScope);
|
|
3228
3792
|
n.removeAttribute('x-cloak');
|
|
3229
3793
|
n.querySelectorAll('[x-cloak]').forEach(function (c) { c.removeAttribute('x-cloak'); });
|
|
3230
3794
|
});
|
|
@@ -3253,10 +3817,11 @@ catch (e2) {
|
|
|
3253
3817
|
|
|
3254
3818
|
const placeholder = document.createComment('stx-if');
|
|
3255
3819
|
let isInserted = true;
|
|
3256
|
-
let currentNodes = [];
|
|
3257
3820
|
|
|
3258
3821
|
// Handle <template> elements specially - clone their content
|
|
3259
3822
|
const isTemplate = el.tagName === 'TEMPLATE';
|
|
3823
|
+
const componentSetupSiblings = isTemplate ? [] : findConditionalComponentSetupSiblings(el);
|
|
3824
|
+
let currentNodes = isTemplate ? [] : [el, ...componentSetupSiblings];
|
|
3260
3825
|
|
|
3261
3826
|
// Capture BOTH element scope AND passedScope NOW before anything changes
|
|
3262
3827
|
// passedScope may contain @for iteration variables or parent component signals
|
|
@@ -3331,6 +3896,28 @@ catch (e2) {
|
|
|
3331
3896
|
childrenProcessed = true;
|
|
3332
3897
|
};
|
|
3333
3898
|
|
|
3899
|
+
// A <template :if> inserts clones of template.content rather than the
|
|
3900
|
+
// template element itself. Those clones still need the normal directive
|
|
3901
|
+
// and interpolation pass. Previously the initially-visible branch was
|
|
3902
|
+
// inserted and left inert, so bindings such as :text and {{ expression }}
|
|
3903
|
+
// leaked into the rendered UI until the condition toggled.
|
|
3904
|
+
const processTemplateNodes = (nodes) => {
|
|
3905
|
+
const childScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3906
|
+
// Components compiled inside a <template :if> carry setup scripts in the
|
|
3907
|
+
// template content. Browser-cloned scripts are inert, so register their
|
|
3908
|
+
// scopes and forwarded props before walking the inserted branch. This is
|
|
3909
|
+
// the same component hydration path used by <template :for>.
|
|
3910
|
+
hydrateComponentScopes(nodes, childScope);
|
|
3911
|
+
nodes.forEach(node => {
|
|
3912
|
+
if (!node.__stx_disposers && node.tagName !== 'SCRIPT')
|
|
3913
|
+
processElement(node, childScope);
|
|
3914
|
+
if (node.nodeType === 1) {
|
|
3915
|
+
node.removeAttribute('x-cloak');
|
|
3916
|
+
node.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
3917
|
+
}
|
|
3918
|
+
});
|
|
3919
|
+
};
|
|
3920
|
+
|
|
3334
3921
|
// Evaluate the :if expression \u2014 use direct signal read for simple refs,
|
|
3335
3922
|
// falling back to evalExpr for complex expressions
|
|
3336
3923
|
const fullScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
@@ -3359,16 +3946,8 @@ catch (e2) {
|
|
|
3359
3946
|
currentNodes = Array.from(el.content.childNodes).map(n => n.cloneNode(true));
|
|
3360
3947
|
const insertionAnchor = placeholder.nextSibling;
|
|
3361
3948
|
currentNodes.forEach(node => parent.insertBefore(node, insertionAnchor));
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
currentNodes.forEach(node => {
|
|
3365
|
-
if (node.nodeType === 1) {
|
|
3366
|
-
processElement(node, childScope);
|
|
3367
|
-
// Remove x-cloak from processed clones
|
|
3368
|
-
node.removeAttribute('x-cloak');
|
|
3369
|
-
node.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
3370
|
-
}
|
|
3371
|
-
});
|
|
3949
|
+
processTemplateNodes(currentNodes);
|
|
3950
|
+
childrenProcessed = true;
|
|
3372
3951
|
// Stamp insertion time for click propagation guard
|
|
3373
3952
|
currentNodes.forEach(function(n) { if (n.nodeType === 1) n.__stx_shown_at = performance.now(); });
|
|
3374
3953
|
isInserted = true;
|
|
@@ -3380,12 +3959,22 @@ else if (!value && isInserted) {
|
|
|
3380
3959
|
currentNodes.forEach(node => node.remove());
|
|
3381
3960
|
currentNodes = [];
|
|
3382
3961
|
isInserted = false;
|
|
3962
|
+
childrenProcessed = false;
|
|
3963
|
+
}
|
|
3964
|
+
else if (value && isInserted && !childrenProcessed) {
|
|
3965
|
+
// Initial template content is cloned before the effect runs. Process
|
|
3966
|
+
// it after the condition is confirmed so hidden branches stay inert.
|
|
3967
|
+
childrenProcessed = true;
|
|
3968
|
+
setTimeout(function() {
|
|
3969
|
+
if (isInserted) processTemplateNodes(currentNodes);
|
|
3970
|
+
}, 0);
|
|
3383
3971
|
}
|
|
3384
3972
|
}
|
|
3385
3973
|
else {
|
|
3386
3974
|
if (value && !isInserted) {
|
|
3387
3975
|
console.log('[stx] bindIf INSERTING element for :if=' + expr);
|
|
3388
|
-
|
|
3976
|
+
const insertionAnchor = placeholder.nextSibling;
|
|
3977
|
+
currentNodes.forEach(node => parent.insertBefore(node, insertionAnchor));
|
|
3389
3978
|
el.__stx_shown_at = performance.now();
|
|
3390
3979
|
isInserted = true;
|
|
3391
3980
|
}
|
|
@@ -3400,7 +3989,7 @@ else if (!value && isInserted) {
|
|
|
3400
3989
|
// once (#1737). Permanent disposal stays in bindFor (item removal)
|
|
3401
3990
|
// and cleanupContainer (SPA navigation). The double-bind guards make
|
|
3402
3991
|
// re-show idempotent, so toggling doesn't leak.
|
|
3403
|
-
|
|
3992
|
+
currentNodes.forEach(node => node.remove());
|
|
3404
3993
|
isInserted = false;
|
|
3405
3994
|
console.log('[stx] bindIf REMOVED, el.isConnected:', el.isConnected);
|
|
3406
3995
|
}
|
|
@@ -3415,7 +4004,15 @@ else if (!value && isInserted) {
|
|
|
3415
4004
|
childrenProcessed = true;
|
|
3416
4005
|
setTimeout(function() {
|
|
3417
4006
|
var childScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3418
|
-
|
|
4007
|
+
// A false single-element conditional is detached before the
|
|
4008
|
+
// DOMContentLoaded component-scope pass. When it becomes visible,
|
|
4009
|
+
// hydrate any component roots in the restored subtree first so
|
|
4010
|
+
// projected slot bindings and component-local directives receive
|
|
4011
|
+
// the same lifecycle as components inserted by template :if.
|
|
4012
|
+
var wasTrackedBeforeConditionalHydration = !!el.__stx_disposers;
|
|
4013
|
+
hydrateComponentScopes(currentNodes, childScope);
|
|
4014
|
+
if (wasTrackedBeforeConditionalHydration || !el.__stx_disposers)
|
|
4015
|
+
processElement(el, childScope);
|
|
3419
4016
|
// Remove x-cloak from the inserted subtree \u2014 the initial
|
|
3420
4017
|
// cloak removal (after processElement on the root) already
|
|
3421
4018
|
// ran before this deferred processing, so newly-inserted
|
|
@@ -3677,8 +4274,16 @@ else if (timer === null) {
|
|
|
3677
4274
|
}
|
|
3678
4275
|
|
|
3679
4276
|
function useClickOutside(target, handler) {
|
|
4277
|
+
function resolveTarget() {
|
|
4278
|
+
if (typeof target === 'string') return document.querySelector(target);
|
|
4279
|
+
if (target && typeof target === 'object') {
|
|
4280
|
+
if ('current' in target) return target.current;
|
|
4281
|
+
if ('value' in target) return target.value;
|
|
4282
|
+
}
|
|
4283
|
+
return target;
|
|
4284
|
+
}
|
|
3680
4285
|
function listener(event) {
|
|
3681
|
-
var el =
|
|
4286
|
+
var el = resolveTarget();
|
|
3682
4287
|
if (!el) return;
|
|
3683
4288
|
if (el === event.target || el.contains(event.target)) return;
|
|
3684
4289
|
handler(event);
|
|
@@ -3725,6 +4330,15 @@ else if (timer === null) {
|
|
|
3725
4330
|
};
|
|
3726
4331
|
}
|
|
3727
4332
|
|
|
4333
|
+
// Queue work after the current synchronous signal/effect flush. This mirrors
|
|
4334
|
+
// the Composition API nextTick contract and gives template refs a chance to
|
|
4335
|
+
// resolve after a structural directive inserts their elements.
|
|
4336
|
+
function nextTick(fn) {
|
|
4337
|
+
return Promise.resolve().then(function() {
|
|
4338
|
+
if (fn) fn();
|
|
4339
|
+
});
|
|
4340
|
+
}
|
|
4341
|
+
|
|
3728
4342
|
function useAsync(fn, options) {
|
|
3729
4343
|
options = options || {};
|
|
3730
4344
|
var asyncState = 'idle';
|
|
@@ -3863,6 +4477,61 @@ catch (e) {}
|
|
|
3863
4477
|
return matches;
|
|
3864
4478
|
}
|
|
3865
4479
|
|
|
4480
|
+
var scrollLockStates = new WeakMap();
|
|
4481
|
+
|
|
4482
|
+
function resolveScrollLockTarget(target) {
|
|
4483
|
+
var value = target;
|
|
4484
|
+
if (typeof value === 'function') value = value();
|
|
4485
|
+
if (value && typeof value === 'object' && 'current' in value) value = value.current;
|
|
4486
|
+
if (value && typeof value === 'object' && !value.nodeType && 'value' in value) value = value.value;
|
|
4487
|
+
return value || document.body || document.documentElement;
|
|
4488
|
+
}
|
|
4489
|
+
|
|
4490
|
+
function acquireScrollLock(element) {
|
|
4491
|
+
if (!element) return;
|
|
4492
|
+
var existing = scrollLockStates.get(element);
|
|
4493
|
+
if (existing) {
|
|
4494
|
+
existing.count += 1;
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
scrollLockStates.set(element, { count: 1, overflow: element.style.overflow });
|
|
4498
|
+
element.style.overflow = 'hidden';
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
function releaseScrollLock(element) {
|
|
4502
|
+
if (!element) return;
|
|
4503
|
+
var existing = scrollLockStates.get(element);
|
|
4504
|
+
if (!existing) return;
|
|
4505
|
+
existing.count -= 1;
|
|
4506
|
+
if (existing.count > 0) return;
|
|
4507
|
+
element.style.overflow = existing.overflow;
|
|
4508
|
+
scrollLockStates.delete(element);
|
|
4509
|
+
}
|
|
4510
|
+
|
|
4511
|
+
function useScrollLock(target) {
|
|
4512
|
+
var locked = state(false);
|
|
4513
|
+
var lockedElement = null;
|
|
4514
|
+
var unsubscribe = locked.subscribe(function(value) {
|
|
4515
|
+
if (value) {
|
|
4516
|
+
if (lockedElement) return;
|
|
4517
|
+
lockedElement = resolveScrollLockTarget(target);
|
|
4518
|
+
acquireScrollLock(lockedElement);
|
|
4519
|
+
}
|
|
4520
|
+
else if (lockedElement) {
|
|
4521
|
+
releaseScrollLock(lockedElement);
|
|
4522
|
+
lockedElement = null;
|
|
4523
|
+
}
|
|
4524
|
+
});
|
|
4525
|
+
onDestroy(function() {
|
|
4526
|
+
unsubscribe();
|
|
4527
|
+
if (lockedElement) {
|
|
4528
|
+
releaseScrollLock(lockedElement);
|
|
4529
|
+
lockedElement = null;
|
|
4530
|
+
}
|
|
4531
|
+
});
|
|
4532
|
+
return locked;
|
|
4533
|
+
}
|
|
4534
|
+
|
|
3866
4535
|
function usePreferredDark() { return useMediaQuery('(prefers-color-scheme: dark)'); }
|
|
3867
4536
|
function usePreferredLight() { return useMediaQuery('(prefers-color-scheme: light)'); }
|
|
3868
4537
|
function usePreferredReducedMotion() { return useMediaQuery('(prefers-reduced-motion: reduce)'); }
|
|
@@ -3950,6 +4619,30 @@ catch (e) {}
|
|
|
3950
4619
|
return s;
|
|
3951
4620
|
}
|
|
3952
4621
|
|
|
4622
|
+
var __stxGeneratedIdCounter = 0;
|
|
4623
|
+
|
|
4624
|
+
// Stable, scope-aware DOM ids for component authoring. A component setup
|
|
4625
|
+
// runs with window.__STX_CURRENT_ELEMENT__ pointing at its compiled root, so
|
|
4626
|
+
// the data-stx scope id is already the canonical per-instance identity. The
|
|
4627
|
+
// local counter supports multiple useId() calls in one component while the
|
|
4628
|
+
// global fallback covers page scripts and manually-mounted roots.
|
|
4629
|
+
function useId(prefix) {
|
|
4630
|
+
var normalizedPrefix = String(prefix || 'stx')
|
|
4631
|
+
.trim()
|
|
4632
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
4633
|
+
.replace(/^-+|-+$/g, '') || 'stx';
|
|
4634
|
+
var root = window.__STX_CURRENT_ELEMENT__;
|
|
4635
|
+
if (root && root.getAttribute) {
|
|
4636
|
+
var scopeId = root.getAttribute('data-stx-scope') || root.getAttribute('data-stx') || '';
|
|
4637
|
+
if (scopeId) {
|
|
4638
|
+
root.__stx_id_counter = (root.__stx_id_counter || 0) + 1;
|
|
4639
|
+
return normalizedPrefix + '-' + String(scopeId).replace(/[^a-zA-Z0-9_-]+/g, '-') + '-' + root.__stx_id_counter;
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
__stxGeneratedIdCounter++;
|
|
4643
|
+
return normalizedPrefix + '-' + __stxGeneratedIdCounter;
|
|
4644
|
+
}
|
|
4645
|
+
|
|
3953
4646
|
// Reactive prop binding. Bridges the gap between a parent's clientReactive
|
|
3954
4647
|
// attribute (e.g. :open="modalOpen()" on a component) and the component's
|
|
3955
4648
|
// internal state. Returns a signal whose value tracks the named attribute
|
|
@@ -4036,7 +4729,19 @@ catch (e) {}
|
|
|
4036
4729
|
var raw = attribute.raw;
|
|
4037
4730
|
if (raw === lastRaw) return;
|
|
4038
4731
|
lastRaw = raw;
|
|
4039
|
-
var
|
|
4732
|
+
var directValues = root.__stx_direct_prop_values;
|
|
4733
|
+
var directValue = directValues && directValues[name];
|
|
4734
|
+
var next;
|
|
4735
|
+
if (directValue && directValue.raw === raw) {
|
|
4736
|
+
next = directValue.value;
|
|
4737
|
+
delete directValues[name];
|
|
4738
|
+
for (var directKey in directValues) {
|
|
4739
|
+
if (directValues[directKey] === directValue) delete directValues[directKey];
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
else {
|
|
4743
|
+
next = hasAttr ? parse(raw) : staticValue;
|
|
4744
|
+
}
|
|
4040
4745
|
if (next !== s()) s.set(next);
|
|
4041
4746
|
});
|
|
4042
4747
|
observer.observe(root, { attributes: true, attributeFilter: attributeNames });
|
|
@@ -4579,16 +5284,19 @@ catch (e) {}
|
|
|
4579
5284
|
useCounter,
|
|
4580
5285
|
useClickOutside,
|
|
4581
5286
|
useFocus,
|
|
5287
|
+
nextTick,
|
|
4582
5288
|
useAsync,
|
|
4583
5289
|
useLocalStorage,
|
|
4584
5290
|
useSessionStorage,
|
|
4585
5291
|
useCookie,
|
|
5292
|
+
useId,
|
|
4586
5293
|
useReactiveProp,
|
|
4587
5294
|
useEventListener,
|
|
4588
5295
|
useWebSocket,
|
|
4589
5296
|
useColorMode,
|
|
4590
5297
|
useDark,
|
|
4591
5298
|
useMediaQuery,
|
|
5299
|
+
useScrollLock,
|
|
4592
5300
|
usePreferredDark,
|
|
4593
5301
|
usePreferredLight,
|
|
4594
5302
|
usePreferredReducedMotion,
|
|
@@ -4633,10 +5341,23 @@ catch (e) {}
|
|
|
4633
5341
|
}
|
|
4634
5342
|
return result;
|
|
4635
5343
|
},
|
|
4636
|
-
defineEmits: function() {
|
|
5344
|
+
defineEmits: function(events) {
|
|
4637
5345
|
var el = window.__STX_CURRENT_ELEMENT__ || null;
|
|
5346
|
+
var declaredEvents = Array.isArray(events)
|
|
5347
|
+
? events
|
|
5348
|
+
: (events && typeof events === 'object' ? Object.keys(events) : []);
|
|
5349
|
+
if (el && declaredEvents.length) {
|
|
5350
|
+
el.__stx_component_emits = el.__stx_component_emits || Object.create(null);
|
|
5351
|
+
declaredEvents.forEach(function(event) {
|
|
5352
|
+
el.__stx_component_emits[event] = true;
|
|
5353
|
+
});
|
|
5354
|
+
}
|
|
4638
5355
|
return function(event, payload) {
|
|
4639
5356
|
var target = el || document.body;
|
|
5357
|
+
if (el) {
|
|
5358
|
+
el.__stx_component_emits = el.__stx_component_emits || Object.create(null);
|
|
5359
|
+
el.__stx_component_emits[event] = true;
|
|
5360
|
+
}
|
|
4640
5361
|
target.dispatchEvent(new CustomEvent(event, { detail: payload, bubbles: true, cancelable: true }));
|
|
4641
5362
|
};
|
|
4642
5363
|
},
|
|
@@ -5093,6 +5814,7 @@ else {
|
|
|
5093
5814
|
window.useCounter = useCounter;
|
|
5094
5815
|
window.useClickOutside = useClickOutside;
|
|
5095
5816
|
window.useFocus = useFocus;
|
|
5817
|
+
window.nextTick = nextTick;
|
|
5096
5818
|
window.useAsync = useAsync;
|
|
5097
5819
|
window.useLocalStorage = useLocalStorage;
|
|
5098
5820
|
window.useEventListener = useEventListener;
|