@stacksjs/stx 0.2.173 → 0.2.175

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.
@@ -32,6 +32,7 @@ export function injectBrowserRuntime(template) {
32
32
  "clamp",
33
33
  "rand",
34
34
  "readableSize",
35
+ "useActiveElement",
35
36
  "useDocumentVisibility",
36
37
  "useIntervalFn"
37
38
  ].some((sym) => {
package/dist/signals.js CHANGED
@@ -1175,9 +1175,15 @@ else if (immediate) {
1175
1175
  // ==========================================================================
1176
1176
 
1177
1177
  function useRef(name) {
1178
+ // A ref belongs to the component scope that created it. Capturing that
1179
+ // owner now is essential: componentScope is a mutable compatibility
1180
+ // fallback and changes while the rest of the page hydrates. Reading it
1181
+ // from the getter made an early component resolve refs from whichever
1182
+ // component happened to hydrate last.
1183
+ var ownerScope = currentLifecycleScope() || componentScope;
1178
1184
  return {
1179
1185
  get current() {
1180
- return componentScope.$refs ? componentScope.$refs[name] : null;
1186
+ return (ownerScope.$refs && ownerScope.$refs[name]) || null;
1181
1187
  },
1182
1188
  get value() {
1183
1189
  return this.current;
@@ -4361,33 +4367,30 @@ catch (e2) {
4361
4367
  }
4362
4368
 
4363
4369
  // Helper to evaluate with captured scope (auto-unwraps signals).
4364
- // Mirrors bindStyle/bindShow/evalAttrExpr: first try the auto-unwrap
4365
- // proxy (so a bare-reference comparison like count > 5 reads the signal
4366
- // as a primitive), then retry with the raw scope so call-syntax
4367
- // expressions like count() === 0 or !recording() still work. The proxy
4368
- // unwraps signals to their values, which turns count() into 0() ->
4369
- // TypeError; pre-fix (stacksjs/stx#1733) the single catch swallowed
4370
- // that TypeError and returned '' (falsy), silently hiding the element
4371
- // for EVERY comparison/boolean expression. bindIf was the only
4372
- // directive missing this retry.
4370
+ // A with-scope resolves only identifiers the expression actually references,
4371
+ // so the conditional subscribes narrowly instead of reading every signal
4372
+ // in a component scope. That broad subscription made an unrelated ref
4373
+ // update re-enter structural removal while the real condition was already
4374
+ // removing the same subtree. First try the auto-unwrap proxy (so a bare
4375
+ // comparison like count > 5 reads the signal value), then retry with the
4376
+ // raw scope so call syntax like count() === 0 still works (#1733).
4373
4377
  const evalExpr = (expression) => {
4374
4378
  // Skip placeholder expressions like __TITLE__ (build-time placeholders)
4375
4379
  if (/^__[A-Z_]+__$/.test(expression.trim())) {
4376
4380
  return expression;
4377
4381
  }
4378
4382
  try {
4379
- // Use captured componentScope (with @for vars) merged with element scope
4380
4383
  const scope = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
4381
4384
  const unwrapScope = createExpressionAutoUnwrapProxy(scope, expression);
4382
- const fn = new Function(...Object.keys(scope), 'return ' + expression);
4383
- return fn(...Object.values(unwrapScope));
4385
+ const fn = new Function('__scope__', 'with(__scope__) { return ' + expression + ' }');
4386
+ return fn(unwrapScope);
4384
4387
  }
4385
4388
  catch (e1) {
4386
4389
  try {
4387
4390
  // Retry without the unwrap proxy so call-syntax (signal()) works.
4388
4391
  const scope2 = { ...globalHelpers, ...capturedComponentScope, ...(capturedElementScope || {}) };
4389
- const fn2 = new Function(...Object.keys(scope2), 'return ' + expression);
4390
- return fn2(...Object.values(scope2));
4392
+ const fn2 = new Function('__scope__', 'with(__scope__) { return ' + expression + ' }');
4393
+ return fn2(scope2);
4391
4394
  }
4392
4395
  catch (e2) {
4393
4396
  if (!(e2 instanceof ReferenceError) && !(e2 instanceof TypeError)) console.warn('[STX] Expression error:', expression, e2);
@@ -4444,11 +4447,6 @@ catch (e2) {
4444
4447
  value = directSignal();
4445
4448
  console.log('[stx] bindIf effect (direct):', expr, '\u2192', value, 'isInserted:', isInserted);
4446
4449
  } else {
4447
- // Complex expression: read all signals first for tracking, then evaluate
4448
- for (var sk in fullScope) {
4449
- var sv = fullScope[sk];
4450
- if (sv && typeof sv === 'function' && (sv._isSignal || sv._isDerived)) sv();
4451
- }
4452
4450
  value = evalExpr(expr);
4453
4451
  }
4454
4452
 
@@ -4886,12 +4884,19 @@ else if (timer === null) {
4886
4884
  };
4887
4885
  }
4888
4886
 
4889
- // Queue work after the current synchronous signal/effect flush. This mirrors
4890
- // the Composition API nextTick contract and gives template refs a chance to
4891
- // resolve after a structural directive inserts their elements.
4887
+ // Queue work after the current synchronous signal/effect flush and after
4888
+ // structural directives finish their deferred subtree hydration. The
4889
+ // microtask schedules this timer only after every subscriber has run, so a
4890
+ // :if/:for timer created later in the same signal flush runs first and its
4891
+ // template refs are available to the callback.
4892
4892
  function nextTick(fn) {
4893
4893
  return Promise.resolve().then(function() {
4894
- if (fn) fn();
4894
+ return new Promise(function(resolve) {
4895
+ setTimeout(function() {
4896
+ if (fn) fn();
4897
+ resolve();
4898
+ }, 0);
4899
+ });
4895
4900
  });
4896
4901
  }
4897
4902