@hyperframes/studio 0.8.105 → 0.8.107

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.
Files changed (161) hide show
  1. package/dist/assets/{hyperframes-player-tnfCiVdR.js → hyperframes-player-h4mktSdm.js} +1 -1
  2. package/dist/assets/index-CM1xA_jD.js +394 -0
  3. package/dist/assets/{index-Ca6LZlVb.js → index-D8GKc2D9.js} +1 -1
  4. package/dist/assets/index-lrT-gfHa.css +1 -0
  5. package/dist/assets/{index-zpe332hb.js → index-zoqPmBF6.js} +1 -1
  6. package/dist/{chunk-QADHJFDF.js → chunk-4OZC426H.js} +13 -3
  7. package/dist/chunk-4OZC426H.js.map +1 -0
  8. package/dist/index.d.ts +381 -14
  9. package/dist/index.html +11 -2
  10. package/dist/index.js +9974 -9045
  11. package/dist/index.js.map +1 -1
  12. package/dist/player.js +1 -1
  13. package/dist/styles.css +1 -1
  14. package/package.json +9 -9
  15. package/src/App.tsx +5 -3
  16. package/src/agentToolsExport.test.ts +8 -0
  17. package/src/captions/keyboard.test.ts +12 -0
  18. package/src/captions/keyboard.ts +2 -1
  19. package/src/components/StudioHeader.dom.test.tsx +31 -9
  20. package/src/components/StudioHeader.tsx +4 -1
  21. package/src/components/StudioRightPanels.tsx +2 -0
  22. package/src/components/ThemeToggle.tsx +99 -0
  23. package/src/components/TimelineToolbar.test.tsx +27 -0
  24. package/src/components/TimelineToolbar.tsx +2 -5
  25. package/src/components/editor/DomEditOverlay.tsx +31 -5
  26. package/src/components/editor/PropertyPanel.tsx +1 -14
  27. package/src/components/editor/PropertyPanelFlat.tsx +3 -6
  28. package/src/components/editor/SnapToolbar.test.tsx +32 -1
  29. package/src/components/editor/SnapToolbar.tsx +3 -4
  30. package/src/components/editor/SourceEditor.tsx +2 -6
  31. package/src/components/editor/domEditOverlayStartGesture.ts +132 -117
  32. package/src/components/editor/fontAssets.test.ts +12 -22
  33. package/src/components/editor/gestureHoldsPreviewReload.test.tsx +338 -0
  34. package/src/components/editor/gsapAnimatesProperty.ts +3 -0
  35. package/src/components/editor/manualEdits.test.ts +4 -3
  36. package/src/components/editor/manualEditsDom.ts +58 -2
  37. package/src/components/editor/manualEditsDomPatches.ts +5 -1
  38. package/src/components/editor/manualOffsetDrag.ts +4 -1
  39. package/src/components/editor/marqueeCommit.test.tsx +69 -1
  40. package/src/components/editor/marqueeCommit.ts +6 -1
  41. package/src/components/editor/mediaAudioEdits.test.ts +212 -0
  42. package/src/components/editor/mediaAudioEdits.ts +253 -0
  43. package/src/components/editor/overlayFrameLoop.test.ts +27 -0
  44. package/src/components/editor/overlayFrameLoop.ts +21 -0
  45. package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +286 -87
  46. package/src/components/editor/propertyPanelFlatMediaSection.tsx +17 -20
  47. package/src/components/editor/propertyPanelFlatProps.ts +1 -0
  48. package/src/components/editor/propertyPanelFont.test.tsx +80 -3
  49. package/src/components/editor/propertyPanelFont.tsx +69 -46
  50. package/src/components/editor/propertyPanelHelpers.ts +1 -2
  51. package/src/components/editor/propertyPanelMediaSection.tsx +17 -27
  52. package/src/components/editor/propertyPanelTypes.ts +2 -0
  53. package/src/components/editor/studioCodeTheme.test.ts +20 -0
  54. package/src/components/editor/studioCodeTheme.ts +99 -0
  55. package/src/components/editor/useDomEditNudge.test.tsx +165 -2
  56. package/src/components/editor/useDomEditNudge.ts +43 -25
  57. package/src/components/editor/useInlineTextEditing.tsx +1 -1
  58. package/src/components/editor/useMotionPathData.test.tsx +64 -0
  59. package/src/components/editor/useMotionPathData.ts +5 -1
  60. package/src/components/panels/SlideshowPanel.test.ts +27 -0
  61. package/src/components/panels/SlideshowPanel.tsx +12 -7
  62. package/src/contexts/DomEditContext.test.tsx +62 -0
  63. package/src/contexts/DomEditContext.tsx +94 -77
  64. package/src/hooks/appHotkeysDispatch.ts +2 -1
  65. package/src/hooks/canvasQuickSaves.test.tsx +72 -7
  66. package/src/hooks/domEditCommitTypes.ts +10 -0
  67. package/src/hooks/domEditPersistFailure.ts +12 -1
  68. package/src/hooks/domStyleCommit.fonts.test.ts +5 -4
  69. package/src/hooks/domStyleCommit.ts +2 -8
  70. package/src/hooks/gsapDragCommit.ts +3 -8
  71. package/src/hooks/gsapDragPositionCommit.ts +6 -1
  72. package/src/hooks/gsapEditOutcome.ts +2 -1
  73. package/src/hooks/gsapResizeElementSize.test.tsx +136 -0
  74. package/src/hooks/gsapResizeGeometrySweep.test.ts +3 -1
  75. package/src/hooks/gsapResizeIntercept.test.ts +3 -11
  76. package/src/hooks/gsapResizeIntercept.ts +6 -2
  77. package/src/hooks/gsapResizeMixedTween.test.ts +1 -2
  78. package/src/hooks/gsapRuntimeBridge.test.ts +55 -0
  79. package/src/hooks/gsapRuntimeBridge.ts +5 -3
  80. package/src/hooks/gsapRuntimeKeyframes.ts +1 -3
  81. package/src/hooks/inspectorSaveReload.test.tsx +107 -0
  82. package/src/hooks/previewPromotionSelection.test.tsx +180 -0
  83. package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +16 -1
  84. package/src/hooks/useAppHotkeys.test.ts +49 -0
  85. package/src/hooks/useAppHotkeys.textEditing.test.tsx +1 -1
  86. package/src/hooks/useAppHotkeys.ts +4 -5
  87. package/src/hooks/useAutomationSelectionKeyboard.test.tsx +17 -0
  88. package/src/hooks/useAutomationSelectionKeyboard.ts +3 -9
  89. package/src/hooks/useClipboard.source.test.ts +99 -19
  90. package/src/hooks/useClipboard.ts +145 -124
  91. package/src/hooks/useDomEditAttributeCommits.ts +71 -29
  92. package/src/hooks/useDomEditCommits.test.tsx +172 -92
  93. package/src/hooks/useDomEditCommits.ts +2 -0
  94. package/src/hooks/useDomEditCommitsHelpers.ts +4 -6
  95. package/src/hooks/useDomEditPersist.ts +28 -13
  96. package/src/hooks/useDomEditPositionPatchCommit.ts +2 -0
  97. package/src/hooks/useDomEditSession.test.tsx +76 -72
  98. package/src/hooks/useDomEditSession.ts +9 -46
  99. package/src/hooks/useDomEditTextCommits.ts +4 -8
  100. package/src/hooks/useDomGeometryCommits.ts +11 -2
  101. package/src/hooks/useEditHistoryActions.paint.test.tsx +122 -3
  102. package/src/hooks/useGsapAwareEditing.ts +6 -6
  103. package/src/hooks/useGsapScriptCommits.test.tsx +19 -0
  104. package/src/hooks/useGsapScriptCommits.ts +5 -4
  105. package/src/hooks/useGsapTweenCache.ts +1 -2
  106. package/src/hooks/useInlineTextEdit.test.tsx +27 -0
  107. package/src/hooks/useInlineTextEdit.ts +21 -5
  108. package/src/hooks/useKeyframeKeyboard.test.tsx +15 -0
  109. package/src/hooks/useKeyframeKeyboard.ts +2 -8
  110. package/src/hooks/useLivePreviewIframe.ts +18 -0
  111. package/src/hooks/usePreviewPersistence.history.test.tsx +47 -0
  112. package/src/hooks/usePreviewPersistence.ts +8 -5
  113. package/src/hooks/useRenderClipContent.ts +3 -3
  114. package/src/hooks/useTrackPendingTimelineEdit.ts +2 -6
  115. package/src/index.ts +9 -0
  116. package/src/livePreviewIframeExport.test.tsx +33 -0
  117. package/src/main.tsx +4 -1
  118. package/src/player/components/AudioWaveform.test.tsx +36 -15
  119. package/src/player/components/AudioWaveform.tsx +24 -7
  120. package/src/player/components/Timeline.test.ts +0 -21
  121. package/src/player/components/Timeline.tsx +0 -1
  122. package/src/player/components/TimelineClip.test.tsx +38 -0
  123. package/src/player/components/TimelineClip.tsx +14 -6
  124. package/src/player/components/TimelineClipFades.test.tsx +376 -74
  125. package/src/player/components/TimelineClipFades.tsx +368 -137
  126. package/src/player/components/timelineLayout.ts +0 -28
  127. package/src/player/components/useTimelineKeyboardActor.ts +1 -1
  128. package/src/player/hooks/timelinePlayerTestHarness.ts +27 -0
  129. package/src/player/hooks/usePlaybackKeyboard.test.ts +43 -0
  130. package/src/player/hooks/useShadowPreviewReload.ts +72 -5
  131. package/src/player/lib/automationStoreSync.test.ts +18 -1
  132. package/src/player/lib/automationStoreSync.ts +29 -17
  133. package/src/player/lib/playbackShortcuts.ts +3 -7
  134. package/src/player/lib/timelineElementHelpers.test.ts +16 -0
  135. package/src/player/lib/timelineElementHelpers.ts +16 -2
  136. package/src/player/lib/timelineViewportBudgets.test.ts +2 -0
  137. package/src/player/lib/timelineViewportBudgets.ts +4 -0
  138. package/src/player/sceneSwap.ts +18 -0
  139. package/src/styles/components.css +26 -0
  140. package/src/styles/studio.css +6 -0
  141. package/src/styles/theme.css +10 -0
  142. package/src/utils/clipboardPayload.ts +10 -1
  143. package/src/utils/gsapSoftReload.ts +3 -2
  144. package/src/utils/gsapUndoRestore.browser.test.ts +94 -0
  145. package/src/utils/gsapUndoRestore.test.ts +145 -1
  146. package/src/utils/gsapUndoRestore.ts +70 -21
  147. package/src/utils/sourcePatcher.ts +1 -1
  148. package/src/utils/studioFontHelpers.ts +2 -23
  149. package/src/utils/studioHelpers.test.ts +30 -0
  150. package/src/utils/studioHelpers.ts +18 -4
  151. package/src/utils/studioPendingEdits.test.ts +63 -0
  152. package/src/utils/studioPendingEdits.ts +37 -7
  153. package/src/utils/studioTheme.test.tsx +141 -0
  154. package/src/utils/studioTheme.ts +34 -0
  155. package/src/utils/studioUiPreferences.ts +3 -0
  156. package/src/utils/typingTarget.test.ts +21 -1
  157. package/src/utils/typingTarget.ts +8 -0
  158. package/src/webmcp/useStudioAgentTools.ts +3 -1
  159. package/dist/assets/index-Cre9X4RS.css +0 -1
  160. package/dist/assets/index-DEDhPmPD.js +0 -399
  161. package/dist/chunk-QADHJFDF.js.map +0 -1
