@hyperframes/studio 0.7.58 → 0.7.60

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 (173) hide show
  1. package/dist/assets/{hyperframes-player-CtTDO63S.js → hyperframes-player-3XTTaVNf.js} +1 -1
  2. package/dist/assets/{index-C47jAC3Q.js → index-D6etaey-.js} +1 -1
  3. package/dist/assets/index-DXbu6IPT.css +1 -0
  4. package/dist/assets/{index-DeQPzqwH.js → index-Dh_WhagG.js} +1 -1
  5. package/dist/assets/index-cH6NfVV_.js +426 -0
  6. package/dist/index.d.ts +12 -3
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +13755 -8627
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles/tailwind-preset.d.ts +5 -0
  11. package/dist/styles/tailwind-preset.js +8 -1
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +7 -7
  14. package/src/App.tsx +4 -4
  15. package/src/components/DesignPanelPromoteProvider.tsx +15 -2
  16. package/src/components/StudioRightPanel.tsx +42 -53
  17. package/src/components/editor/AnimationCard.test.tsx +134 -0
  18. package/src/components/editor/AnimationCard.tsx +19 -4
  19. package/src/components/editor/ArcPathControls.tsx +1 -0
  20. package/src/components/editor/GestureRecordControl.tsx +7 -1
  21. package/src/components/editor/GsapAnimationSection.tsx +109 -14
  22. package/src/components/editor/InspectorHeaderActions.tsx +42 -4
  23. package/src/components/editor/PropertyPanel.test.tsx +948 -0
  24. package/src/components/editor/PropertyPanel.tsx +144 -148
  25. package/src/components/editor/PropertyPanelEmptyState.test.tsx +74 -0
  26. package/src/components/editor/PropertyPanelEmptyState.tsx +179 -1
  27. package/src/components/editor/PropertyPanelFlat.tsx +580 -0
  28. package/src/components/editor/PropertyPanelFlatFooter.test.tsx +83 -0
  29. package/src/components/editor/PropertyPanelFlatFooter.tsx +73 -0
  30. package/src/components/editor/PropertyPanelFlatHeader.test.tsx +79 -0
  31. package/src/components/editor/PropertyPanelFlatHeader.tsx +112 -0
  32. package/src/components/editor/gsapAnimationCallbacks.ts +27 -0
  33. package/src/components/editor/gsapLivePreview.ts +34 -0
  34. package/src/components/editor/manualEditingAvailability.test.ts +10 -0
  35. package/src/components/editor/manualEditingAvailability.ts +9 -0
  36. package/src/components/editor/propertyPanel3dTransform.tsx +5 -0
  37. package/src/components/editor/propertyPanelColor.test.tsx +28 -0
  38. package/src/components/editor/propertyPanelColor.tsx +31 -1
  39. package/src/components/editor/propertyPanelColorGradingControls.tsx +11 -2
  40. package/src/components/editor/propertyPanelColorGradingSection.tsx +45 -365
  41. package/src/components/editor/propertyPanelColorGradingSlider.tsx +19 -5
  42. package/src/components/editor/propertyPanelFill.tsx +22 -5
  43. package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +622 -0
  44. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +570 -0
  45. package/src/components/editor/propertyPanelFlatLayoutSection.test.tsx +293 -0
  46. package/src/components/editor/propertyPanelFlatLayoutSection.tsx +383 -0
  47. package/src/components/editor/propertyPanelFlatMaskInsetRows.tsx +102 -0
  48. package/src/components/editor/propertyPanelFlatMediaSection.test.tsx +436 -0
  49. package/src/components/editor/propertyPanelFlatMediaSection.tsx +286 -0
  50. package/src/components/editor/propertyPanelFlatMotionSection.test.tsx +272 -0
  51. package/src/components/editor/propertyPanelFlatMotionSection.tsx +315 -0
  52. package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +1174 -0
  53. package/src/components/editor/propertyPanelFlatPrimitives.tsx +563 -0
  54. package/src/components/editor/propertyPanelFlatSelectRow.tsx +107 -0
  55. package/src/components/editor/propertyPanelFlatStyleHelpers.test.ts +23 -0
  56. package/src/components/editor/propertyPanelFlatStyleHelpers.ts +27 -0
  57. package/src/components/editor/propertyPanelFlatStyleSections.test.tsx +598 -0
  58. package/src/components/editor/propertyPanelFlatStyleSections.tsx +516 -0
  59. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +465 -0
  60. package/src/components/editor/propertyPanelFlatTextSection.tsx +407 -0
  61. package/src/components/editor/propertyPanelFlatTimingDerivation.test.ts +71 -0
  62. package/src/components/editor/propertyPanelFlatTimingDerivation.ts +59 -0
  63. package/src/components/editor/propertyPanelFlatToggle.test.tsx +63 -0
  64. package/src/components/editor/propertyPanelFlatToggle.tsx +53 -0
  65. package/src/components/editor/propertyPanelFont.test.tsx +30 -0
  66. package/src/components/editor/propertyPanelFont.tsx +136 -95
  67. package/src/components/editor/propertyPanelHelpers.ts +73 -0
  68. package/src/components/editor/propertyPanelInputCoverage.test.tsx +587 -0
  69. package/src/components/editor/propertyPanelMediaSection.tsx +10 -0
  70. package/src/components/editor/propertyPanelPrimitives.tsx +60 -29
  71. package/src/components/editor/propertyPanelSections.test.tsx +161 -0
  72. package/src/components/editor/propertyPanelSections.tsx +154 -90
  73. package/src/components/editor/propertyPanelStyleSections.tsx +12 -1
  74. package/src/components/editor/propertyPanelTimingSection.tsx +1 -1
  75. package/src/components/editor/propertyPanelTypes.ts +14 -1
  76. package/src/components/editor/propertyPanelValueTier.test.ts +32 -0
  77. package/src/components/editor/propertyPanelValueTier.ts +28 -0
  78. package/src/components/editor/useColorGradingController.test.ts +418 -0
  79. package/src/components/editor/useColorGradingController.ts +596 -0
  80. package/src/components/panels/VariablesPanel.tsx +4 -0
  81. package/src/components/sidebar/CompositionsTab.test.ts +28 -2
  82. package/src/components/sidebar/CompositionsTab.tsx +3 -1
  83. package/src/components/storyboard/FramePoster.tsx +24 -4
  84. package/src/components/storyboard/StoryboardFrameFocus.tsx +4 -0
  85. package/src/components/storyboard/StoryboardFrameTile.tsx +30 -1
  86. package/src/components/storyboard/StoryboardGrid.tsx +23 -1
  87. package/src/components/storyboard/StoryboardLoaded.tsx +58 -2
  88. package/src/components/storyboard/StoryboardView.tsx +9 -2
  89. package/src/components/storyboard/frameComments.test.ts +95 -0
  90. package/src/components/storyboard/frameComments.ts +115 -0
  91. package/src/components/storyboard/useFrameComments.ts +82 -0
  92. package/src/contexts/DesignPanelInputContext.test.tsx +81 -0
  93. package/src/contexts/DesignPanelInputContext.tsx +48 -0
  94. package/src/contexts/DomEditContext.tsx +4 -0
  95. package/src/contexts/FileManagerContext.tsx +3 -0
  96. package/src/contexts/PanelLayoutContext.tsx +3 -0
  97. package/src/contexts/VariablePromoteContext.test.tsx +67 -0
  98. package/src/contexts/VariablePromoteContext.tsx +10 -5
  99. package/src/hooks/domEditCommitRunner.ts +11 -0
  100. package/src/hooks/gsapScriptCommitTypes.ts +6 -4
  101. package/src/hooks/timelineEditingHelpers.ts +2 -2
  102. package/src/hooks/timelineTrackVisibility.test.ts +52 -0
  103. package/src/hooks/timelineTrackVisibility.ts +27 -10
  104. package/src/hooks/useAppHotkeys.ts +9 -0
  105. package/src/hooks/useDomEditAttributeCommits.ts +123 -1
  106. package/src/hooks/useDomEditCommits.test.tsx +41 -1
  107. package/src/hooks/useDomEditCommits.ts +31 -177
  108. package/src/hooks/useDomEditCommitsHelpers.ts +158 -0
  109. package/src/hooks/useDomEditSession.ts +18 -6
  110. package/src/hooks/useDomEditTextCommits.ts +14 -9
  111. package/src/hooks/useDomEditWiring.ts +10 -9
  112. package/src/hooks/useEditorSave.ts +1 -1
  113. package/src/hooks/useElementLifecycleOps.ts +8 -3
  114. package/src/hooks/useFileManager.projectOwnership.test.tsx +109 -0
  115. package/src/hooks/useFileManager.ts +151 -65
  116. package/src/hooks/useGsapAnimationOps.ts +7 -6
  117. package/src/hooks/useGsapKeyframeOps.ts +6 -5
  118. package/src/hooks/useGsapPropertyDebounce.ts +43 -28
  119. package/src/hooks/useGsapPropertyDebounceFlush.test.ts +35 -0
  120. package/src/hooks/useGsapScriptCommits.test.tsx +48 -2
  121. package/src/hooks/useGsapScriptCommits.ts +150 -50
  122. package/src/hooks/useGsapSelectionHandlers.test.tsx +116 -0
  123. package/src/hooks/useGsapSelectionHandlers.ts +94 -31
  124. package/src/hooks/useInspectorSplitResize.ts +51 -0
  125. package/src/hooks/usePanelLayout.test.ts +118 -0
  126. package/src/hooks/usePanelLayout.ts +24 -1
  127. package/src/hooks/usePersistentEditHistory.projectOwnership.test.tsx +60 -0
  128. package/src/hooks/usePersistentEditHistory.test.ts +49 -1
  129. package/src/hooks/usePersistentEditHistory.ts +71 -31
  130. package/src/hooks/usePreviewPersistence.ts +4 -1
  131. package/src/hooks/useProjectCompositionVariables.ts +10 -10
  132. package/src/hooks/useProjectSignaturePoll.test.tsx +135 -0
  133. package/src/hooks/useProjectSignaturePoll.ts +68 -0
  134. package/src/hooks/useRazorSplit.history.test.tsx +12 -4
  135. package/src/hooks/useRazorSplit.test.tsx +88 -0
  136. package/src/hooks/useRazorSplit.testHelpers.ts +3 -2
  137. package/src/hooks/useRazorSplit.ts +50 -16
  138. package/src/hooks/useSdkSession.lifecycle.test.tsx +137 -0
  139. package/src/hooks/useSdkSession.ts +155 -14
  140. package/src/hooks/useSlideshowPersist.ts +7 -1
  141. package/src/hooks/useStoryboard.ts +23 -4
  142. package/src/hooks/useTimelineEditing.test.tsx +4 -1
  143. package/src/hooks/useTimelineEditing.ts +18 -10
  144. package/src/hooks/useTimelineEditingTypes.ts +5 -1
  145. package/src/hooks/useTimelineGroupEditing.ts +17 -3
  146. package/src/hooks/useVariablesPersist.ts +9 -7
  147. package/src/icons/SystemIcons.tsx +2 -0
  148. package/src/styles/studio.css +22 -0
  149. package/src/styles/tailwind-preset.shared.js +7 -0
  150. package/src/utils/designInputTracking.test.ts +97 -0
  151. package/src/utils/designInputTracking.ts +80 -0
  152. package/src/utils/domEditSaveQueue.test.ts +19 -0
  153. package/src/utils/domEditSaveQueue.ts +2 -1
  154. package/src/utils/sdkCutover.gate.test.ts +7 -7
  155. package/src/utils/sdkCutover.test.ts +511 -62
  156. package/src/utils/sdkCutover.ts +181 -219
  157. package/src/utils/sdkEditTransaction.ts +293 -0
  158. package/src/utils/sdkResolverAttempts.ts +94 -0
  159. package/src/utils/sdkResolverShadow.test.ts +148 -0
  160. package/src/utils/sdkResolverShadow.ts +139 -106
  161. package/src/utils/setSlideshowManifest.test.ts +0 -9
  162. package/src/utils/setSlideshowManifest.ts +20 -27
  163. package/src/utils/studioFileHistory.test.ts +41 -0
  164. package/src/utils/studioFileHistory.ts +34 -29
  165. package/src/utils/studioFileMutationCoordinator.ts +45 -0
  166. package/src/utils/studioFileVersion.test.ts +36 -0
  167. package/src/utils/studioFileVersion.ts +23 -0
  168. package/src/utils/studioHelpers.test.ts +45 -0
  169. package/src/utils/studioHelpers.ts +45 -14
  170. package/src/utils/studioSaveDiagnostics.test.ts +18 -0
  171. package/src/utils/studioSaveDiagnostics.ts +38 -0
  172. package/dist/assets/index-B_UvTX3E.js +0 -423
  173. package/dist/assets/index-uahwWkgw.css +0 -1
