@stacksjs/stx 0.2.119 → 0.2.121
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 +1869 -863
- package/dist/client-script-bundler.d.ts +9 -7
- package/dist/client-script-bundler.js +43 -4
- package/dist/client-script.d.ts +24 -0
- package/dist/client-script.js +59 -5
- package/dist/component-library.js +30 -12
- 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 +12708 -118
- 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/native-build.d.ts +6 -0
- package/dist/native-build.js +77 -60
- package/dist/process.js +11 -3
- package/dist/pwa.js +1232 -508
- package/dist/render.js +6 -1
- 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 +785 -62
- package/dist/ssg.js +1176 -452
- 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,17 @@ 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
|
|
2418
|
+
&& (expressionCallsSignal(value, k) || expressionUsesSignalApi(value, k))) {
|
|
2184
2419
|
return 'var ' + k + ' = __s["' + k + '"]';
|
|
2185
2420
|
}
|
|
2186
2421
|
return 'var ' + k + ' = __s["' + k + '"] && typeof __s["' + k + '"] === "function" && __s["' + k + '"]._isSignal ? __s["' + k + '"]() : __s["' + k + '"]';
|
|
@@ -2387,12 +2622,54 @@ catch (e) {
|
|
|
2387
2622
|
}
|
|
2388
2623
|
};
|
|
2389
2624
|
|
|
2390
|
-
if (tag === 'input' &&
|
|
2391
|
-
|
|
2392
|
-
|
|
2625
|
+
if (tag === 'input' && type === 'checkbox') {
|
|
2626
|
+
var syncCheckbox = () => {
|
|
2627
|
+
var current = getValue();
|
|
2628
|
+
el.checked = Array.isArray(current)
|
|
2629
|
+
? current.some(function(item) { return String(item) === String(el.value); })
|
|
2630
|
+
: Boolean(current);
|
|
2631
|
+
};
|
|
2632
|
+
el.__stx_model_sync = syncCheckbox;
|
|
2633
|
+
effect(syncCheckbox);
|
|
2634
|
+
el.addEventListener('change', () => {
|
|
2635
|
+
var current = getValue();
|
|
2636
|
+
if (!Array.isArray(current)) {
|
|
2637
|
+
setValue(el.checked);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
var checkboxValue = coerceValue(el.value);
|
|
2642
|
+
var next = current.filter(function(item) { return String(item) !== String(checkboxValue); });
|
|
2643
|
+
if (el.checked) next.push(checkboxValue);
|
|
2644
|
+
setValue(next);
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
else if (tag === 'input' && type === 'radio') {
|
|
2648
|
+
var syncRadio = () => { el.checked = String(getValue()) === String(el.value); };
|
|
2649
|
+
el.__stx_model_sync = syncRadio;
|
|
2650
|
+
effect(syncRadio);
|
|
2651
|
+
el.addEventListener('change', () => {
|
|
2652
|
+
if (el.checked) setValue(coerceValue(el.value));
|
|
2653
|
+
});
|
|
2393
2654
|
}
|
|
2394
2655
|
else if (tag === 'select') {
|
|
2395
|
-
|
|
2656
|
+
var syncSelect = () => { el.value = getValue() ?? ''; };
|
|
2657
|
+
el.__stx_model_sync = syncSelect;
|
|
2658
|
+
effect(syncSelect);
|
|
2659
|
+
if (!el.__stx_model_observer && typeof window.MutationObserver !== 'undefined') {
|
|
2660
|
+
el.__stx_model_observer = new window.MutationObserver(syncSelect);
|
|
2661
|
+
el.__stx_model_observer.observe(el, {
|
|
2662
|
+
childList: true,
|
|
2663
|
+
subtree: true,
|
|
2664
|
+
attributes: true,
|
|
2665
|
+
attributeFilter: ['value'],
|
|
2666
|
+
});
|
|
2667
|
+
el.__stx_destroy = el.__stx_destroy || [];
|
|
2668
|
+
el.__stx_destroy.push(function() {
|
|
2669
|
+
if (el.__stx_model_observer) el.__stx_model_observer.disconnect();
|
|
2670
|
+
el.__stx_model_observer = null;
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2396
2673
|
el.addEventListener('change', () => setValue(coerceValue(el.value)));
|
|
2397
2674
|
}
|
|
2398
2675
|
else {
|
|
@@ -2599,6 +2876,179 @@ else if (typeof value === 'string') {
|
|
|
2599
2876
|
});
|
|
2600
2877
|
}
|
|
2601
2878
|
|
|
2879
|
+
var __forComponentScopeCounter = 0;
|
|
2880
|
+
var __forComponentSetupFactories = new Map();
|
|
2881
|
+
|
|
2882
|
+
function forEachGroupElement(nodes, callback) {
|
|
2883
|
+
nodes.forEach(function(node) {
|
|
2884
|
+
if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
|
|
2885
|
+
callback(node);
|
|
2886
|
+
if (node.querySelectorAll) {
|
|
2887
|
+
node.querySelectorAll('*').forEach(function(child) { callback(child); });
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
// A signal component expanded inside <template :for> carries a compiled
|
|
2893
|
+
// data-stx-scope id and setup script. cloneNode() used to duplicate that id
|
|
2894
|
+
// for every row, so all instances shared the first child's props and state.
|
|
2895
|
+
// Give each cloned component tree its own ids before insertion, and rewrite
|
|
2896
|
+
// the setup scripts that target those ids.
|
|
2897
|
+
function remapForComponentScopes(nodes) {
|
|
2898
|
+
var scopeMap = {};
|
|
2899
|
+
forEachGroupElement(nodes, function(node) {
|
|
2900
|
+
if (!node.hasAttribute || !node.hasAttribute('data-stx-scope')) return;
|
|
2901
|
+
var oldId = node.getAttribute('data-stx-scope');
|
|
2902
|
+
if (!oldId) return;
|
|
2903
|
+
if (!scopeMap[oldId])
|
|
2904
|
+
scopeMap[oldId] = oldId + '_for_' + (++__forComponentScopeCounter);
|
|
2905
|
+
// Keep the compiler's original id through any additional structural
|
|
2906
|
+
// clones. DOM cloneNode() preserves attributes but not expando fields.
|
|
2907
|
+
if (!node.hasAttribute('data-stx-template-scope'))
|
|
2908
|
+
node.setAttribute('data-stx-template-scope', oldId);
|
|
2909
|
+
node.setAttribute('data-stx-scope', scopeMap[oldId]);
|
|
2910
|
+
});
|
|
2911
|
+
|
|
2912
|
+
var oldIds = Object.keys(scopeMap);
|
|
2913
|
+
if (!oldIds.length) return;
|
|
2914
|
+
|
|
2915
|
+
forEachGroupElement(nodes, function(node) {
|
|
2916
|
+
if (node.attributes) {
|
|
2917
|
+
Array.from(node.attributes).forEach(function(rawAttribute) {
|
|
2918
|
+
var attribute = Array.isArray(rawAttribute)
|
|
2919
|
+
? { name: rawAttribute[0], value: rawAttribute[1] }
|
|
2920
|
+
: rawAttribute;
|
|
2921
|
+
if (!attribute || attribute.name === 'data-stx-scope' || attribute.name === 'data-stx-template-scope') return;
|
|
2922
|
+
var value = attribute.value;
|
|
2923
|
+
if (typeof value !== 'string') return;
|
|
2924
|
+
oldIds.forEach(function(oldId) {
|
|
2925
|
+
if (value.indexOf(oldId) !== -1)
|
|
2926
|
+
value = value.split(oldId).join(scopeMap[oldId]);
|
|
2927
|
+
});
|
|
2928
|
+
if (value !== attribute.value) node.setAttribute(attribute.name, value);
|
|
2929
|
+
});
|
|
2930
|
+
}
|
|
2931
|
+
});
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function hydrateComponentScopes(nodes, callerScope) {
|
|
2935
|
+
var roots = [];
|
|
2936
|
+
var scripts = [];
|
|
2937
|
+
forEachGroupElement(nodes, function(node) {
|
|
2938
|
+
if (node.hasAttribute && node.hasAttribute('data-stx-scope')) roots.push(node);
|
|
2939
|
+
if (node.tagName === 'SCRIPT' && node.hasAttribute('data-stx-scoped')) scripts.push(node);
|
|
2940
|
+
});
|
|
2941
|
+
if (!roots.length) return;
|
|
2942
|
+
|
|
2943
|
+
function hasPendingStructuralAncestor(root) {
|
|
2944
|
+
var cursor = root && root.parentElement;
|
|
2945
|
+
while (cursor && cursor.nodeType === Node.ELEMENT_NODE) {
|
|
2946
|
+
if (cursor.hasAttribute('@for')
|
|
2947
|
+
|| cursor.hasAttribute(':for')
|
|
2948
|
+
|| cursor.hasAttribute('x-for')
|
|
2949
|
+
|| cursor.hasAttribute('@if')
|
|
2950
|
+
|| cursor.hasAttribute(':if')
|
|
2951
|
+
|| cursor.hasAttribute('x-if')
|
|
2952
|
+
|| cursor.hasAttribute('@else-if')
|
|
2953
|
+
|| cursor.hasAttribute(':else-if')
|
|
2954
|
+
|| cursor.hasAttribute('x-else-if')
|
|
2955
|
+
|| cursor.hasAttribute('@else')
|
|
2956
|
+
|| cursor.hasAttribute(':else')
|
|
2957
|
+
|| cursor.hasAttribute('x-else'))
|
|
2958
|
+
return true;
|
|
2959
|
+
cursor = cursor.parentElement;
|
|
2960
|
+
}
|
|
2961
|
+
return false;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// Scripts cloned from template.content are inert in browsers. Compile each
|
|
2965
|
+
// distinct component setup body once, then run that cached function for
|
|
2966
|
+
// every loop instance. Temporarily restoring the template's original scope
|
|
2967
|
+
// id lets existing compiled setup code locate the correct root without
|
|
2968
|
+
// rewriting and reparsing the full script for every row.
|
|
2969
|
+
scripts.forEach(function(old) {
|
|
2970
|
+
var text = old.textContent || '';
|
|
2971
|
+
var pendingRoots = roots.filter(function(root) {
|
|
2972
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
2973
|
+
var originalScopeId = root.getAttribute('data-stx-template-scope') || scopeId;
|
|
2974
|
+
return scopeId
|
|
2975
|
+
&& originalScopeId
|
|
2976
|
+
&& text.indexOf(originalScopeId) !== -1
|
|
2977
|
+
&& !hasPendingStructuralAncestor(root)
|
|
2978
|
+
&& !(window.stx._scopes && window.stx._scopes[scopeId]);
|
|
2979
|
+
});
|
|
2980
|
+
if (!pendingRoots.length) return;
|
|
2981
|
+
|
|
2982
|
+
var setup = __forComponentSetupFactories.get(text);
|
|
2983
|
+
if (!setup) {
|
|
2984
|
+
try {
|
|
2985
|
+
setup = new Function(text);
|
|
2986
|
+
__forComponentSetupFactories.set(text, setup);
|
|
2987
|
+
}
|
|
2988
|
+
catch (e) {
|
|
2989
|
+
console.error('[stx] cloned component setup compile error:', e);
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
pendingRoots.forEach(function(root) {
|
|
2995
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
2996
|
+
var originalScopeId = root.getAttribute('data-stx-template-scope') || scopeId;
|
|
2997
|
+
if (!scopeId || !originalScopeId) return;
|
|
2998
|
+
|
|
2999
|
+
root.setAttribute('data-stx-scope', originalScopeId);
|
|
3000
|
+
try {
|
|
3001
|
+
setup();
|
|
3002
|
+
var registeredScope = window.stx._scopes && (window.stx._scopes[originalScopeId] || window.stx._scopes[scopeId]);
|
|
3003
|
+
if (registeredScope && originalScopeId !== scopeId) {
|
|
3004
|
+
delete window.stx._scopes[originalScopeId];
|
|
3005
|
+
window.stx._scopes[scopeId] = registeredScope;
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
catch (e) {
|
|
3009
|
+
console.error('[stx] cloned component setup error:', e);
|
|
3010
|
+
}
|
|
3011
|
+
finally {
|
|
3012
|
+
root.setAttribute('data-stx-scope', scopeId);
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
|
|
3016
|
+
// A component that is itself a structural source still needs its setup
|
|
3017
|
+
// sibling when bindFor/bindIf turns that source into a clone template.
|
|
3018
|
+
// Final row instances have no structural attribute and can discard it.
|
|
3019
|
+
var isStructuralSource = pendingRoots.some(function(root) {
|
|
3020
|
+
return root.hasAttribute('@for')
|
|
3021
|
+
|| root.hasAttribute(':for')
|
|
3022
|
+
|| root.hasAttribute('x-for')
|
|
3023
|
+
|| root.hasAttribute('@if')
|
|
3024
|
+
|| root.hasAttribute(':if')
|
|
3025
|
+
|| root.hasAttribute('x-if');
|
|
3026
|
+
});
|
|
3027
|
+
if (!isStructuralSource && old.parentNode) old.parentNode.removeChild(old);
|
|
3028
|
+
});
|
|
3029
|
+
|
|
3030
|
+
roots.forEach(function(root) {
|
|
3031
|
+
var scopeId = root.getAttribute('data-stx-scope');
|
|
3032
|
+
var scopeVars = scopeId && window.stx._scopes && window.stx._scopes[scopeId];
|
|
3033
|
+
var resolvedCallerScope = resolveComponentCallerScope(root, callerScope);
|
|
3034
|
+
root.__stx_parent_scope = resolvedCallerScope;
|
|
3035
|
+
bindParentComponentProps(root, resolvedCallerScope);
|
|
3036
|
+
if (!scopeVars || root.__stx_disposers) return;
|
|
3037
|
+
scopeVars.__el = root;
|
|
3038
|
+
var childScope = { ...resolvedCallerScope, ...scopeVars };
|
|
3039
|
+
root.__stx_disposers = trackEffects(function() {
|
|
3040
|
+
processElement(root, childScope);
|
|
3041
|
+
});
|
|
3042
|
+
if (scopeVars.__mountCallbacks && !scopeVars.__mounted) {
|
|
3043
|
+
scopeVars.__mounted = true;
|
|
3044
|
+
scopeVars.__mountCallbacks.forEach(function(fn) {
|
|
3045
|
+
try { fn(); }
|
|
3046
|
+
catch (e) { console.error('[stx] onMount error:', e); }
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
|
|
2602
3052
|
function bindFor(el, passedScope = componentScope, attrName = '@for') {
|
|
2603
3053
|
if (el.__stx_for_bound) return;
|
|
2604
3054
|
el.__stx_for_bound = true;
|
|
@@ -2622,6 +3072,34 @@ else if (typeof value === 'string') {
|
|
|
2622
3072
|
|
|
2623
3073
|
const placeholder = document.createComment('stx-for');
|
|
2624
3074
|
const isTemplate = el.tagName === 'TEMPLATE';
|
|
3075
|
+
const componentSetupTemplates = [];
|
|
3076
|
+
if (!isTemplate && el.hasAttribute('data-stx-scope')) {
|
|
3077
|
+
const componentScopeId = el.getAttribute('data-stx-scope');
|
|
3078
|
+
const componentTemplateScopeId = el.getAttribute('data-stx-template-scope') || componentScopeId;
|
|
3079
|
+
let setupSibling = el.nextSibling;
|
|
3080
|
+
while (setupSibling) {
|
|
3081
|
+
const nextSetupSibling = setupSibling.nextSibling;
|
|
3082
|
+
if (setupSibling.nodeType === Node.TEXT_NODE && !(setupSibling.textContent || '').trim()) {
|
|
3083
|
+
setupSibling = nextSetupSibling;
|
|
3084
|
+
continue;
|
|
3085
|
+
}
|
|
3086
|
+
if (setupSibling.nodeType === Node.COMMENT_NODE) {
|
|
3087
|
+
setupSibling = nextSetupSibling;
|
|
3088
|
+
continue;
|
|
3089
|
+
}
|
|
3090
|
+
if (setupSibling.nodeType === Node.ELEMENT_NODE
|
|
3091
|
+
&& setupSibling.tagName === 'SCRIPT'
|
|
3092
|
+
&& setupSibling.hasAttribute('data-stx-scoped')
|
|
3093
|
+
&& ((setupSibling.textContent || '').indexOf(componentScopeId) !== -1
|
|
3094
|
+
|| (componentTemplateScopeId && (setupSibling.textContent || '').indexOf(componentTemplateScopeId) !== -1))) {
|
|
3095
|
+
componentSetupTemplates.push(setupSibling.cloneNode(true));
|
|
3096
|
+
setupSibling.remove();
|
|
3097
|
+
setupSibling = nextSetupSibling;
|
|
3098
|
+
continue;
|
|
3099
|
+
}
|
|
3100
|
+
break;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
2625
3103
|
|
|
2626
3104
|
// Check if element also has @if / :if - need to handle together
|
|
2627
3105
|
const ifExpr = el.getAttribute('@if') || el.getAttribute(':if');
|
|
@@ -2658,6 +3136,11 @@ else if (typeof value === 'string') {
|
|
|
2658
3136
|
const capturedScope = findElementScope(el) || findElementScope(parent);
|
|
2659
3137
|
|
|
2660
3138
|
parent.insertBefore(placeholder, el);
|
|
3139
|
+
// Component setup scripts execute while the server-rendered loop source is
|
|
3140
|
+
// still in the document. That source becomes a template, not a live
|
|
3141
|
+
// instance, so reclaim all registered scopes before removing it. Every
|
|
3142
|
+
// rendered iteration receives remapped setup scripts and fresh scopes.
|
|
3143
|
+
disposeSubtreeScopes(el);
|
|
2661
3144
|
parent.removeChild(el);
|
|
2662
3145
|
|
|
2663
3146
|
// Capture :key expression BEFORE removing the attribute
|
|
@@ -2694,6 +3177,49 @@ else {
|
|
|
2694
3177
|
let loadingElement = null;
|
|
2695
3178
|
let emptyElement = null;
|
|
2696
3179
|
|
|
3180
|
+
// A <template :for> can contain another structural directive. That
|
|
3181
|
+
// nested directive replaces its source node with a placeholder and owns
|
|
3182
|
+
// additional live siblings around that placeholder. Track an explicit
|
|
3183
|
+
// boundary for every template iteration so keyed reuse moves the complete
|
|
3184
|
+
// live range, including nodes created by nested :for / :if bindings.
|
|
3185
|
+
// Moving only the original cloned nodes pulled static siblings past the
|
|
3186
|
+
// nested range on the next outer update (header, then rows became rows,
|
|
3187
|
+
// then header).
|
|
3188
|
+
const liveGroupNodes = (group) => {
|
|
3189
|
+
if (!isTemplate || !group || group.length < 2) return group || [];
|
|
3190
|
+
const start = group[0];
|
|
3191
|
+
const end = group[group.length - 1];
|
|
3192
|
+
if (!start || !end || start.parentNode !== parent || end.parentNode !== parent)
|
|
3193
|
+
return group.filter(groupNode => groupNode && groupNode.parentNode === parent);
|
|
3194
|
+
|
|
3195
|
+
const nodes = [];
|
|
3196
|
+
let currentNode = start;
|
|
3197
|
+
while (currentNode) {
|
|
3198
|
+
nodes.push(currentNode);
|
|
3199
|
+
if (currentNode === end) break;
|
|
3200
|
+
currentNode = currentNode.nextSibling;
|
|
3201
|
+
}
|
|
3202
|
+
return nodes;
|
|
3203
|
+
};
|
|
3204
|
+
|
|
3205
|
+
const removeGroup = (group) => {
|
|
3206
|
+
const liveNodes = liveGroupNodes(group);
|
|
3207
|
+
const liveSet = new Set(liveNodes);
|
|
3208
|
+
liveNodes.forEach(e => { disposeSubtreeScopes(e); e.remove(); });
|
|
3209
|
+
group.forEach(groupNode => {
|
|
3210
|
+
if (liveSet.has(groupNode)) return;
|
|
3211
|
+
disposeSubtreeScopes(groupNode);
|
|
3212
|
+
groupNode.remove();
|
|
3213
|
+
});
|
|
3214
|
+
};
|
|
3215
|
+
|
|
3216
|
+
const clearRenderedItems = () => {
|
|
3217
|
+
currentGroups.forEach(removeGroup);
|
|
3218
|
+
currentElements = [];
|
|
3219
|
+
currentGroups = [];
|
|
3220
|
+
currentKeys = [];
|
|
3221
|
+
};
|
|
3222
|
+
|
|
2697
3223
|
// Helper to evaluate with captured scope (auto-unwraps signals)
|
|
2698
3224
|
// NOTE: This eagerly reads ALL signals in scope \u2014 use evalLazy inside effects
|
|
2699
3225
|
// to avoid over-broad dependency tracking.
|
|
@@ -2787,6 +3313,7 @@ catch (e) {
|
|
|
2787
3313
|
// The item is wrapped in a signal so bindings track it reactively.
|
|
2788
3314
|
const createItemElements = (item, index, key) => {
|
|
2789
3315
|
const itemSignal = state(item);
|
|
3316
|
+
itemSignal._isStxLoopItem = true;
|
|
2790
3317
|
const indexSignal = state(index);
|
|
2791
3318
|
if (key) itemSignalMap.set(key, { item: itemSignal, index: indexSignal });
|
|
2792
3319
|
|
|
@@ -2796,6 +3323,7 @@ catch (e) {
|
|
|
2796
3323
|
|
|
2797
3324
|
const elements = [];
|
|
2798
3325
|
if (isTemplate) {
|
|
3326
|
+
elements.push(document.createComment('stx-for-item-start'));
|
|
2799
3327
|
Array.from(templateContent.childNodes).forEach(node => {
|
|
2800
3328
|
const clone = node.cloneNode(true);
|
|
2801
3329
|
if (clone.nodeType === 1) {
|
|
@@ -2805,11 +3333,16 @@ catch (e) {
|
|
|
2805
3333
|
}
|
|
2806
3334
|
elements.push(clone);
|
|
2807
3335
|
});
|
|
3336
|
+
elements.push(document.createComment('stx-for-item-end'));
|
|
2808
3337
|
} else {
|
|
2809
3338
|
const clone = templateContent.cloneNode(true);
|
|
2810
3339
|
clone.__stx_for_scope = itemScope;
|
|
2811
3340
|
elements.push(clone);
|
|
3341
|
+
componentSetupTemplates.forEach(function(setupTemplate) {
|
|
3342
|
+
elements.push(setupTemplate.cloneNode(true));
|
|
3343
|
+
});
|
|
2812
3344
|
}
|
|
3345
|
+
remapForComponentScopes(elements);
|
|
2813
3346
|
return elements;
|
|
2814
3347
|
};
|
|
2815
3348
|
|
|
@@ -2835,10 +3368,7 @@ catch (e) {
|
|
|
2835
3368
|
const isLoading = evalLazy(loadingExpr);
|
|
2836
3369
|
if (isLoading) {
|
|
2837
3370
|
// Dispose scopes registered by items before they leave the DOM (#1727).
|
|
2838
|
-
|
|
2839
|
-
currentElements = [];
|
|
2840
|
-
currentGroups = [];
|
|
2841
|
-
currentKeys = [];
|
|
3371
|
+
clearRenderedItems();
|
|
2842
3372
|
showLoading();
|
|
2843
3373
|
return;
|
|
2844
3374
|
}
|
|
@@ -2884,10 +3414,7 @@ catch (e) {
|
|
|
2884
3414
|
const ifValue = evalLazy(ifExpr);
|
|
2885
3415
|
if (!ifValue) {
|
|
2886
3416
|
// Dispose scopes registered by items before they leave the DOM (#1727).
|
|
2887
|
-
|
|
2888
|
-
currentElements = [];
|
|
2889
|
-
currentGroups = [];
|
|
2890
|
-
currentKeys = [];
|
|
3417
|
+
clearRenderedItems();
|
|
2891
3418
|
hideEmpty();
|
|
2892
3419
|
return;
|
|
2893
3420
|
}
|
|
@@ -2925,10 +3452,7 @@ catch (e) {
|
|
|
2925
3452
|
// Check empty state (Feature #3)
|
|
2926
3453
|
if (list.length === 0) {
|
|
2927
3454
|
// Dispose scopes registered by items before they leave the DOM (#1727).
|
|
2928
|
-
|
|
2929
|
-
currentElements = [];
|
|
2930
|
-
currentGroups = [];
|
|
2931
|
-
currentKeys = [];
|
|
3455
|
+
clearRenderedItems();
|
|
2932
3456
|
if (emptyExpr) {
|
|
2933
3457
|
const emptyContent = evalLazy(emptyExpr);
|
|
2934
3458
|
if (emptyContent && typeof emptyContent === 'string') {
|
|
@@ -2981,9 +3505,10 @@ catch (e) {
|
|
|
2981
3505
|
// group's nodes. Its own placeholder owns reinsertion, so keyed reuse
|
|
2982
3506
|
// must not resurrect that hidden node.
|
|
2983
3507
|
const group = existing.shift();
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
3508
|
+
const liveNodes = liveGroupNodes(group);
|
|
3509
|
+
const fragment = document.createDocumentFragment();
|
|
3510
|
+
liveNodes.forEach(groupNode => fragment.appendChild(groupNode));
|
|
3511
|
+
parent.insertBefore(fragment, placeholder);
|
|
2987
3512
|
newElements.push(...group);
|
|
2988
3513
|
newGroups.push(group);
|
|
2989
3514
|
usedKeys.add(key);
|
|
@@ -3000,14 +3525,20 @@ catch (e) {
|
|
|
3000
3525
|
const elements = createItemElements(list[i], i, key);
|
|
3001
3526
|
elements.forEach(el => {
|
|
3002
3527
|
parent.insertBefore(el, placeholder);
|
|
3528
|
+
if (tgReady && el.isConnected) tgEnter(el, tgName);
|
|
3529
|
+
});
|
|
3530
|
+
hydrateComponentScopes(elements, elements.find(function(node) {
|
|
3531
|
+
return node && node.__stx_for_scope;
|
|
3532
|
+
})?.__stx_for_scope || passedScope);
|
|
3533
|
+
elements.forEach(el => {
|
|
3003
3534
|
if (el.nodeType === 1) {
|
|
3004
3535
|
var itemScope = el.__stx_for_scope || passedScope;
|
|
3005
3536
|
delete el.__stx_for_scope;
|
|
3006
|
-
|
|
3537
|
+
if (!el.__stx_disposers && el.tagName !== 'SCRIPT')
|
|
3538
|
+
processElement(el, itemScope);
|
|
3007
3539
|
el.removeAttribute('x-cloak');
|
|
3008
3540
|
el.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
3009
3541
|
}
|
|
3010
|
-
if (tgReady && el.isConnected) tgEnter(el, tgName);
|
|
3011
3542
|
});
|
|
3012
3543
|
newElements.push(...elements);
|
|
3013
3544
|
newGroups.push(elements);
|
|
@@ -3019,11 +3550,28 @@ catch (e) {
|
|
|
3019
3550
|
// remove() call (#1727).
|
|
3020
3551
|
for (const [key, groups] of oldKeyMap) {
|
|
3021
3552
|
if (!usedKeys.has(key)) {
|
|
3022
|
-
const elements = groups.flat();
|
|
3023
3553
|
// In a transition group, run the leave animation and let it remove the
|
|
3024
3554
|
// node; otherwise remove immediately. Scopes are disposed up-front
|
|
3025
3555
|
// either way (their destroy hooks shouldn't wait on a CSS transition).
|
|
3026
|
-
|
|
3556
|
+
groups.forEach((group) => {
|
|
3557
|
+
if (tgReady) {
|
|
3558
|
+
const liveNodes = liveGroupNodes(group);
|
|
3559
|
+
const transitioned = new Set();
|
|
3560
|
+
liveNodes.forEach((el) => {
|
|
3561
|
+
disposeSubtreeScopes(el);
|
|
3562
|
+
if (tgLeave(el, tgName)) transitioned.add(el);
|
|
3563
|
+
else el.remove();
|
|
3564
|
+
});
|
|
3565
|
+
group.forEach((el) => {
|
|
3566
|
+
if (transitioned.has(el) || liveNodes.includes(el)) return;
|
|
3567
|
+
disposeSubtreeScopes(el);
|
|
3568
|
+
el.remove();
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
else {
|
|
3572
|
+
removeGroup(group);
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3027
3575
|
itemSignalMap.delete(key);
|
|
3028
3576
|
}
|
|
3029
3577
|
}
|
|
@@ -3222,9 +3770,26 @@ catch (e2) {
|
|
|
3222
3770
|
(function (branch) {
|
|
3223
3771
|
setTimeout(function () {
|
|
3224
3772
|
var childScope = { ...globalHelpers, ...capturedComponentScope, ...(branch.capturedElementScope || {}) };
|
|
3773
|
+
// Components compiled inside any branch of an if/else-if/else
|
|
3774
|
+
// chain carry scoped setup scripts with them. Branch nodes are
|
|
3775
|
+
// detached before those scripts can execute, so simply
|
|
3776
|
+
// re-inserting the selected branch leaves component props,
|
|
3777
|
+
// events, directives, and projected slot expressions inert.
|
|
3778
|
+
// Hydrate those scopes before the normal subtree walk, matching
|
|
3779
|
+
// the single-template :if and :for insertion paths.
|
|
3780
|
+
var previouslyTrackedNodes = new Set();
|
|
3781
|
+
branch.nodes.forEach(function (n) {
|
|
3782
|
+
if (n && n.__stx_disposers) previouslyTrackedNodes.add(n);
|
|
3783
|
+
});
|
|
3784
|
+
hydrateComponentScopes(branch.nodes, childScope);
|
|
3225
3785
|
branch.nodes.forEach(function (n) {
|
|
3226
3786
|
if (n.nodeType !== 1) return; // whitespace/text between template children
|
|
3227
|
-
|
|
3787
|
+
// A scoped component receives its disposer in the outer scope
|
|
3788
|
+
// walk immediately after processElement returns from binding
|
|
3789
|
+
// this conditional. That first pass intentionally stops at
|
|
3790
|
+
// :if, so a disposer does not mean its children were hydrated.
|
|
3791
|
+
if (previouslyTrackedNodes.has(n) || !n.__stx_disposers)
|
|
3792
|
+
processElement(n, childScope);
|
|
3228
3793
|
n.removeAttribute('x-cloak');
|
|
3229
3794
|
n.querySelectorAll('[x-cloak]').forEach(function (c) { c.removeAttribute('x-cloak'); });
|
|
3230
3795
|
});
|
|
@@ -3253,10 +3818,11 @@ catch (e2) {
|
|
|
3253
3818
|
|
|
3254
3819
|
const placeholder = document.createComment('stx-if');
|
|
3255
3820
|
let isInserted = true;
|
|
3256
|
-
let currentNodes = [];
|
|
3257
3821
|
|
|
3258
3822
|
// Handle <template> elements specially - clone their content
|
|
3259
3823
|
const isTemplate = el.tagName === 'TEMPLATE';
|
|
3824
|
+
const componentSetupSiblings = isTemplate ? [] : findConditionalComponentSetupSiblings(el);
|
|
3825
|
+
let currentNodes = isTemplate ? [] : [el, ...componentSetupSiblings];
|
|
3260
3826
|
|
|
3261
3827
|
// Capture BOTH element scope AND passedScope NOW before anything changes
|
|
3262
3828
|
// passedScope may contain @for iteration variables or parent component signals
|
|
@@ -3331,6 +3897,28 @@ catch (e2) {
|
|
|
3331
3897
|
childrenProcessed = true;
|
|
3332
3898
|
};
|
|
3333
3899
|
|
|
3900
|
+
// A <template :if> inserts clones of template.content rather than the
|
|
3901
|
+
// template element itself. Those clones still need the normal directive
|
|
3902
|
+
// and interpolation pass. Previously the initially-visible branch was
|
|
3903
|
+
// inserted and left inert, so bindings such as :text and {{ expression }}
|
|
3904
|
+
// leaked into the rendered UI until the condition toggled.
|
|
3905
|
+
const processTemplateNodes = (nodes) => {
|
|
3906
|
+
const childScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3907
|
+
// Components compiled inside a <template :if> carry setup scripts in the
|
|
3908
|
+
// template content. Browser-cloned scripts are inert, so register their
|
|
3909
|
+
// scopes and forwarded props before walking the inserted branch. This is
|
|
3910
|
+
// the same component hydration path used by <template :for>.
|
|
3911
|
+
hydrateComponentScopes(nodes, childScope);
|
|
3912
|
+
nodes.forEach(node => {
|
|
3913
|
+
if (!node.__stx_disposers && node.tagName !== 'SCRIPT')
|
|
3914
|
+
processElement(node, childScope);
|
|
3915
|
+
if (node.nodeType === 1) {
|
|
3916
|
+
node.removeAttribute('x-cloak');
|
|
3917
|
+
node.querySelectorAll('[x-cloak]').forEach(c => c.removeAttribute('x-cloak'));
|
|
3918
|
+
}
|
|
3919
|
+
});
|
|
3920
|
+
};
|
|
3921
|
+
|
|
3334
3922
|
// Evaluate the :if expression \u2014 use direct signal read for simple refs,
|
|
3335
3923
|
// falling back to evalExpr for complex expressions
|
|
3336
3924
|
const fullScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
@@ -3359,16 +3947,8 @@ catch (e2) {
|
|
|
3359
3947
|
currentNodes = Array.from(el.content.childNodes).map(n => n.cloneNode(true));
|
|
3360
3948
|
const insertionAnchor = placeholder.nextSibling;
|
|
3361
3949
|
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
|
-
});
|
|
3950
|
+
processTemplateNodes(currentNodes);
|
|
3951
|
+
childrenProcessed = true;
|
|
3372
3952
|
// Stamp insertion time for click propagation guard
|
|
3373
3953
|
currentNodes.forEach(function(n) { if (n.nodeType === 1) n.__stx_shown_at = performance.now(); });
|
|
3374
3954
|
isInserted = true;
|
|
@@ -3380,12 +3960,22 @@ else if (!value && isInserted) {
|
|
|
3380
3960
|
currentNodes.forEach(node => node.remove());
|
|
3381
3961
|
currentNodes = [];
|
|
3382
3962
|
isInserted = false;
|
|
3963
|
+
childrenProcessed = false;
|
|
3964
|
+
}
|
|
3965
|
+
else if (value && isInserted && !childrenProcessed) {
|
|
3966
|
+
// Initial template content is cloned before the effect runs. Process
|
|
3967
|
+
// it after the condition is confirmed so hidden branches stay inert.
|
|
3968
|
+
childrenProcessed = true;
|
|
3969
|
+
setTimeout(function() {
|
|
3970
|
+
if (isInserted) processTemplateNodes(currentNodes);
|
|
3971
|
+
}, 0);
|
|
3383
3972
|
}
|
|
3384
3973
|
}
|
|
3385
3974
|
else {
|
|
3386
3975
|
if (value && !isInserted) {
|
|
3387
3976
|
console.log('[stx] bindIf INSERTING element for :if=' + expr);
|
|
3388
|
-
|
|
3977
|
+
const insertionAnchor = placeholder.nextSibling;
|
|
3978
|
+
currentNodes.forEach(node => parent.insertBefore(node, insertionAnchor));
|
|
3389
3979
|
el.__stx_shown_at = performance.now();
|
|
3390
3980
|
isInserted = true;
|
|
3391
3981
|
}
|
|
@@ -3400,7 +3990,7 @@ else if (!value && isInserted) {
|
|
|
3400
3990
|
// once (#1737). Permanent disposal stays in bindFor (item removal)
|
|
3401
3991
|
// and cleanupContainer (SPA navigation). The double-bind guards make
|
|
3402
3992
|
// re-show idempotent, so toggling doesn't leak.
|
|
3403
|
-
|
|
3993
|
+
currentNodes.forEach(node => node.remove());
|
|
3404
3994
|
isInserted = false;
|
|
3405
3995
|
console.log('[stx] bindIf REMOVED, el.isConnected:', el.isConnected);
|
|
3406
3996
|
}
|
|
@@ -3415,7 +4005,15 @@ else if (!value && isInserted) {
|
|
|
3415
4005
|
childrenProcessed = true;
|
|
3416
4006
|
setTimeout(function() {
|
|
3417
4007
|
var childScope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
|
|
3418
|
-
|
|
4008
|
+
// A false single-element conditional is detached before the
|
|
4009
|
+
// DOMContentLoaded component-scope pass. When it becomes visible,
|
|
4010
|
+
// hydrate any component roots in the restored subtree first so
|
|
4011
|
+
// projected slot bindings and component-local directives receive
|
|
4012
|
+
// the same lifecycle as components inserted by template :if.
|
|
4013
|
+
var wasTrackedBeforeConditionalHydration = !!el.__stx_disposers;
|
|
4014
|
+
hydrateComponentScopes(currentNodes, childScope);
|
|
4015
|
+
if (wasTrackedBeforeConditionalHydration || !el.__stx_disposers)
|
|
4016
|
+
processElement(el, childScope);
|
|
3419
4017
|
// Remove x-cloak from the inserted subtree \u2014 the initial
|
|
3420
4018
|
// cloak removal (after processElement on the root) already
|
|
3421
4019
|
// ran before this deferred processing, so newly-inserted
|
|
@@ -3677,8 +4275,16 @@ else if (timer === null) {
|
|
|
3677
4275
|
}
|
|
3678
4276
|
|
|
3679
4277
|
function useClickOutside(target, handler) {
|
|
4278
|
+
function resolveTarget() {
|
|
4279
|
+
if (typeof target === 'string') return document.querySelector(target);
|
|
4280
|
+
if (target && typeof target === 'object') {
|
|
4281
|
+
if ('current' in target) return target.current;
|
|
4282
|
+
if ('value' in target) return target.value;
|
|
4283
|
+
}
|
|
4284
|
+
return target;
|
|
4285
|
+
}
|
|
3680
4286
|
function listener(event) {
|
|
3681
|
-
var el =
|
|
4287
|
+
var el = resolveTarget();
|
|
3682
4288
|
if (!el) return;
|
|
3683
4289
|
if (el === event.target || el.contains(event.target)) return;
|
|
3684
4290
|
handler(event);
|
|
@@ -3725,6 +4331,15 @@ else if (timer === null) {
|
|
|
3725
4331
|
};
|
|
3726
4332
|
}
|
|
3727
4333
|
|
|
4334
|
+
// Queue work after the current synchronous signal/effect flush. This mirrors
|
|
4335
|
+
// the Composition API nextTick contract and gives template refs a chance to
|
|
4336
|
+
// resolve after a structural directive inserts their elements.
|
|
4337
|
+
function nextTick(fn) {
|
|
4338
|
+
return Promise.resolve().then(function() {
|
|
4339
|
+
if (fn) fn();
|
|
4340
|
+
});
|
|
4341
|
+
}
|
|
4342
|
+
|
|
3728
4343
|
function useAsync(fn, options) {
|
|
3729
4344
|
options = options || {};
|
|
3730
4345
|
var asyncState = 'idle';
|
|
@@ -3863,6 +4478,61 @@ catch (e) {}
|
|
|
3863
4478
|
return matches;
|
|
3864
4479
|
}
|
|
3865
4480
|
|
|
4481
|
+
var scrollLockStates = new WeakMap();
|
|
4482
|
+
|
|
4483
|
+
function resolveScrollLockTarget(target) {
|
|
4484
|
+
var value = target;
|
|
4485
|
+
if (typeof value === 'function') value = value();
|
|
4486
|
+
if (value && typeof value === 'object' && 'current' in value) value = value.current;
|
|
4487
|
+
if (value && typeof value === 'object' && !value.nodeType && 'value' in value) value = value.value;
|
|
4488
|
+
return value || document.body || document.documentElement;
|
|
4489
|
+
}
|
|
4490
|
+
|
|
4491
|
+
function acquireScrollLock(element) {
|
|
4492
|
+
if (!element) return;
|
|
4493
|
+
var existing = scrollLockStates.get(element);
|
|
4494
|
+
if (existing) {
|
|
4495
|
+
existing.count += 1;
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
scrollLockStates.set(element, { count: 1, overflow: element.style.overflow });
|
|
4499
|
+
element.style.overflow = 'hidden';
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
function releaseScrollLock(element) {
|
|
4503
|
+
if (!element) return;
|
|
4504
|
+
var existing = scrollLockStates.get(element);
|
|
4505
|
+
if (!existing) return;
|
|
4506
|
+
existing.count -= 1;
|
|
4507
|
+
if (existing.count > 0) return;
|
|
4508
|
+
element.style.overflow = existing.overflow;
|
|
4509
|
+
scrollLockStates.delete(element);
|
|
4510
|
+
}
|
|
4511
|
+
|
|
4512
|
+
function useScrollLock(target) {
|
|
4513
|
+
var locked = state(false);
|
|
4514
|
+
var lockedElement = null;
|
|
4515
|
+
var unsubscribe = locked.subscribe(function(value) {
|
|
4516
|
+
if (value) {
|
|
4517
|
+
if (lockedElement) return;
|
|
4518
|
+
lockedElement = resolveScrollLockTarget(target);
|
|
4519
|
+
acquireScrollLock(lockedElement);
|
|
4520
|
+
}
|
|
4521
|
+
else if (lockedElement) {
|
|
4522
|
+
releaseScrollLock(lockedElement);
|
|
4523
|
+
lockedElement = null;
|
|
4524
|
+
}
|
|
4525
|
+
});
|
|
4526
|
+
onDestroy(function() {
|
|
4527
|
+
unsubscribe();
|
|
4528
|
+
if (lockedElement) {
|
|
4529
|
+
releaseScrollLock(lockedElement);
|
|
4530
|
+
lockedElement = null;
|
|
4531
|
+
}
|
|
4532
|
+
});
|
|
4533
|
+
return locked;
|
|
4534
|
+
}
|
|
4535
|
+
|
|
3866
4536
|
function usePreferredDark() { return useMediaQuery('(prefers-color-scheme: dark)'); }
|
|
3867
4537
|
function usePreferredLight() { return useMediaQuery('(prefers-color-scheme: light)'); }
|
|
3868
4538
|
function usePreferredReducedMotion() { return useMediaQuery('(prefers-reduced-motion: reduce)'); }
|
|
@@ -3950,6 +4620,30 @@ catch (e) {}
|
|
|
3950
4620
|
return s;
|
|
3951
4621
|
}
|
|
3952
4622
|
|
|
4623
|
+
var __stxGeneratedIdCounter = 0;
|
|
4624
|
+
|
|
4625
|
+
// Stable, scope-aware DOM ids for component authoring. A component setup
|
|
4626
|
+
// runs with window.__STX_CURRENT_ELEMENT__ pointing at its compiled root, so
|
|
4627
|
+
// the data-stx scope id is already the canonical per-instance identity. The
|
|
4628
|
+
// local counter supports multiple useId() calls in one component while the
|
|
4629
|
+
// global fallback covers page scripts and manually-mounted roots.
|
|
4630
|
+
function useId(prefix) {
|
|
4631
|
+
var normalizedPrefix = String(prefix || 'stx')
|
|
4632
|
+
.trim()
|
|
4633
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
4634
|
+
.replace(/^-+|-+$/g, '') || 'stx';
|
|
4635
|
+
var root = window.__STX_CURRENT_ELEMENT__;
|
|
4636
|
+
if (root && root.getAttribute) {
|
|
4637
|
+
var scopeId = root.getAttribute('data-stx-scope') || root.getAttribute('data-stx') || '';
|
|
4638
|
+
if (scopeId) {
|
|
4639
|
+
root.__stx_id_counter = (root.__stx_id_counter || 0) + 1;
|
|
4640
|
+
return normalizedPrefix + '-' + String(scopeId).replace(/[^a-zA-Z0-9_-]+/g, '-') + '-' + root.__stx_id_counter;
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
__stxGeneratedIdCounter++;
|
|
4644
|
+
return normalizedPrefix + '-' + __stxGeneratedIdCounter;
|
|
4645
|
+
}
|
|
4646
|
+
|
|
3953
4647
|
// Reactive prop binding. Bridges the gap between a parent's clientReactive
|
|
3954
4648
|
// attribute (e.g. :open="modalOpen()" on a component) and the component's
|
|
3955
4649
|
// internal state. Returns a signal whose value tracks the named attribute
|
|
@@ -4036,7 +4730,19 @@ catch (e) {}
|
|
|
4036
4730
|
var raw = attribute.raw;
|
|
4037
4731
|
if (raw === lastRaw) return;
|
|
4038
4732
|
lastRaw = raw;
|
|
4039
|
-
var
|
|
4733
|
+
var directValues = root.__stx_direct_prop_values;
|
|
4734
|
+
var directValue = directValues && directValues[name];
|
|
4735
|
+
var next;
|
|
4736
|
+
if (directValue && directValue.raw === raw) {
|
|
4737
|
+
next = directValue.value;
|
|
4738
|
+
delete directValues[name];
|
|
4739
|
+
for (var directKey in directValues) {
|
|
4740
|
+
if (directValues[directKey] === directValue) delete directValues[directKey];
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
else {
|
|
4744
|
+
next = hasAttr ? parse(raw) : staticValue;
|
|
4745
|
+
}
|
|
4040
4746
|
if (next !== s()) s.set(next);
|
|
4041
4747
|
});
|
|
4042
4748
|
observer.observe(root, { attributes: true, attributeFilter: attributeNames });
|
|
@@ -4579,16 +5285,19 @@ catch (e) {}
|
|
|
4579
5285
|
useCounter,
|
|
4580
5286
|
useClickOutside,
|
|
4581
5287
|
useFocus,
|
|
5288
|
+
nextTick,
|
|
4582
5289
|
useAsync,
|
|
4583
5290
|
useLocalStorage,
|
|
4584
5291
|
useSessionStorage,
|
|
4585
5292
|
useCookie,
|
|
5293
|
+
useId,
|
|
4586
5294
|
useReactiveProp,
|
|
4587
5295
|
useEventListener,
|
|
4588
5296
|
useWebSocket,
|
|
4589
5297
|
useColorMode,
|
|
4590
5298
|
useDark,
|
|
4591
5299
|
useMediaQuery,
|
|
5300
|
+
useScrollLock,
|
|
4592
5301
|
usePreferredDark,
|
|
4593
5302
|
usePreferredLight,
|
|
4594
5303
|
usePreferredReducedMotion,
|
|
@@ -4633,10 +5342,23 @@ catch (e) {}
|
|
|
4633
5342
|
}
|
|
4634
5343
|
return result;
|
|
4635
5344
|
},
|
|
4636
|
-
defineEmits: function() {
|
|
5345
|
+
defineEmits: function(events) {
|
|
4637
5346
|
var el = window.__STX_CURRENT_ELEMENT__ || null;
|
|
5347
|
+
var declaredEvents = Array.isArray(events)
|
|
5348
|
+
? events
|
|
5349
|
+
: (events && typeof events === 'object' ? Object.keys(events) : []);
|
|
5350
|
+
if (el && declaredEvents.length) {
|
|
5351
|
+
el.__stx_component_emits = el.__stx_component_emits || Object.create(null);
|
|
5352
|
+
declaredEvents.forEach(function(event) {
|
|
5353
|
+
el.__stx_component_emits[event] = true;
|
|
5354
|
+
});
|
|
5355
|
+
}
|
|
4638
5356
|
return function(event, payload) {
|
|
4639
5357
|
var target = el || document.body;
|
|
5358
|
+
if (el) {
|
|
5359
|
+
el.__stx_component_emits = el.__stx_component_emits || Object.create(null);
|
|
5360
|
+
el.__stx_component_emits[event] = true;
|
|
5361
|
+
}
|
|
4640
5362
|
target.dispatchEvent(new CustomEvent(event, { detail: payload, bubbles: true, cancelable: true }));
|
|
4641
5363
|
};
|
|
4642
5364
|
},
|
|
@@ -5093,6 +5815,7 @@ else {
|
|
|
5093
5815
|
window.useCounter = useCounter;
|
|
5094
5816
|
window.useClickOutside = useClickOutside;
|
|
5095
5817
|
window.useFocus = useFocus;
|
|
5818
|
+
window.nextTick = nextTick;
|
|
5096
5819
|
window.useAsync = useAsync;
|
|
5097
5820
|
window.useLocalStorage = useLocalStorage;
|
|
5098
5821
|
window.useEventListener = useEventListener;
|