@@ -12,6 +12,7 @@ import {
12
12
  import { readCssRotationTarget, readRotationBase } from "./rotationDraft";
13
13
  import {
14
14
  beginStudioManualEditGesture,
15
+ endStudioManualEditGesture,
15
16
  captureStudioBoxSize,
16
17
  captureStudioPathOffset,
17
18
  captureStudioRotation,
@@ -81,48 +82,54 @@ export function startGroupDrag(
81
82
  }
82
83
  members.push(result.member);
83
84
  }
84
- resetDragMoveLog();
85
- logDrag("group-start", {
86
- // A member whose mapping differs from its neighbours travels a different
87
- // distance for the same pointer delta, which is the group coming apart.
88
- members: Object.fromEntries(
89
- members.map((member) => [
90
- member.key,
91
- {
92
- map: `${member.screenToOffset.a.toFixed(3)},${member.screenToOffset.d.toFixed(3)}`,
93
- base: `${Math.round(member.baseGsap.x)},${Math.round(member.baseGsap.y)}`,
94
- offset: `${Math.round(member.initialOffset.x)},${Math.round(member.initialOffset.y)}`,
95
- },
96
- ]),
97
- ),
98
- at: readDragPositions(members),
99
- });
85
+ try {
86
+ resetDragMoveLog();
87
+ logDrag("group-start", {
88
+ // A member whose mapping differs from its neighbours travels a different
89
+ // distance for the same pointer delta, which is the group coming apart.
90
+ members: Object.fromEntries(
91
+ members.map((member) => [
92
+ member.key,
93
+ {
94
+ map: `${member.screenToOffset.a.toFixed(3)},${member.screenToOffset.d.toFixed(3)}`,
95
+ base: `${Math.round(member.baseGsap.x)},${Math.round(member.baseGsap.y)}`,
96
+ offset: `${Math.round(member.initialOffset.x)},${Math.round(member.initialOffset.y)}`,
97
+ },
98
+ ]),
99
+ ),
100
+ at: readDragPositions(members),
101
+ });
100
102
 
101
- const overlayEl = opts.overlayRef.current;
102
- const iframe = opts.iframeRef.current;
103
- const snapContext =
104
- overlayEl && iframe
105
- ? collectSnapContext({
106
- overlayEl,
107
- iframe,
108
- excludeElements: buildExcludeElements({
103
+ const overlayEl = opts.overlayRef.current;
104
+ const iframe = opts.iframeRef.current;
105
+ const snapContext =
106
+ overlayEl && iframe
107
+ ? collectSnapContext({
108
+ overlayEl,
109
109
  iframe,
110
- groupSelections: items.map((i) => i.selection),
111
- }),
112
- })
113
- : undefined;
110
+ excludeElements: buildExcludeElements({
111
+ iframe,
112
+ groupSelections: items.map((i) => i.selection),
113
+ }),
114
+ })
115
+ : undefined;
114
116
 
115
- e.preventDefault();
116
- e.stopPropagation();
117
- e.currentTarget.setPointerCapture(e.pointerId);
118
- opts.rafPausedRef.current = true;
119
- opts.groupGestureRef.current = {
120
- startX: e.clientX,
121
- startY: e.clientY,
122
- originItems: items,
123
- members,
124
- snapContext,
125
- };
117
+ e.preventDefault();
118
+ e.stopPropagation();
119
+ e.currentTarget.setPointerCapture(e.pointerId);
120
+ opts.rafPausedRef.current = true;
121
+ opts.groupGestureRef.current = {
122
+ startX: e.clientX,
123
+ startY: e.clientY,
124
+ originItems: items,
125
+ members,
126
+ snapContext,
127
+ };
128
+ } catch (error) {
129
+ opts.groupGestureRef.current = null;
130
+ restoreManualOffsetDragMembers(members);
131
+ throw error;
132
+ }
126
133
  return true;
127
134
  }