@@ -1,7 +1,11 @@
1
1
  import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
2
+ import {
3
+ DesignPanelInputProvider,
4
+ useTrackDesignInput,
5
+ } from "../../contexts/DesignPanelInputContext";
2
6
  import { adjustNumericToken, FIELD, LABEL, parseNumericToken } from "./propertyPanelHelpers";
3
7
 
4
- function CommitField({
8
+ export function CommitField({
5
9
  value,
6
10
  disabled,
7
11
  liveCommit,
@@ -116,7 +120,15 @@ export function MetricField({
116
120
  tooltip?: string;
117
121
  onCommit: (nextValue: string) => void;
118
122
  }) {
123
+ const track = useTrackDesignInput();
119
124
  const scrubRef = useRef<{ startX: number; startValue: number; pointerId: number } | null>(null);
125
+ const commit = useCallback(
126
+ (nextValue: string) => {
127
+ if (nextValue !== value) track("metric", label);
128
+ onCommit(nextValue);
129
+ },
130
+ [label, onCommit, track, value],
131
+ );
120
132
 
121
133
  const handleScrubPointerDown = useCallback(
122
134
  (e: React.PointerEvent<HTMLSpanElement>) => {
@@ -134,9 +146,9 @@ export function MetricField({
134
146
  const state = scrubRef.current;
135
147
  if (!state) return;
136
148
  const delta = e.clientX - state.startX;
137
- onCommit(String(Math.round(state.startValue + delta)));
149
+ commit(String(Math.round(state.startValue + delta)));
138
150
  },
139
- [onCommit],
151
+ [commit],
140
152
  );
141
153
 
142
154
  const handleScrubPointerUp = useCallback(() => {
@@ -158,12 +170,7 @@ export function MetricField({
158
170
  <div className={FIELD} title={tooltip}>
159
171
  <div className="flex min-w-0 items-center gap-3">
160
172
  <span {...scrubProps}>{label}</span>
161
- <CommitField
162
- value={value}
163
- disabled={disabled}
164
- liveCommit={liveCommit}
165
- onCommit={onCommit}
166
- />
173
+ <CommitField value={value} disabled={disabled} liveCommit={liveCommit} onCommit={commit} />
167
174
  {suffix && <span className="flex-shrink-0 text-[10px] text-neutral-600">{suffix}</span>}
168
175
  </div>
169
176
  </div>
@@ -185,17 +192,23 @@ export function DetailField({
185
192
  disabled?: boolean;
186
193
  onCommit: (nextValue: string) => void;
187
194
  }) {
195
+ const track = useTrackDesignInput();
196
+ const commit = (nextValue: string) => {
197
+ if (nextValue !== value) track("text", label);
198
+ onCommit(nextValue);
199
+ };
188
200
  return (
189
201
  <label className="grid min-w-0 gap-1.5">
190
202
  <span className={LABEL}>{label}</span>
191
203
  <div className={FIELD}>
192
- <CommitField value={value} disabled={disabled} onCommit={onCommit} />
204
+ <CommitField value={value} disabled={disabled} onCommit={commit} />
193
205
  </div>
194
206
  </label>
195
207
  );
196
208
  }
197
209
 
198
210
  export function SliderControl({
211
+ trackName,
199
212
  value,
200
213
  min,
201
214
  max,
@@ -205,6 +218,7 @@ export function SliderControl({
205
218
  disabled,
206
219
  onCommit,
207
220
  }: {
221
+ trackName: string;
208
222
  value: number;
209
223
  min: number;
210
224
  max: number;
@@ -214,8 +228,10 @@ export function SliderControl({
214
228
  disabled?: boolean;
215
229
  onCommit: (nextValue: number) => void;
216
230
  }) {
231
+ const track = useTrackDesignInput();
217
232
  const [draft, setDraft] = useState(value);
218
233
  const commitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
234
+ const interactionChangedRef = useRef(false);
219
235
  const valueRef = useRef(value);
220
236
  valueRef.current = value;
221
237
 
@@ -231,6 +247,10 @@ export function SliderControl({
231
247
 
232
248
  const commitDraft = (nextDraft: number) => {
233
249
  if (commitTimerRef.current) clearTimeout(commitTimerRef.current);
250
+ if (interactionChangedRef.current) {
251
+ interactionChangedRef.current = false;
252
+ track("slider", trackName);
253
+ }
234
254
  if (nextDraft !== valueRef.current) onCommit(nextDraft);
235
255
  };
236
256
  const scheduleCommit = (nextDraft: number) => {
@@ -252,6 +272,7 @@ export function SliderControl({
252
272
  onChange={(e) => {
253
273
  const n = Number(e.target.value);
254
274
  setDraft(n);
275
+ interactionChangedRef.current = true;
255
276
  scheduleCommit(n);
256
277
  }}
257
278
  onMouseUp={() => commitDraft(draft)}
@@ -267,16 +288,19 @@ export function SliderControl({
267
288
  }
268
289
 
269
290
  export function SegmentedControl({
291
+ trackName,
270
292
  options,
271
293
  value,
272
294
  disabled,
273
295
  onChange,
274
296
  }: {
297
+ trackName: string;
275
298
  options: Array<{ label: string; value: string }>;
276
299
  value: string;
277
300
  disabled?: boolean;
278
301
  onChange: (nextValue: string) => void;
279
302
  }) {
303
+ const track = useTrackDesignInput();
280
304
  return (
281
305
  <div
282
306
  className="grid min-w-0 gap-[2px] rounded-md bg-panel-input p-[2px]"
@@ -287,7 +311,10 @@ export function SegmentedControl({
287
311
  key={option.value}
288
312
  type="button"
289
313
  disabled={disabled}
290
- onClick={() => onChange(option.value)}
314
+ onClick={() => {
315
+ if (option.value !== value) track("segmented", trackName);
316
+ onChange(option.value);
317
+ }}
291
318
  className={`min-w-0 truncate rounded px-2 py-[5px] text-[11px] font-medium transition-colors disabled:cursor-not-allowed ${
292
319
  option.value === value
293
320
  ? "bg-panel-hover text-white"
@@ -314,6 +341,7 @@ export function SelectField({
314
341
  options: string[];
315
342
  onChange: (nextValue: string) => void;
316
343
  }) {
344
+ const track = useTrackDesignInput();
317
345
  const renderedOptions = value && !options.includes(value) ? [value, ...options] : options;
318
346
  return (
319
347
  <label className={`${FIELD} flex items-center gap-3`}>
@@ -321,7 +349,10 @@ export function SelectField({
321
349
  <select
322
350
  value={value}
323
351
  disabled={disabled}
324
- onChange={(e) => onChange(e.target.value)}
352
+ onChange={(e) => {
353
+ track("select", label);
354
+ onChange(e.target.value);
355
+ }}
325
356
  className="min-w-0 w-full appearance-none bg-transparent text-[11px] font-medium text-neutral-100 outline-none disabled:cursor-not-allowed disabled:text-neutral-600"
326
357
  >
327
358
  {renderedOptions.map((option) => (
@@ -370,24 +401,24 @@ export function Section({
370
401
  </svg>
371
402
  );
372
403
 
404
+ const section = slugifyPanelSectionTitle(title);
373
405
  return (
374
- <section
375
- className="min-w-0 border-t border-panel-border"
376
- data-panel-section={slugifyPanelSectionTitle(title)}
377
- >
378
- <div className="flex w-full items-center gap-2 px-4 py-2.5">
379
- <button
380
- type="button"
381
- onClick={() => setCollapsed((v) => !v)}
382
- className="flex min-w-0 flex-1 items-center justify-between gap-2 text-left"
383
- >
384
- <h3 className="text-[12px] font-semibold text-panel-text-1">{title}</h3>
385
- {collapseIcon}
386
- </button>
387
- {accessory && <div className="flex flex-shrink-0 items-center">{accessory}</div>}
388
- </div>
389
- {!collapsed && <div className="px-4 pb-3">{children}</div>}
390
- </section>
406
+ <DesignPanelInputProvider section={section}>
407
+ <section className="min-w-0 border-t border-panel-border" data-panel-section={section}>
408
+ <div className="flex w-full items-center gap-2 px-4 py-2.5">
409
+ <button
410
+ type="button"
411
+ onClick={() => setCollapsed((v) => !v)}
412
+ className="flex min-w-0 flex-1 items-center justify-between gap-2 text-left"
413
+ >
414
+ <h3 className="text-[12px] font-semibold text-panel-text-1">{title}</h3>
415
+ {collapseIcon}
416
+ </button>
417
+ {accessory && <div className="flex flex-shrink-0 items-center">{accessory}</div>}
418
+ </div>
419
+ {!collapsed && <div className="px-4 pb-3">{children}</div>}
420
+ </section>
421
+ </DesignPanelInputProvider>
391
422
  );
392
423
  }
393
424
 
@@ -0,0 +1,161 @@
1
+ // @vitest-environment happy-dom
2
+
3
+ import React, { act } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { FlatTextSection } from "./propertyPanelFlatTextSection";
7
+ import type { DomEditSelection } from "./domEditingTypes";
8
+
9
+ (globalThis as unknown as { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
10
+
11
+ afterEach(() => {
12
+ document.body.innerHTML = "";
13
+ });
14
+
15
+ function makeElement(overrides: Partial<DomEditSelection> = {}): DomEditSelection {
16
+ return {
17
+ element: document.createElement("div"),
18
+ id: "mono-label",
19
+ selector: ".mono-label",
20
+ label: "Mono Label",
21
+ tagName: "div",
22
+ sourceFile: "index.html",
23
+ compositionPath: "index.html",
24
+ isCompositionHost: false,
25
+ isInsideLockedComposition: false,
26
+ boundingBox: { x: 0, y: -24, width: 257, height: 29 },
27
+ textContent: "PACKETS / FRAME",
28
+ dataAttributes: {},
29
+ inlineStyles: {},
30
+ computedStyles: {},
31
+ textFields: [
32
+ {
33
+ key: "field-0",
34
+ label: "Text",
35
+ value: "PACKETS / FRAME",
36
+ tagName: "div",
37
+ attributes: [],
38
+ inlineStyles: { "letter-spacing": "3.96px" },
39
+ computedStyles: {
40
+ "font-family": "JetBrains Mono",
41
+ "font-size": "22px",
42
+ "font-weight": "400",
43
+ "letter-spacing": "3.96px",
44
+ "line-height": "normal",
45
+ "text-align": "right",
46
+ "text-transform": "none",
47
+ "font-style": "normal",
48
+ color: "rgb(255, 176, 32)",
49
+ },
50
+ source: "self",
51
+ },
52
+ ],
53
+ capabilities: {
54
+ canSelect: true,
55
+ canEditStyles: true,
56
+ canCrop: true,
57
+ canMove: true,
58
+ canResize: true,
59
+ canApplyManualOffset: true,
60
+ canApplyManualSize: true,
61
+ canApplyManualRotation: true,
62
+ },
63
+ ...overrides,
64
+ } as DomEditSelection;
65
+ }
66
+
67
+ function renderSection(overrides: Partial<DomEditSelection> = {}) {
68
+ const host = document.createElement("div");
69
+ document.body.append(host);
70
+ const root = createRoot(host);
71
+ const element = makeElement(overrides);
72
+ act(() => {
73
+ root.render(
74
+ <FlatTextSection
75
+ element={element}
76
+ styles={{}}
77
+ fontAssets={[]}
78
+ onSetText={vi.fn()}
79
+ onSetTextFieldStyle={vi.fn()}
80
+ onAddTextField={vi.fn()}
81
+ onRemoveTextField={vi.fn()}
82
+ />,
83
+ );
84
+ });
85
+ return { host, root };
86
+ }
87
+
88
+ describe("FlatTextSection", () => {
89
+ it("renders the content block and every row from #10a", () => {
90
+ const { host, root } = renderSection();
91
+ expect(host.textContent).toContain("PACKETS / FRAME");
92
+ expect(host.textContent).toContain("Font");
93
+ expect(host.textContent).toContain("Weight");
94
+ expect(host.textContent).toContain("Letter spacing");
95
+ expect(host.textContent).toContain("Line height");
96
+ expect(host.textContent).toContain("Align");
97
+ act(() => root.unmount());
98
+ });
99
+
100
+ it("colors letter-spacing mint (explicit, differs from 0px default) with a reset button", () => {
101
+ const { host, root } = renderSection();
102
+ const resetButtons = host.querySelectorAll('[data-flat-row-reset="true"]');
103
+ expect(resetButtons.length).toBeGreaterThan(0);
104
+ act(() => root.unmount());
105
+ });
106
+
107
+ it("commits a font-weight change through onSetTextFieldStyle", () => {
108
+ const onSetTextFieldStyle = vi.fn();
109
+ const host = document.createElement("div");
110
+ document.body.append(host);
111
+ const root = createRoot(host);
112
+ const element = makeElement();
113
+ act(() => {
114
+ root.render(
115
+ <FlatTextSection
116
+ element={element}
117
+ styles={{}}
118
+ fontAssets={[]}
119
+ onSetText={vi.fn()}
120
+ onSetTextFieldStyle={onSetTextFieldStyle}
121
+ onAddTextField={vi.fn()}
122
+ onRemoveTextField={vi.fn()}
123
+ />,
124
+ );
125
+ });
126
+ const select = host.querySelector<HTMLSelectElement>("select");
127
+ if (!select) throw new Error("expected a weight <select>");
128
+ act(() => {
129
+ select.value = "700";
130
+ select.dispatchEvent(new Event("change", { bubbles: true }));
131
+ });
132
+ expect(onSetTextFieldStyle).toHaveBeenCalledWith("field-0", "font-weight", "700");
133
+ act(() => root.unmount());
134
+ });
135
+
136
+ it("renders the flat layer list (not the legacy TextSection) for a multi-field element", () => {
137
+ const { host, root } = renderSection({
138
+ textFields: [
139
+ makeElement().textFields[0],
140
+ {
141
+ key: "field-1",
142
+ label: "Text",
143
+ value: "SECOND FIELD",
144
+ tagName: "div",
145
+ attributes: [],
146
+ inlineStyles: {},
147
+ computedStyles: {},
148
+ source: "self",
149
+ },
150
+ ],
151
+ });
152
+ // Legacy TextSection's own Section wrapper (data-panel-section="text")
153
+ // must never render here — multi-field elements now go through the flat
154
+ // FlatTextLayerList + FlatTextFieldEditor path end to end, not a
155
+ // delegation to the legacy component.
156
+ expect(host.querySelector('[data-panel-section="text"]')).toBeNull();
157
+ // The flat layer list's own content must render.
158
+ expect(host.textContent).toContain("Text layers");
159
+ act(() => root.unmount());
160
+ });
161
+ });