octane 0.2.13 → 0.2.14

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/runtime.d.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import type { BindingHandle } from './dom-bindings.js';
16
16
  import { type BindingHandoff } from './dom-binding-handoff.js';
17
+ import { type ControlHandoff, type SignalControlBinding } from './signals/control-handoff.js';
17
18
  import { EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY } from './constants.js';
18
19
  import type { HydrateProps } from './hydration/types.js';
19
20
  import type { IndependentHydrateActivator } from './hydration/independent-island.js';
@@ -23,7 +24,7 @@ import { SignalCandidateFrame } from './signals/transition-candidate.js';
23
24
  import { setNativeAdoptionResolver } from './signals/read-protocol.js';
24
25
  import { type NativeAdoptionState } from './signals/native-read-seeds.js';
25
26
  export { documentSignalOwner, installStreamedSignalOwnerActivator, } from './signals/document-owner.js';
26
- import { type SignalOwner, type SignalRendererOwnerIdentity } from './signals/types.js';
27
+ import { type SignalHandle, type SignalOwner, type SignalRendererOwnerIdentity } from './signals/types.js';
27
28
  export { EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY };
28
29
  export { validateNativeReadWitness };
29
30
  export type ComponentBody<P = any, E = any> = (props: P, scope: Scope, extra: E) => void;