128
135
 
@@ -212,91 +219,99 @@ export function startGesture(
212
219
  // Hold a % translate as the same px now, so a growing box can't drag it along mid-frame.
213
220
  if (result.member.plainTranslate) applyManualOffsetDragDraft(result.member, 0, 0);
214
221
  } else {
215
- manualEditDragToken = beginStudioManualEditGesture(sel.element);
222
+ manualEditDragToken = beginStudioManualEditGesture(sel.element, "resize");
216
223
  }
217
224
  } else {
218
- manualEditDragToken = beginStudioManualEditGesture(sel.element);
225
+ manualEditDragToken = beginStudioManualEditGesture(
226
+ sel.element,
227
+ kind === "rotate" ? "rotate" : "resize",
228
+ );
219
229
  }
220
230
  }
221
231
 
222
- // Rotation base: the angle the element shows. An element GSAP does not turn, or a plain-translate
223
- // move, never asks GSAP: reading a property makes it bake the CSS into its transform.
224
- const plain = !!pathOffsetMember?.plainTranslate || !gsapWritesRotation(sel.element);
225
- const plainRotation = plain && kind === "rotate" ? readCssRotationTarget(sel.element) : null;
226
- const rotation = { angle: readRotationBase(sel.element, plain) };
227
- const overlayBounds = overlayEl?.getBoundingClientRect();
228
- const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
229
- const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
232
+ // A throw before the gesture is armed would leave its mark, and the mark holds every reload.
233
+ try {
234
+ // Rotation base: the angle the element shows. An element GSAP does not turn, or a plain-translate
235
+ // move, never asks GSAP: reading a property makes it bake the CSS into its transform.
236
+ const plain = !!pathOffsetMember?.plainTranslate || !gsapWritesRotation(sel.element);
237
+ const plainRotation = plain && kind === "rotate" ? readCssRotationTarget(sel.element) : null;
238
+ const rotation = { angle: readRotationBase(sel.element, plain) };
239
+ const overlayBounds = overlayEl?.getBoundingClientRect();
240
+ const centerX = (overlayBounds?.left ?? 0) + rect.left + rect.width / 2;
241
+ const centerY = (overlayBounds?.top ?? 0) + rect.top + rect.height / 2;
230
242
 
231
- const iframe = opts.iframeRef.current;
243
+ const iframe = opts.iframeRef.current;
232
244
 
233
- // For a center-anchored corner resize, capture the element's rendered CENTER (the
234
- // centroid of its four real, rotation-aware corners) now, so per-frame anchoring
235
- // can pin that exact point instead of an axis-aligned width/height delta (which
236
- // only holds the center still when the element grows symmetrically from an
237
- // unrotated layout box). Present whenever an anchor member exists (all corners).
238
- let resizeFixedCenterStart: { x: number; y: number } | undefined;
239
- if (kind === "resize" && pathOffsetMember && overlayEl && iframe) {
240
- const corners = elementCornerOverlayPoints(overlayEl, iframe, sel.element);
241
- if (corners) resizeFixedCenterStart = overlayCornersCentroid(corners);
242
- }
243
- const snapContext =
244
- (kind === "drag" || kind === "resize") && overlayEl && iframe
245
- ? collectSnapContext({
246
- overlayEl,
247
- iframe,
248
- excludeElements: buildExcludeElements({ iframe, selection: sel }),
249
- })
250
- : undefined;
251
- e.preventDefault();
252
- e.stopPropagation();
253
- e.currentTarget.setPointerCapture(e.pointerId);
254
- opts.rafPausedRef.current = true;
255
- opts.gestureRef.current = {
256
- kind,
257
- mode,
258
- selection: sel,
259
- startX: e.clientX,
260
- startY: e.clientY,
261
- centerX,
262
- centerY,
263
- initialPathOffset,
264
- initialRotation: captureStudioRotation(sel.element),
265
- initialBoxSize: captureStudioBoxSize(sel.element),
266
- pathOffsetMember,
267
- originLeft: rect.left,
268
- originTop: rect.top,
269
- originWidth: rect.width,
270
- originHeight: rect.height,
271
- actualWidth,
272
- actualHeight,
273
- actualRotation: rotation.angle,
274
- plainRotation,
275
- editScaleX: rect.editScaleX,
276
- editScaleY: rect.editScaleY,
277
- contentScaleX,
278
- contentScaleY,
279
- manualEditDragToken,
280
- snapContext,
281
- resizeHandle: kind === "resize" ? (options?.resizeHandle ?? "se") : undefined,
282
- resizeFixedCenterStart,
283
- };
284
- if (kind === "resize") {
285
- resetResizeMoveLog();
286
- logResize("start", {
287
- handle: options?.resizeHandle ?? "se",
288
- pointer: { x: e.clientX, y: e.clientY },
289
- center: { x: centerX, y: centerY },
290
- origin: { left: rect.left, top: rect.top, w: rect.width, h: rect.height },
291
- actual: { w: actualWidth, h: actualHeight },
292
- editScale: { x: rect.editScaleX, y: rect.editScaleY },
293
- contentScale: { x: contentScaleX, y: contentScaleY },
294
- rotation: rotation.angle,
295
- hasOffsetMember: !!pathOffsetMember,
296
- fixedCenterStart: resizeFixedCenterStart ?? null,
297
- initialBoxSize: opts.gestureRef.current?.initialBoxSize ?? null,
298
- initialInlineStyle: sel.element.getAttribute("style"),
299
- });
245
+ // A corner resize pins the centroid of the four rotation-aware corners: an axis-aligned
246
+ // size delta only holds the centre still for a symmetric, unrotated growth.
247
+ let resizeFixedCenterStart: { x: number; y: number } | undefined;
248
+ if (kind === "resize" && pathOffsetMember && overlayEl && iframe) {
249
+ const corners = elementCornerOverlayPoints(overlayEl, iframe, sel.element);
250
+ if (corners) resizeFixedCenterStart = overlayCornersCentroid(corners);
251
+ }
252
+ const snapContext =
253
+ (kind === "drag" || kind === "resize") && overlayEl && iframe
254
+ ? collectSnapContext({
255
+ overlayEl,
256
+ iframe,
257
+ excludeElements: buildExcludeElements({ iframe, selection: sel }),
258
+ })
259
+ : undefined;
260
+ e.preventDefault();
261
+ e.stopPropagation();
262
+ e.currentTarget.setPointerCapture(e.pointerId);
263
+ opts.rafPausedRef.current = true;
264
+ opts.gestureRef.current = {
265
+ kind,
266
+ mode,
267
+ selection: sel,
268
+ startX: e.clientX,
269
+ startY: e.clientY,
270
+ centerX,
271
+ centerY,
272
+ initialPathOffset,
273
+ initialRotation: captureStudioRotation(sel.element),
274
+ initialBoxSize: captureStudioBoxSize(sel.element),
275
+ pathOffsetMember,
276
+ originLeft: rect.left,
277
+ originTop: rect.top,
278
+ originWidth: rect.width,
279
+ originHeight: rect.height,
280
+ actualWidth,
281
+ actualHeight,
282
+ actualRotation: rotation.angle,
283
+ plainRotation,
284
+ editScaleX: rect.editScaleX,
285
+ editScaleY: rect.editScaleY,
286
+ contentScaleX,
287
+ contentScaleY,
288
+ manualEditDragToken,
289
+ snapContext,
290
+ resizeHandle: kind === "resize" ? (options?.resizeHandle ?? "se") : undefined,
291
+ resizeFixedCenterStart,
292
+ };
293
+ if (kind === "resize") {
294
+ resetResizeMoveLog();
295
+ logResize("start", {
296
+ handle: options?.resizeHandle ?? "se",
297
+ pointer: { x: e.clientX, y: e.clientY },
298
+ center: { x: centerX, y: centerY },
299
+ origin: { left: rect.left, top: rect.top, w: rect.width, h: rect.height },
300
+ actual: { w: actualWidth, h: actualHeight },
301
+ editScale: { x: rect.editScaleX, y: rect.editScaleY },
302
+ contentScale: { x: contentScaleX, y: contentScaleY },
303
+ rotation: rotation.angle,
304
+ hasOffsetMember: !!pathOffsetMember,
305
+ fixedCenterStart: resizeFixedCenterStart ?? null,
306
+ initialBoxSize: opts.gestureRef.current?.initialBoxSize ?? null,
307
+ initialInlineStyle: sel.element.getAttribute("style"),
308
+ });
309
+ }
310
+ } catch (error) {
311
+ opts.gestureRef.current = null;
312
+ if (pathOffsetMember) restoreManualOffsetDragMembers([pathOffsetMember]);
313
+ else if (manualEditDragToken) endStudioManualEditGesture(sel.element, manualEditDragToken);
314
+ throw error;
300
315
  }
301
316
  return true;
302
317
  }
