@hyperframes/sdk 0.8.193 → 0.8.194

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.
@@ -16,6 +16,9 @@ import type { ParsedDocument } from "./model.js";
16
16
  * property is a no-op in applyOne, so this is safe against fresh-base misses.)
17
17
  */
18
18
  export declare function applyOverrideSet(parsed: ParsedDocument, overrides: OverrideSet): void;
19
+ export declare function isElementSnapshotOverride(key: string, value: OverrideSet[string]): value is Record<string, unknown> & {
20
+ siblingIndex: number;
21
+ };
19
22
  interface ApplyOptions {
20
23
  /** Keep a copy of each removed element an inline script looks up (stored deletes replayed onto a base). */
21
24
  keepScriptTargets?: boolean;
@@ -122,6 +122,29 @@ export function applyOverrideSet(parsed, overrides) {
122
122
  }
123
123
  }
124
124
  applyPatchesToDocument(parsed, patches, { keepScriptTargets: true });
125
+ restoreOverridePositions(parsed, overrides);
126
+ }
127
+ export function isElementSnapshotOverride(key, value) {
128
+ if (value === null || typeof value !== "object")
129
+ return false;
130
+ return /^\/elements\/[^/]+$/.test(keyToPath(key) ?? "") && typeof value.siblingIndex === "number";
131
+ }
132
+ function restoreOverridePositions(parsed, overrides) {
133
+ const positions = [];
134
+ for (const [key, value] of Object.entries(overrides)) {
135
+ if (!isElementSnapshotOverride(key, value) || typeof value.childNodeIndex !== "number")
136
+ continue;
137
+ const el = findById(parsed.document, key);
138
+ if (!el?.parentElement)
139
+ continue;
140
+ positions.push({ el, parent: el.parentElement, childNodeIndex: value.childNodeIndex });
141
+ }
142
+ // Detach every restored sibling so inserting one cannot shift another's saved position.
143
+ for (const { el } of positions)
144
+ el.remove();
145
+ for (const { el, parent, childNodeIndex } of positions.sort((a, b) => a.childNodeIndex - b.childNodeIndex)) {
146
+ parent.insertBefore(el, parent.childNodes[childNodeIndex] ?? null);
147
+ }
125
148
  }
126
149
  /** Keep a deleted node a script looks up: serialize writes it back hidden, so the script finds it. */