@@ -526,6 +527,7 @@ interface RootRenderOwner {
526
527
  /** Shared document/account data owner; distinct from this root's presentation owner. */
527
528
  signalOwner?: SignalOwner;
528
529
  bindingLeases?: Set<BindingHandoff>;
530
+ controlLeases?: Map<Element, ControlHandoff | undefined>;
529
531
  preservePresentation?: boolean;
530
532
  bindingContainer?: Node;
531
533
  adopt?: (block: Block) => void;
@@ -1428,10 +1430,18 @@ interface PresentationHydrationFrame {
1428
1430
  readToken: number;
1429
1431
  witnessToken: number;
1430
1432
  witnesses: Map<object, NativeReadWitness | null>;
1433
+ /** Successor native writers only; a host receipt never tears down children. */
1434
+ hostSuccessors?: Map<object, () => void>;
1435
+ controls?: Map<Element, {
1436
+ valid: (retired?: boolean) => boolean;
1437
+ binding: DirectSignalBinding;
1438
+ lease: ControlHandoff | undefined;
1439
+ stop?: () => void;
1440
+ }>;
1431
1441
  completed: boolean;
1432
1442
  lease: BindingHandoff;
1433
1443
  revision?: number;
1434
- /** Installed only for structural receipts so ordinary commits do not retain the validator. */
1444
+ /** Installed only for structural/control/host receipts, not ordinary fixed presentations. */
1435
1445
  current?: typeof currentPresentation;
1436
1446
  hydration: HydrationCapability | null;
1437
1447
  }
@@ -1439,7 +1449,7 @@ interface PresentationHydrationFrame {
1439
1449
  export declare function presentationFailure(error: unknown): never;
1440
1450
  declare function currentPresentation(frame: PresentationHydrationFrame): boolean;
1441
1451
  /** @internal Only compiler-proven native views enter this publication boundary. */
1442
- export declare function beginPresentationHydration(scope: Scope, id: string, supported?: boolean, structural?: boolean, conditionalRest?: boolean): PresentationHydrationFrame | null;
1452
+ export declare function beginPresentationHydration(scope: Scope, id: string, supported?: boolean, structural?: boolean, conditionalRest?: boolean, host?: boolean): PresentationHydrationFrame | null;
1443
1453
  /** @internal Restore the read frame even when preparation suspends or throws. */
1444
1454
  export declare function endPresentationHydration(frame: PresentationHydrationFrame | null, completed?: boolean): void;
1445
1455
  /** @internal Only authored host spreads retain generic host preparation. */
@@ -1465,7 +1475,26 @@ export declare function child<T extends Node>(node: T): Node | null;
1465
1475
  */
1466
1476
  export declare function sibling(node: Node, n?: number): Node | null;
1467
1477
  export declare function setText(node: Text, value: any): void;
1478
+ declare const DIRECT_SIGNAL_BINDING: unique symbol;
1479
+ type DirectSignalBindingKind = 'text' | 'textOnlyChild' | 'attribute' | 'value' | 'checked';
1468
1480
  type DirectSignalAttributeKind = 'attr' | 'class' | 'booleanAttr' | 'ariaAttr' | 'stringData';
1481
+ interface DirectSignalBinding {
1482
+ readonly [DIRECT_SIGNAL_BINDING]: true;
1483
+ readonly scope: Scope;
1484
+ readonly kind: DirectSignalBindingKind;
1485
+ readonly target: Node;
1486
+ readonly site: string;
1487
+ readonly name?: string;
1488
+ readonly attributeKind?: DirectSignalAttributeKind;
1489
+ readonly handle: SignalHandle<unknown> | null;
1490
+ text?: Text;
1491
+ value: unknown;
1492
+ unsubscribe?: () => void;
1493
+ input?: EventListener;
1494
+ controlWriterCleanup?: () => void;
1495
+ pendingControl?: boolean;
1496
+ disposed: boolean;
1497
+ }
1469
1498
  /** @internal Compiler target for a direct signal/scalar text binding. */
1470
1499
  export declare function bindSignalText(scope: Scope, previous: unknown, position: Node, value: unknown, site: string, onlyChild?: boolean, seededText?: 1 | undefined, bindingMarker?: string, previousValue?: unknown): unknown;
1471
1500
  /** @internal Compiler target for a direct signal/scalar attribute binding. */
@@ -2284,6 +2313,8 @@ export interface Root {
2284
2313
  export interface RootOptions {
2285
2314
  /** Adopt fixed native early bindings only when their matching hydration capture commits. */
2286
2315
  bindingLeases?: readonly BindingHandle[];
2316
+ /** Offer an existing textarea value owner for accepted presentation hydration, never early disposal. */
2317
+ controlLeases?: readonly SignalControlBinding[];
2287
2318
  /**
2288
2319
  * Shared document/account owner for module signals. Roots borrow this owner;
2289
2320
  * unmounting a presentation root never retires shared data state.
package/dist/runtime.js CHANGED
@@ -9,6 +9,10 @@ import {
9
9
  releaseBindingHandoff
10
10
  } from "./dom-binding-handoff.js";
11
11
  import { bumpContextEpoch, contextEpochNow } from "./context-epoch.js";
12
+ import {
13
+ CONTROL_HANDOFF,
14
+ hasSignalControlBinding
15
+ } from "./signals/control-handoff.js";
12
16
  import {
13
17
  SUSPENSE_SCRIPT_ATTR,
14
18
  SUSPENSE_RESOLVED_COMMENT,
@@ -2257,7 +2261,9 @@ function rollbackRootRender(transaction) {
2257
2261
  }
2258
2262
  function retireDetachedBindingLeases(owner) {
2259
2263
  for (const lease of owner.bindingLeases) {
2260
- if (owner.bindingContainer.contains(lease.anchor)) continue;
2264
+ const preparation = lease.host !== void 0 && !lease.active() ? PRESENTATION_PREPARATIONS.get(lease) : void 0;
2265
+ if (preparation !== void 0 ? !preparation.scope.block.disposed : owner.bindingContainer.contains(lease.anchor))
2266
+ continue;
2261
2267
  owner.bindingLeases.delete(lease);
2262
2268
  PRESENTATION_PREPARATIONS.delete(lease);
2263
2269
  releaseBindingHandoff(lease);
@@ -6292,7 +6298,8 @@ function nativeEffectPublicationCurrent(entry, slot) {
6292
6298
  if (ROOT_RENDER_TRANSACTION !== null) journalObjectOnce(slot);
6293
6299
  slot.deps = void 0;
6294
6300
  slot.active = slot.connectedFn !== null;
6295
- invalidateRender(entry.scope.block, entry.scope.block);
6301
+ if (!entry.scope.block.idState.renderOwner?.disposed && !blockSubtreeDisposed(entry.scope.block))
6302
+ scheduleNativeRead(entry.scope.block);
6296
6303
  return false;
6297
6304
  }
6298
6305
  function drainMutationEffects() {
@@ -8662,6 +8669,7 @@ function releasePreservedHydrateActivation(state) {
8662
8669
  function pendingHydrateOwner(target) {
8663
8670
  let owner = null;
8664
8671
  for (let block = target; block !== null; block = block.parentBlock) {
8672
+ if (block.block !== block) continue;
8665
8673
  const state = block.slots[0];
8666
8674
  if (state?.__kind === "hydrateBlockSlot" && !state.hydrated && preservedHydrateActivations?.has(state))
8667
8675
  owner = state;
@@ -11856,7 +11864,7 @@ function isAdoptionControl(error) {
11856
11864
  }
11857
11865
  function presentationFailure(error) {
11858
11866
  const frame = PRESENTATION_HYDRATION;
11859
- if (frame?.revision !== void 0 && !isSuspenseException(error) && !isAdoptionControl(error)) {
11867
+ if (frame !== null && !isSuspenseException(error) && !isAdoptionControl(error)) {
11860
11868
  const failure = new PresentationAdoptionMiss(frame.lease, false);
11861
11869
  failure.failure = error;
11862
11870
  throw failure;
@@ -11867,12 +11875,25 @@ function presentationMiss(retry = true) {
11867
11875
  throw new PresentationAdoptionMiss(PRESENTATION_HYDRATION?.lease, retry);
11868
11876
  }
11869
11877
  function currentPresentation(frame) {
11870
- if (frame.revision === void 0) return true;
11878
+ if (frame.controls !== void 0 && [...frame.controls.values()].some(({ valid }) => !valid()))
11879
+ return false;
11880
+ if (frame.revision === void 0 && frame.controls === void 0) return true;
11871
11881
  const lease = frame.lease;
11872
- return lease.active() && lease.valid?.() !== false && lease.revision?.() === frame.revision && lease.root.parentNode !== null && lease.end?.parentNode === lease.root.parentNode && [...frame.witnesses.values()].every(
11882
+ return lease.active() && lease.valid?.() !== false && lease.revision?.() === frame.revision && lease.root.parentNode !== null && (lease.host === void 0 ? lease.end?.parentNode === lease.root.parentNode : lease.owner === frame.scope.block.idState.renderOwner && frame.scope.block.idState.renderOwner.bindingContainer.contains(lease.root)) && [...frame.witnesses.values()].every(
11873
11883
  (witness) => witness === null || validateNativeReadWitness(witness)
11874
11884
  );
11875
11885
  }
11886
+ function trackPresentationFrame(frame) {
11887
+ if (frame.current !== void 0) return;
11888
+ frame.current = currentPresentation;
11889
+ (WIP_CAPTURE.presentations ??= []).push(frame);
11890
+ if (frame.hydration === null) return;
11891
+ frame.hydration.presentation = true;
11892
+ for (let block = frame.scope.block; block !== null; block = block.parentBlock) {
11893
+ preserveRootCreatedDom(block);
11894
+ if (block === frame.hydration.rootBlock) break;
11895
+ }
11896
+ }
11876
11897
  function currentPresentations(capture) {
11877
11898
  return capture.presentations?.every((frame) => frame.current(frame)) !== false;
11878
11899
  }
@@ -11908,7 +11929,7 @@ function hasClosedPresentationView(lease, scope, id) {
11908
11929
  return false;
11909
11930
  }
11910
11931
  }
11911
- function beginPresentationHydration(scope, id, supported = false, structural = false, conditionalRest = false) {
11932
+ function beginPresentationHydration(scope, id, supported = false, structural = false, conditionalRest = false, host = false) {
11912
11933
  const owner = scope.block.idState.renderOwner;
11913
11934
  if (PRESENTATION_HYDRATION !== null && PRESENTATION_HYDRATION.scope.block.idState.renderOwner === owner) {
11914
11935
  if (conditionalRest) {
@@ -11920,15 +11941,17 @@ function beginPresentationHydration(scope, id, supported = false, structural = f
11920
11941
  if (owner?.bindingLeases === void 0 || WIP_CAPTURE === null) return null;
11921
11942
  const hydration = activeHydration();
11922
11943
  const lease = [...owner.bindingLeases].find(
11923
- (candidate) => candidate.id === id && (candidate.root === scope.block.startMarker || candidate.root === hydration?.node || candidate.root.nodeType === 8 && candidate.root.nextSibling === hydration?.node)
11944
+ (candidate) => candidate.id === id && (candidate.root === scope.block.startMarker || candidate.root === hydration?.node || candidate.root.nodeType === 8 && candidate.root.nextSibling === hydration?.node || candidate.host !== void 0 && PRESENTATION_PREPARATIONS.get(candidate)?.scope === scope)
11924
11945
  );
11925
11946
  if (lease === void 0) return null;
11947
+ if (!lease.active()) throw new PresentationAdoptionMiss(lease, false);
11948
+ if (host !== (lease.host !== void 0)) throw new PresentationAdoptionMiss(lease, false);
11926
11949
  if (conditionalRest ? !supported || !structural || !hasClosedPresentationView(lease, scope, id) : !supported)
11927
11950
  throw new PresentationAdoptionMiss(lease, false);
11928
11951
  const revision = lease.revision?.();
11929
- if (revision !== void 0 && revision >= 0 && lease.valid?.() === false)
11952
+ if ((revision === void 0 || revision >= 0) && lease.valid?.() === false)
11930
11953
  throw new PresentationAdoptionMiss(lease, false);
11931
- if (revision !== void 0 && (!structural || revision < 0))
11954
+ if (revision !== void 0 && (!structural && !host || revision < 0))
11932
11955
  throw new PresentationAdoptionMiss(lease, revision < 0, revision);
11933
11956
  const pending = PRESENTATION_PREPARATIONS.get(lease);
11934
11957
  const frame = {
@@ -11941,42 +11964,95 @@ function beginPresentationHydration(scope, id, supported = false, structural = f
11941
11964
  readToken: beginNativeReadScope(scope),
11942
11965
  witnessToken: beginNativeReadWitness(),
11943
11966
  witnesses: pending?.scope === scope ? new Map(pending.witnesses) : /* @__PURE__ */ new Map(),
11967
+ ...lease.host === void 0 ? {} : { hostSuccessors: new Map(pending?.scope === scope ? pending.hostSuccessors : void 0) },
11968
+ ...pending?.scope === scope && pending.controls !== void 0 ? { controls: new Map(pending.controls) } : {},
11944
11969
  completed: false,
11945
11970
  lease,
11946
- ...revision === void 0 ? {} : { revision, current: currentPresentation },
11971
+ ...revision === void 0 ? {} : { revision },
11947
11972
  hydration
11948
11973
  };
11949
11974
  PRESENTATION_HYDRATION = frame;
11950
- if (revision !== void 0 && hydration !== null) {
11951
- hydration.presentation = true;
11952
- for (let block = scope.block; block !== null; block = block.parentBlock) {
11953
- preserveRootCreatedDom(block);
11954
- if (block === hydration.rootBlock) break;
11955
- }
11956
- }
11975
+ if (revision !== void 0 || frame.controls !== void 0) trackPresentationFrame(frame);
11957
11976
  PRESENTATION_PREPARATIONS.set(lease, frame);
11958
- if (revision !== void 0) (WIP_CAPTURE.presentations ??= []).push(frame);
11977
+ const hostRefs = host ? WIP_CAPTURE.refs : void 0;
11959
11978
  (WIP_CAPTURE.renderCleanups ??= []).push((discarded) => {
11960
11979
  if (discarded || !frame.completed || owner.disposed || scope.block.disposed || PRESENTATION_PREPARATIONS.get(lease) !== frame)
11961
11980
  return;
11962
11981
  if (!currentPresentation(frame)) {
11963
- frame.hydration?.retryPresentation?.();
11982
+ if (frame.hydration?.retryPresentation !== void 0) frame.hydration.retryPresentation();
11983
+ else throw new PresentationAdoptionMiss(lease, false);
11964
11984
  return;
11965
11985
  }
11966
11986
  PRESENTATION_PREPARATIONS.delete(lease);
11967
11987
  owner.bindingLeases.delete(lease);
11968
11988
  releaseBindingHandoff(lease);
11989
+ let publicationStarted = false;
11969
11990
  let published = false;
11991
+ let hostFailed = false;
11992
+ let hostFailure;
11970
11993
  try {
11971
11994
  lease.retire(() => {
11972
- for (const writes of frame.writes.values()) for (const write of writes.values()) write();
11995
+ publicationStarted = true;
11996
+ try {
11997
+ for (const writes of frame.writes.values()) for (const write of writes.values()) write();
11998
+ } catch (error) {
11999
+ for (const stop of frame.hostSuccessors?.values() ?? []) {
12000
+ try {
12001
+ stop();
12002
+ } catch {
12003
+ }
12004
+ }
12005
+ frame.hostSuccessors?.clear();
12006
+ for (const [element, control] of frame.controls ?? []) {
12007
+ const binding = control.binding;
12008
+ if (binding.disposed && control.valid(true)) continue;
12009
+ (owner.controlLeases ??= /* @__PURE__ */ new Map()).set(element, control.lease);
12010
+ if (binding.disposed) continue;
12011
+ if (binding.input !== void 0) renderControlledState(element).v = UNCONTROLLED;
12012
+ control.stop = binding.unsubscribe;
12013
+ binding.unsubscribe = void 0;
12014
+ disposeDirectSignalBinding(binding);
12015
+ }
12016
+ for (const control of frame.controls?.values() ?? []) {
12017
+ try {
12018
+ control.stop?.();
12019
+ } catch {
12020
+ }
12021
+ }
12022
+ throw error;
12023
+ }
11973
12024
  published = true;
11974
12025
  });
11975
12026
  } catch (error) {
11976
- if (!published) throw error;
11977
- reportRendererOwnerError(scope, error);
12027
+ if (!publicationStarted) throw error;
12028
+ if (frame.hostSuccessors !== void 0) {
12029
+ hostFailed = true;
12030
+ hostFailure = error;
12031
+ } else reportRendererOwnerError(scope, error);
11978
12032
  } finally {
12033
+ if (frame.hostSuccessors !== void 0 && (!published || lease.valid?.() === false)) {
12034
+ published = false;
12035
+ owner.bindingLeases.add(lease);
12036
+ PRESENTATION_PREPARATIONS.set(lease, frame);
12037
+ for (const entry of hostRefs) if (entry.el === lease.root) entry.ref = null;
12038
+ for (const stop of frame.hostSuccessors.values()) {
12039
+ try {
12040
+ stop();
12041
+ } catch (error) {
12042
+ if (!hostFailed) {
12043
+ hostFailed = true;
12044
+ hostFailure = error;
12045
+ }
12046
+ }
12047
+ }
12048
+ if (!hostFailed) {
12049
+ hostFailed = true;
12050
+ hostFailure = new Error(formatClientError(77));
12051
+ }
12052
+ }
12053
+ frame.hostSuccessors?.clear();
11979
12054
  frame.writes.clear();
12055
+ frame.controls?.clear();
11980
12056
  if (published && !owner.disposed && owner.current !== null && [...frame.witnesses.values()].some(
11981
12057
  (witness) => witness !== null && !validateNativeReadWitness(witness)
11982
12058
  )) {
@@ -11986,6 +12062,7 @@ function beginPresentationHydration(scope, id, supported = false, structural = f
11986
12062
  }
11987
12063
  frame.witnesses.clear();
11988
12064
  }
12065
+ if (hostFailed) reportRendererOwnerError(scope, hostFailure);
11989
12066
  });
11990
12067
  return frame;
11991
12068
  }
@@ -12004,13 +12081,33 @@ function preparedPresentationAttribute(el, name, value, kind) {
12004
12081
  const result = coerceAttrValue(el, name, value);
12005
12082
  return result === "" && BOOLEAN_ATTR_PROPS.has(name.toLowerCase()) ? true : result;
12006
12083
  }
12007
- function preparePresentationSignalAttribute(args, frame) {
12008
- const [scope, previous, element, name, value, site, attributeKind = "attr"] = args;
12084
+ function preparePresentationSignalBinding(args, frame, text = false) {
12085
+ const [scope, previous, element] = args;
12086
+ const name = text ? void 0 : args[3];
12087
+ const value = args[text ? 3 : 4];
12088
+ const site = args[text ? 4 : 5];
12089
+ const attributeKind = text ? void 0 : args[6] ?? "attr";
12090
+ const kind = text ? args[5] ? "textOnlyChild" : "text" : "attribute";
12009
12091
  return runWithBlockSignalOwner(scope, () => {
12010
12092
  const handle = isSignalHandle(value) ? value : null;
12011
- const current = handle === null ? value : handle.get();
12012
- const prepared = preparedPresentationAttribute(element, name, current, attributeKind);
12093
+ const witness = handle !== null && frame.lease.host !== void 0 ? beginNativeReadWitness(true) : -1;
12094
+ let current;
12095
+ let readCompleted = false;
12096
+ try {
12097
+ current = handle === null ? value : handle.get();
12098
+ readCompleted = true;
12099
+ } finally {
12100
+ if (witness >= 0)
12101
+ frame.witnesses.set(handle, finishNativeReadWitness(witness, readCompleted));
12102
+ }
12103
+ if (text && (current !== null && (typeof current === "object" || typeof current === "function") || typeof args[7] !== "string"))
12104
+ presentationMiss(false);
12105
+ const prepared = text ? bindingText(element, current, args[7]) : preparedPresentationAttribute(element, name, current, attributeKind);
12013
12106
  if (handle === null) {
12107
+ if (text) {
12108
+ frame.writes.get(element)?.delete("signalText");
12109
+ return prepared;
12110
+ }
12014
12111
  preparePresentationOperation(
12015
12112
  frame,
12016
12113
  element,
@@ -12020,12 +12117,12 @@ function preparePresentationSignalAttribute(args, frame) {
12020
12117
  return value;
12021
12118
  }
12022
12119
  const prior = previous;
12023
- const reusable = prior?.[DIRECT_SIGNAL_BINDING] === true && !prior.disposed && prior.unsubscribe === void 0 && prior.handle === handle && prior.target === element && prior.site === site && prior.name === name && prior.attributeKind === attributeKind;
12120
+ const reusable = prior?.[DIRECT_SIGNAL_BINDING] === true && !prior.disposed && prior.unsubscribe === void 0 && prior.handle === handle && prior.target === element && prior.kind === kind && prior.site === site && prior.name === name && prior.attributeKind === attributeKind;
12024
12121
  const binding = reusable ? prior : {
12025
12122
  [DIRECT_SIGNAL_BINDING]: true,
12026
12123
  scope,
12027
12124
  target: element,
12028
- kind: "attribute",
12125
+ kind,
12029
12126
  site,
12030
12127
  name,
12031
12128
  attributeKind,
@@ -12033,16 +12130,114 @@ function preparePresentationSignalAttribute(args, frame) {
12033
12130
  value: current,
12034
12131
  disposed: false
12035
12132
  };
12133
+ if (text) binding.text = prepared;
12036
12134
  if (!reusable) registerHookCleanup(scope, () => disposeDirectSignalBinding(binding));
12037
- preparePresentationOperation(frame, element, name, () => {
12135
+ frame.hostSuccessors?.set(binding, () => disposeDirectSignalBinding(binding));
12136
+ preparePresentationOperation(frame, element, name ?? "signalText", () => {
12038
12137
  if (binding.disposed) return;
12039
- writeDirectSignalScalar(element, prepared, "attribute", previous, name, attributeKind);
12138
+ if (!text)
12139
+ writeDirectSignalScalar(element, prepared, "attribute", previous, name, attributeKind);
12040
12140
  binding.value = current;
12041
12141
  runWithBlockSignalOwner(scope, () => activateDirectSignalBinding(binding, void 0));
12042
12142
  });
12043
12143
  return binding;
12044
12144
  });
12045
12145
  }
12146
+ function preparePresentationSignalValue(args, frame) {
12147
+ const [scope, previous, element, handle, site] = args;
12148
+ return runWithBlockSignalOwner(scope, () => {
12149
+ if (element.localName !== "textarea" || element.namespaceURI !== HTML_NS || !isWritableSignal(handle))
12150
+ presentationMiss(false);
12151
+ validateDirectSignalControl(element, site);
12152
+ const value = handle.get();
12153
+ if (typeof value !== "string") presentationMiss(false);
12154
+ const owner = scope.block.idState.renderOwner;
12155
+ const lease = owner.controlLeases?.get(element);
12156
+ const valid = (retired = false) => owner.bindingContainer.contains(element) && (!retired || frame.lease.valid?.() !== false) && (lease !== void 0 && !retired ? lease.owner === owner && lease.active() && runWithBlockSignalOwner(scope, () => lease.matches(handle)) : !hasSignalControlBinding(element, "value") && !hasHydrationControlSignalWriter(element, "value"));
12157
+ if (!valid()) presentationMiss(false);
12158
+ const prior = previous;
12159
+ const reusable = prior?.[DIRECT_SIGNAL_BINDING] === true && !prior.disposed && prior.input === void 0 && prior.handle === handle && prior.target === element && prior.kind === "value" && prior.site === site;
12160
+ const binding = reusable ? prior : {
12161
+ [DIRECT_SIGNAL_BINDING]: true,
12162
+ scope,
12163
+ target: element,
12164
+ kind: "value",
12165
+ site,
12166
+ handle,
12167
+ value,
12168
+ disposed: false
12169
+ };
12170
+ (frame.controls ??= /* @__PURE__ */ new Map()).set(element, { valid, binding, lease });
12171
+ trackPresentationFrame(frame);
12172
+ if (!reusable) registerHookCleanup(scope, () => disposeDirectSignalBinding(binding));
12173
+ if (binding.unsubscribe === void 0) {
12174
+ const notify = Object.assign(
12175
+ () => {
12176
+ if (binding.controlWriterCleanup !== void 0) updateDirectSignalBinding(binding);
12177
+ },
12178
+ {
12179
+ [NATIVE_TRANSITION_CONSUMER]: {
12180
+ active: () => binding.controlWriterCleanup !== void 0 && !binding.disposed && !scope.block.disposed,
12181
+ prepare: () => prepareNativeTransitionBlock(scope.block)
12182
+ }
12183
+ }
12184
+ );
12185
+ const stop = handle[SIGNAL_BINDING_SUBSCRIBE](
12186
+ notify,
12187
+ () => disposeDirectSignalBinding(binding)
12188
+ );
12189
+ if (typeof stop !== "function") throw new TypeError(formatClientError(74));
12190
+ if (binding.disposed || scope.block.disposed) {
12191
+ stop();
12192
+ presentationMiss(false);
12193
+ }
12194
+ binding.unsubscribe = stop;
12195
+ }
12196
+ preparePresentationOperation(frame, element, "value", () => {
12197
+ if (binding.disposed || scope.block.disposed) return;
12198
+ runWithBlockSignalOwner(scope, () => {
12199
+ if (!valid()) throw new Error(formatClientError(77));
12200
+ const before = snapshotHydrationControl(element);
12201
+ const ctrl = armControlled(element);
12202
+ ctrl.composing = before.composing || lease?.composing() === true;
12203
+ if (ctrl.composing) element.addEventListener("blur", onCtrlCompositionEnd);
12204
+ ctrl.v = UNCONTROLLED;
12205
+ let failed = false;
12206
+ let failure;
12207
+ if (lease !== void 0) {
12208
+ try {
12209
+ lease.retire();
12210
+ } catch (error) {
12211
+ failed = true;
12212
+ failure = error;
12213
+ } finally {
12214
+ lease.owner = void 0;
12215
+ }
12216
+ }
12217
+ if (lease !== void 0 && !scope.block.disposed && !valid(true))
12218
+ throw failed ? failure : new Error(formatClientError(77));
12219
+ if (lease !== void 0) owner.controlLeases.delete(element);
12220
+ if (!binding.disposed && !scope.block.disposed) {
12221
+ const snapshot = snapshotHydrationControl(element);
12222
+ binding.value = element.value;
12223
+ binding.pendingControl = snapshot.editRevision > (lease === void 0 ? 0 : before.editRevision);
12224
+ installDirectSignalControl(binding);
12225
+ if (!binding.pendingControl) consumeHydrationControl(element, snapshot.revision);
12226
+ if (binding.pendingControl) queueDirectSignalControlAdoption(binding);
12227
+ else {
12228
+ const current = readSignalBinding(handle);
12229
+ ctrl.sawV = true;
12230
+ ctrl.v = value;
12231
+ binding.value = current;
12232
+ writeDirectSignalBinding(binding, current);
12233
+ }
12234
+ }
12235
+ if (failed) reportRendererOwnerError(scope, failure);
12236
+ });
12237
+ });
12238
+ return binding;
12239
+ });
12240
+ }
12046
12241
  function presentationHostWrite(writer, ...args) {
12047
12242
  const frame = PRESENTATION_HYDRATION;
12048
12243
  if (frame === null) return writer(...args);
@@ -12074,6 +12269,20 @@ function presentationHostWrite(writer, ...args) {
12074
12269
  function presentationWrite(writer, kind, ...args) {
12075
12270
  const frame = PRESENTATION_HYDRATION;
12076
12271
  if (frame === null) return writer(...args);
12272
+ if (frame.lease.host !== void 0) {
12273
+ const channels = frame.lease.host;
12274
+ const changed2 = kind.endsWith("IfChanged") || kind.startsWith("updateFresh");
12275
+ const element2 = kind === "bindSignalAttribute" || kind === "nativeStyleBinding" || kind === "nativeProjectionBinding" ? args[2] : args[changed2 ? 2 : 0];
12276
+ const keys = kind === "nativeProjectionBinding" ? args[4].map((field) => field === "className" ? "class" : field) : kind === "setStyleProperties" ? args[1].filter((_, index) => index % 2 === 0).map((key2) => "style:" + key2) : [
12277
+ kind === "nativeStyleBinding" || kind === "setStyle" ? "style" : kind === "setStyleProperty" ? "style:" + args[1] : kind.includes("Class") ? "class" : kind === "bindSignalAttribute" ? args[3] : args[changed2 ? 3 : 1]
12278
+ ];
12279
+ if (element2 !== frame.lease.root || keys.some((key2) => !channels.has(key2)) || kind === "bindSignalValue" || kind === "setEventHandler" || kind === "bindSignalText" || kind === "setText" || kind === "markDangerouslySetInnerHTMLChildren")
12280
+ presentationMiss(false);
12281
+ }
12282
+ if (kind === "queueNativeChangeDiagnostic") {
12283
+ preparePresentationOperation(frame, args[0], "nativeChangeDiagnostic", () => writer(...args));
12284
+ return void 0;
12285
+ }
12077
12286
  if (kind === "markDangerouslySetInnerHTMLChildren") {
12078
12287
  const element2 = args[0];
12079
12288
  if (!frame.writes.get(element2)?.has("hostProps") || element2[DANGER_HTML_ACTIVE] === true)
@@ -12081,28 +12290,32 @@ function presentationWrite(writer, kind, ...args) {
12081
12290
  preparePresentationOperation(frame, element2, "hostChildren", () => writer(element2));
12082
12291
  return void 0;
12083
12292
  }
12084
- if (kind === "bindSignalText") {
12085
- const value = args[3];
12086
- if (value !== null && (typeof value === "object" || typeof value === "function") || typeof args[7] !== "string")
12087
- presentationMiss(false);
12088
- return bindingText(args[2], value, args[7]);
12089
- }
12293
+ if (kind === "bindSignalText") return preparePresentationSignalBinding(args, frame, true);
12090
12294
  if (kind === "setText") {
12091
12295
  const text = coerceText(args[1]);
12092
12296
  preparePresentationOperation(frame, args[0], "text", () => writer(args[0], text));
12093
12297
  return void 0;
12094
12298
  }
12095
- if (kind === "bindSignalAttribute") return preparePresentationSignalAttribute(args, frame);
12299
+ if (kind === "bindSignalAttribute") return preparePresentationSignalBinding(args, frame);
12300
+ if (kind === "bindSignalValue") return preparePresentationSignalValue(args, frame);
12096
12301
  if (kind === "nativeStyleBinding" || kind === "nativeProjectionBinding") {
12097
12302
  const [owner, slot, el] = args;
12098
12303
  const style = kind === "nativeStyleBinding";
12099
- nativePresentationBinding(
12100
- owner,
12101
- slot,
12102
- el,
12103
- style ? preparedNativeStyleBody : preparedNativeProjectionBody,
12104
- style ? { el, value: args[3], frame } : { el, compute: args[3], fields: args[4], frame }
12105
- );
12304
+ try {
12305
+ nativePresentationBinding(
12306
+ owner,
12307
+ slot,
12308
+ el,
12309
+ style ? preparedNativeStyleBody : preparedNativeProjectionBody,
12310
+ style ? { el, value: args[3], frame } : { el, compute: args[3], fields: args[4], frame }
12311
+ );
12312
+ } finally {
12313
+ if (frame.hostSuccessors !== void 0) {
12314
+ const binding = owner.slots[slot];
12315
+ if (binding !== void 0)
12316
+ frame.hostSuccessors.set(binding, () => disposeNativeStyleBinding(binding));
12317
+ }
12318
+ }
12106
12319
  return void 0;
12107
12320
  }
12108
12321
  const prepared = args.slice();
@@ -12321,11 +12534,14 @@ function disposeDirectSignalBinding(binding) {
12321
12534
  if (DEFERRED_LAYOUT_DRIVER?.stageAction(() => disposeDirectSignalBinding(binding)) === true)
12322
12535
  return;
12323
12536
  binding.disposed = true;
12324
- binding.unsubscribe?.();
12325
- retireNativeTransitionBlock(binding.scope.block);
12326
- binding.controlWriterCleanup?.();
12327
- if (binding.input !== void 0) {
12328
- domNode(binding.target).removeEventListener("input", binding.input);
12537
+ try {
12538
+ binding.unsubscribe?.();
12539
+ } finally {
12540
+ retireNativeTransitionBlock(binding.scope.block);
12541
+ binding.controlWriterCleanup?.();
12542
+ if (binding.input !== void 0) {
12543
+ domNode(binding.target).removeEventListener("input", binding.input);
12544
+ }
12329
12545
  }
12330
12546
  }
12331
12547
  function directSignalControlValue(binding, snapshot) {
@@ -12490,6 +12706,8 @@ function activateDirectSignalBinding(binding, revision) {
12490
12706
  }
12491
12707
  }
12492
12708
  function createDirectSignalBinding(scope, target, value, site, kind, name, attributeKind, text) {
12709
+ if (kind === "value" && scope.block.idState.renderOwner?.controlLeases?.has(target))
12710
+ presentationMiss(false);
12493
12711
  const handle = isSignalHandle(value) ? value : null;
12494
12712
  const binding = {
12495
12713
  [DIRECT_SIGNAL_BINDING]: true,
@@ -14473,6 +14691,8 @@ function bindSignalHostPropSources(scope, previous, element, sources, site, hasN
14473
14691
  binding.sources = sources;
14474
14692
  }
14475
14693
  const valueControl = winningSignalHostControl(sources, "value");
14694
+ if (valueControl !== null && scope.block.idState.renderOwner?.controlLeases?.has(element))
14695
+ presentationMiss(false);
14476
14696
  const checkedControl = winningSignalHostControl(sources, "checked");
14477
14697
  const controlSnapshot = isWritableSignal(valueControl) || isWritableSignal(checkedControl) ? snapshotHydrationControl(element) : null;
14478
14698
  if (controlSnapshot !== null) {
@@ -16125,6 +16345,7 @@ function setNativeChangeDiagnosticMetadata(el, value) {
16125
16345
  if (value === "runtime") queueDevFormDiagnostic(el, CURRENT_SCOPE ?? void 0, true);
16126
16346
  }
16127
16347
  function setValue(el, value) {
16348
+ if (CURRENT_SCOPE?.block.idState.renderOwner?.controlLeases?.has(el)) presentationMiss(false);
16128
16349
  const input = el;
16129
16350
  const ctrl = armControlled(el);
16130
16351
  if (TRANSITION_JOURNAL !== null) journalControlled(el, "value", "defaultValue");
@@ -26884,6 +27105,11 @@ function makeRoot(container, rootBlock, currentBody, currentKey, idState, output
26884
27105
  }
26885
27106
  unmounted = true;
26886
27107
  renderOwner.disposed = true;
27108
+ if (renderOwner.controlLeases !== void 0) {
27109
+ for (const lease of renderOwner.controlLeases.values())
27110
+ if (lease !== void 0) lease.owner = void 0;
27111
+ renderOwner.controlLeases.clear();
27112
+ }
26887
27113
  if (renderOwner.bindingLeases !== void 0) {
26888
27114
  for (const lease of renderOwner.bindingLeases) {
26889
27115
  releaseBindingHandoff(lease);
@@ -26944,7 +27170,8 @@ function makeRoot(container, rootBlock, currentBody, currentKey, idState, output
26944
27170
  }
26945
27171
  function createRootWithOutputHandler(container, options, outputHandler) {
26946
27172
  assertValidRootContainer(container);
26947
- if (options?.bindingLeases !== void 0) throw new Error(formatClientError(76));
27173
+ if (options?.bindingLeases !== void 0 || options?.controlLeases !== void 0)
27174
+ throw new Error(formatClientError(76));
26948
27175
  options = warnCreateRootElementOption(options);
26949
27176
  const ownerToken = claimRootContainer(container);
26950
27177
  registerDelegationTarget(container, true);
@@ -27004,12 +27231,20 @@ function hydrateRoot(container, bodyOrElement, propsOrOptions, rootOptions) {
27004
27231
  const bindingLeases = rootOptions?.bindingLeases?.map((handle) => {
27005
27232
  const capability = handle;
27006
27233
  const lease = capability[BINDING_HANDOFF]?.();
27007
- if (lease === void 0 || !lease.active() || lease.owner !== void 0 || !container.contains(lease.root))
27234
+ if (lease === void 0 || !lease.active() || lease.host !== void 0 && lease.valid?.() === false || lease.owner !== void 0 || !container.contains(lease.root))
27008
27235
  throw new Error(formatClientError(77));
27009
27236
  return lease;
27010
27237
  });
27011
27238
  if (bindingLeases !== void 0 && new Set(bindingLeases).size !== bindingLeases.length)
27012
27239
  throw new Error(formatClientError(78));
27240
+ const controlLeases = rootOptions?.controlLeases?.map((handle) => {
27241
+ const lease = handle?.[CONTROL_HANDOFF]?.();
27242
+ if (!bindingLeases?.length || lease === void 0 || !lease.active() || lease.owner !== void 0 || lease.channel !== "value" || lease.control.localName !== "textarea" || !container.contains(lease.control))
27243
+ throw new Error(formatClientError(77));
27244
+ return lease;
27245
+ });
27246
+ if (controlLeases !== void 0 && new Set(controlLeases.map((lease) => lease.control)).size !== controlLeases.length)
27247
+ throw new Error(formatClientError(78));
27013
27248
  const nativeSidecar = findHydrateSeedSidecar(container, NATIVE_SIGNAL_SEED_ATTR);
27014
27249
  const nativeManifest = nativeSidecar === null ? void 0 : parseNativeSignalManifest(
27015
27250
  (STAGED_DOM?.view(nativeSidecar) ?? nativeSidecar).textContent || ""
@@ -27071,6 +27306,10 @@ function hydrateRoot(container, bodyOrElement, propsOrOptions, rootOptions) {
27071
27306
  rootOptions
27072
27307
  );
27073
27308
  const owner = idState.renderOwner;
27309
+ if (controlLeases !== void 0) {
27310
+ owner.controlLeases = new Map(controlLeases.map((lease) => [lease.control, lease]));
27311
+ for (const lease of controlLeases) lease.owner = owner;
27312
+ }
27074
27313
  if (bindingLeases !== void 0) {
27075
27314
  owner.bindingLeases = new Set(bindingLeases);
27076
27315
  owner.bindingContainer = container;
@@ -27104,7 +27343,7 @@ function hydrateRoot(container, bodyOrElement, propsOrOptions, rootOptions) {
27104
27343
  while (firstNode !== null && (firstNode.nodeType === 10 || isRendererHydrationStyle(firstNode)))
27105
27344
  firstNode = getNextSibling(firstNode);
27106
27345
  const hydration = new HydrationCapability(rootBlock, firstNode, seeds);
27107
- if (bindingLeases?.some((lease) => lease.revision !== void 0)) {
27346
+ if (bindingLeases?.length) {
27108
27347
  const attempted = rootBlock;
27109
27348
  hydration.retryPresentation = () => {
27110
27349
  if (owner.disposed || owner.current !== attempted) return;
@@ -27112,7 +27351,12 @@ function hydrateRoot(container, bodyOrElement, propsOrOptions, rootOptions) {
27112
27351
  collectVisibleSubtreeRefs(attempted, refs);
27113
27352
  withRefDetachSuppression(refs, () => unmountBlock(attempted, false));
27114
27353
  queueMicrotask(() => {
27115
- if (!owner.disposed && owner.current === attempted) adopt();
27354
+ if (owner.disposed || owner.current !== attempted) return;
27355
+ try {
27356
+ adopt();
27357
+ } catch (error) {
27358
+ if (!reportUncaughtError(rootBlock, error)) throw error;
27359
+ }
27116
27360
  });
27117
27361
  };
27118
27362
  }