@@ -1,6 +1,6 @@
1
1
  // @vitest-environment jsdom
2
2
  import { expect, it } from "vitest";
3
- import { ensureImportedFontFace, injectPreviewImportedFont } from "../../utils/studioFontHelpers";
3
+ import { importedFontFaceCssFor, injectPreviewImportedFont } from "../../utils/studioFontHelpers";
4
4
  import { importedFontFaceCss, type ImportedFontAsset } from "./fontAssets";
5
5
 
6
6
  const face = (file: string, extra: Partial<ImportedFontAsset> = {}): ImportedFontAsset => ({
@@ -30,24 +30,25 @@ it("a face says which weight and style it draws, a variable file its weight rang
30
30
  );
31
31
  });
32
32
 
33
- it("two weights of one family are both kept in the file and both drawn in the preview", () => {
33
+ it("two weights of one family are both drawn in the preview", () => {
34
34
  const bold = face("Poppins-Bold.ttf", { weight: "700", style: "normal" });
35
35
  const regular = face("Poppins-Regular.ttf", { weight: "400", style: "normal" });
36
- const html = "<html><head></head><body></body></html>";
37
- const saved = ensureImportedFontFace(
38
- ensureImportedFontFace(html, bold, "index.html"),
39
- regular,
40
- "index.html",
41
- );
42
- expect(saved).toContain("font-weight: 700;");
43
- expect(saved).toContain("font-weight: 400;");
44
- expect(ensureImportedFontFace(saved, bold, "index.html")).toBe(saved);
45
36
  injectPreviewImportedFont(document, bold);
46
37
  injectPreviewImportedFont(document, regular);
47
38
  injectPreviewImportedFont(document, bold);
48
39
  expect(document.head.querySelectorAll("style")).toHaveLength(2);
49
40
  });
50
41
 
42
+ it("the saved rule points at the font from the file that uses it", () => {
43
+ const bold = face("Poppins-Bold.ttf");
44
+ expect(importedFontFaceCssFor(bold, "index.html")).toContain(
45
+ 'url("assets/fonts/Poppins/Poppins-Bold.ttf")',
46
+ );
47
+ expect(importedFontFaceCssFor(bold, "compositions/intro.html")).toContain(
48
+ 'url("../assets/fonts/Poppins/Poppins-Bold.ttf")',
49
+ );
50
+ });
51
+
51
52
  it("a weight the browser would drop is left out, and a family cannot close the style block", () => {
52
53
  for (const weight of ["0", "5000", "bold", "700;}"])
53
54
  expect(importedFontFaceCss(face("x.ttf", { weight }))).not.toContain("font-weight");
@@ -56,14 +57,3 @@ it("a weight the browser would drop is left out, and a family cannot close the s
56
57
  "</style>",
57
58
  );
58
59
  });
59
-
60
- it("a file name with a replacement pattern is saved as written, not expanded into the page", () => {
61
- const html = "<html><head></head><body>rest</body></html>";
62
- const odd = { family: "Cash", path: "assets/Cash$'Font.ttf", url: "/Cash$'Font.ttf" };
63
- const saved = ensureImportedFontFace(html, odd, "index.html");
64
- expect(saved).toContain("Cash$'Font.ttf");
65
- expect(saved.match(/<body>/g)).toHaveLength(1);
66
- const again = ensureImportedFontFace(saved, { ...odd, path: "assets/Cash$&.ttf" }, "index.html");
67
- expect(again).toContain("Cash$&.ttf");
68
- expect(again.match(/<body>/g)).toHaveLength(1);
69
- });