127
150
  function keepDelete(parsed, el) {
@@ -235,7 +258,7 @@ function applyOne(parsed, patch, p, options) {
235
258
  keepDelete(parsed, el);
236
259
  el?.remove();
237
260
  }
238
- else if (patch.op === "add" && patch.value) {
261
+ else if ((patch.op === "add" || patch.op === "replace") && patch.value) {
239
262
  const v = patch.value;
240
263
  const parent = v.parentId
241
264
  ? findById(parsed.document, v.parentId)
@@ -248,6 +271,8 @@ function applyOne(parsed, patch, p, options) {
248
271
  const node = tmp.firstElementChild;
249
272
  if (!node)
250
273
  return;
274
+ if (patch.op === "replace")
275
+ findById(parsed.document, p.id)?.remove();
251
276
  const children = Array.from(parent.children);
252
277
  const ref = children[v.siblingIndex] ?? null;
253
278
  parent.insertBefore(node, ref);
package/dist/session.js CHANGED
@@ -12,14 +12,14 @@ import { buildRoots, flatElements, parsedAnimationIds } from "./document.js";
12
12
  import { syncFixFor, syncOffsetOf } from "./engine/syncTiming.js";
13
13
  import { parseMutable } from "./engine/model.js";
14
14
  import { applyOp, validateOp } from "./engine/mutate.js";
15
- import { getGsapScripts, resolveScoped, declarationCarriers } from "./engine/model.js";
15
+ import { getGsapScripts, resolveScoped, declarationCarriers, getSiblingIndex, } from "./engine/model.js";
16
16
  import { extractGsapLabels } from "@hyperframes/core/gsap-parser-acorn";
17
17
  import { stripEmbeddedRuntimeScripts } from "@hyperframes/core/compiler/html-document";
18
18
  import { readClipTiming } from "@hyperframes/core/composition-contract";
19
19
  import { readDeclaredDefaults, validateVariables, scanVariableUsage, } from "@hyperframes/core/variables";
20
20
  import { readVariableDeclarations } from "./engine/variableModel.js";
21
21
  import { serializeDocument } from "./engine/serialize.js";
22
- import { applyPatchesToDocument, applyOverrideSet } from "./engine/apply-patches.js";
22
+ import { applyPatchesToDocument, applyOverrideSet, isElementSnapshotOverride, } from "./engine/apply-patches.js";
23
23
  import { buildPatchEvent, pathToKey } from "./engine/patches.js";
24
24
  import { createHistory } from "./history.js";
25
25
  import { createPersistQueue } from "./persist-queue.js";
@@ -276,9 +276,9 @@ class CompositionImpl {
276
276
  allLabels = scripts.flatMap((script) => extractGsapLabels(script));
277
277
  this._gsapLabelCache = scripts.length ? { scripts: [...scripts], labels: allLabels } : null;
278
278
  }
279
- // refId remains a bare global id, matching runtime resolution. The shared
280
- // contract owns parsing, canonical duration preference, clamping, and
281
- // legacy data-end compatibility; this adapter only supplies document lookup.
279
+ // refId resolves as a bare hf-id (top level first), unlike the runtime's
280
+ // composition-scoped `id` lookup. The shared contract owns parsing, canonical
281
+ // duration preference, clamping, and legacy data-end; this adapter only supplies lookup.
282
282
  const timingCache = new Map();
283
283
  const visiting = new Set();
284
284
  const resolveTiming = (el) => {
@@ -488,6 +488,7 @@ class CompositionImpl {
488
488
  for (const p of forward) {
489
489
  const key = pathToKey(p.path);
490
490
  if (key !== null) {
491
+ delete this.overrides[key];
491
492
  this.overrides[key] =
492
493
  p.op === "remove"
493
494
  ? null
@@ -649,7 +650,20 @@ class CompositionImpl {
649
650
  }
650
651
  // ── T3 embedded-mode extras ──────────────────────────────────────────────────
651
652
  getOverrides() {
652
- return { ...this.overrides };
653
+ const overrides = { ...this.overrides };
654
+ for (const [key, value] of Object.entries(overrides)) {
655
+ if (!isElementSnapshotOverride(key, value))
656
+ continue;
657
+ const el = resolveScoped(this.parsed.document, key);
658
+ if (el?.parentElement) {
659
+ overrides[key] = {
660
+ ...value,
661
+ siblingIndex: getSiblingIndex(el),
662
+ childNodeIndex: Array.from(el.parentElement.childNodes).indexOf(el),
663
+ };
664
+ }
665
+ }
666
+ return overrides;
653
667
  }
654
668
  // fallow-ignore-next-line complexity
655
669
  applyPatches(patches, opts) {
@@ -665,6 +679,7 @@ class CompositionImpl {
665
679
  for (const p of patches) {
666
680
  const key = pathToKey(p.path);
667
681
  if (key !== null) {
682
+ delete this.overrides[key];
668
683
  this.overrides[key] =
669
684
  p.op === "remove"
670
685
  ? null
@@ -716,8 +731,6 @@ export async function openComposition(html, opts) {
716
731
  // the authored base values are readable. getVariableValue({ base: true })
717
732
  // serves them for the rest of the session (undo-to-base restores).
718
733
  const baseVariableDefaults = declaredDefaults(parsed);
719
- // T3 embedded: replay the stored override-set onto the base in one pass,
720
- // so the session exposes the user's exact edited state — not the template.
721
734
  if (opts?.overrides)
722
735
  applyOverrideSet(parsed, opts.overrides);
723
736
  const session = new CompositionImpl(parsed, opts ?? {}, baseVariableDefaults);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/sdk",
3
- "version": "0.8.193",
3
+ "version": "0.8.194",
4
4
  "description": "Headless, framework-neutral HyperFrames composition editing engine",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,8 +44,8 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "linkedom": "^0.18.12",
47
- "@hyperframes/parsers": "0.8.193",
48
- "@hyperframes/core": "0.8.193"
47
+ "@hyperframes/core": "0.8.194",
48
+ "@hyperframes/parsers": "0.8.194"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@types/node": "^25.0.10",