@parhelia/core 0.2.12783 → 0.2.12786

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 (114) hide show
  1. package/dist/editor/ai/AgentImageCopyButton.d.ts +6 -0
  2. package/dist/editor/ai/AgentImageCopyButton.js +93 -0
  3. package/dist/editor/ai/AgentImageCopyButton.js.map +1 -0
  4. package/dist/editor/ai/terminal/components/EditFieldToolCallContent.d.ts +24 -0
  5. package/dist/editor/ai/terminal/components/EditFieldToolCallContent.js +97 -0
  6. package/dist/editor/ai/terminal/components/EditFieldToolCallContent.js.map +1 -0
  7. package/dist/editor/ai/terminal/components/ValueDiff.d.ts +15 -0
  8. package/dist/editor/ai/terminal/components/ValueDiff.js +21 -0
  9. package/dist/editor/ai/terminal/components/ValueDiff.js.map +1 -0
  10. package/dist/editor/ai/terminal/components/agentThinkingStyles.d.ts +1 -0
  11. package/dist/editor/ai/terminal/components/agentThinkingStyles.js +2 -0
  12. package/dist/editor/ai/terminal/components/agentThinkingStyles.js.map +1 -0
  13. package/dist/editor/ai/terminal/components/toolCallContentRenderers.d.ts +15 -0
  14. package/dist/editor/ai/terminal/components/toolCallContentRenderers.js +12 -0
  15. package/dist/editor/ai/terminal/components/toolCallContentRenderers.js.map +1 -0
  16. package/dist/editor/client/hooks/activeSessionSnapshots.d.ts +2 -0
  17. package/dist/editor/client/hooks/activeSessionSnapshots.js +43 -0
  18. package/dist/editor/client/hooks/activeSessionSnapshots.js.map +1 -0
  19. package/dist/editor/client/hooks/operationRefreshDeduplication.d.ts +22 -0
  20. package/dist/editor/client/hooks/operationRefreshDeduplication.js +45 -0
  21. package/dist/editor/client/hooks/operationRefreshDeduplication.js.map +1 -0
  22. package/dist/editor/client/hooks/socketMessageRetention.d.ts +7 -0
  23. package/dist/editor/client/hooks/socketMessageRetention.js +11 -0
  24. package/dist/editor/client/hooks/socketMessageRetention.js.map +1 -0
  25. package/dist/editor/client/hooks/useClientInfoRegistration.d.ts +13 -0
  26. package/dist/editor/client/hooks/useClientInfoRegistration.js +38 -0
  27. package/dist/editor/client/hooks/useClientInfoRegistration.js.map +1 -0
  28. package/dist/editor/client/undoTargetResolution.d.ts +28 -0
  29. package/dist/editor/client/undoTargetResolution.js +69 -0
  30. package/dist/editor/client/undoTargetResolution.js.map +1 -0
  31. package/dist/editor/field-types/singleLineTextValue.d.ts +3 -0
  32. package/dist/editor/field-types/singleLineTextValue.js +9 -0
  33. package/dist/editor/field-types/singleLineTextValue.js.map +1 -0
  34. package/dist/editor/insertOptionCompatibility.d.ts +7 -0
  35. package/dist/editor/insertOptionCompatibility.js +15 -0
  36. package/dist/editor/insertOptionCompatibility.js.map +1 -0
  37. package/dist/editor/media-selector/mediaItemDrag.d.ts +28 -0
  38. package/dist/editor/media-selector/mediaItemDrag.js +42 -0
  39. package/dist/editor/media-selector/mediaItemDrag.js.map +1 -0
  40. package/dist/editor/menubar/toolbar-sections/PreviewModeControl.d.ts +1 -0
  41. package/dist/editor/menubar/toolbar-sections/PreviewModeControl.js +14 -0
  42. package/dist/editor/menubar/toolbar-sections/PreviewModeControl.js.map +1 -0
  43. package/dist/editor/menubar/toolbar-sections/RefreshPageControl.d.ts +7 -0
  44. package/dist/editor/menubar/toolbar-sections/RefreshPageControl.js +44 -0
  45. package/dist/editor/menubar/toolbar-sections/RefreshPageControl.js.map +1 -0
  46. package/dist/editor/page-editor-chrome/InlineFieldHistoryButton.d.ts +58 -0
  47. package/dist/editor/page-editor-chrome/InlineFieldHistoryButton.js +731 -0
  48. package/dist/editor/page-editor-chrome/InlineFieldHistoryButton.js.map +1 -0
  49. package/dist/editor/page-viewer/bridgeInlineAiSelection.d.ts +17 -0
  50. package/dist/editor/page-viewer/bridgeInlineAiSelection.js +80 -0
  51. package/dist/editor/page-viewer/bridgeInlineAiSelection.js.map +1 -0
  52. package/dist/editor/page-viewer/bridgeInlineEditCommit.d.ts +18 -0
  53. package/dist/editor/page-viewer/bridgeInlineEditCommit.js +24 -0
  54. package/dist/editor/page-viewer/bridgeInlineEditCommit.js.map +1 -0
  55. package/dist/editor/page-viewer/bridgeItemChangeTarget.d.ts +9 -0
  56. package/dist/editor/page-viewer/bridgeItemChangeTarget.js +38 -0
  57. package/dist/editor/page-viewer/bridgeItemChangeTarget.js.map +1 -0
  58. package/dist/editor/page-viewer/bridgePointerDragRelease.d.ts +31 -0
  59. package/dist/editor/page-viewer/bridgePointerDragRelease.js +32 -0
  60. package/dist/editor/page-viewer/bridgePointerDragRelease.js.map +1 -0
  61. package/dist/editor/page-viewer/fullscreenPagePath.d.ts +1 -0
  62. package/dist/editor/page-viewer/fullscreenPagePath.js +11 -0
  63. package/dist/editor/page-viewer/fullscreenPagePath.js.map +1 -0
  64. package/dist/editor/page-viewer/internalLinkNavigation.d.ts +9 -0
  65. package/dist/editor/page-viewer/internalLinkNavigation.js +104 -0
  66. package/dist/editor/page-viewer/internalLinkNavigation.js.map +1 -0
  67. package/dist/editor/reviews/FeedbackFilters.d.ts +16 -0
  68. package/dist/editor/reviews/FeedbackFilters.js +45 -0
  69. package/dist/editor/reviews/FeedbackFilters.js.map +1 -0
  70. package/dist/editor/reviews/commentState.d.ts +4 -0
  71. package/dist/editor/reviews/commentState.js +15 -0
  72. package/dist/editor/reviews/commentState.js.map +1 -0
  73. package/dist/editor/settings/index/IndexGenerationsSection.d.ts +8 -0
  74. package/dist/editor/settings/index/IndexGenerationsSection.js +93 -0
  75. package/dist/editor/settings/index/IndexGenerationsSection.js.map +1 -0
  76. package/dist/editor/settings/panels/MemoriesPanel.d.ts +16 -0
  77. package/dist/editor/settings/panels/MemoriesPanel.js +292 -0
  78. package/dist/editor/settings/panels/MemoriesPanel.js.map +1 -0
  79. package/dist/editor/settings/panels/MemoryEntryDialog.d.ts +45 -0
  80. package/dist/editor/settings/panels/MemoryEntryDialog.js +167 -0
  81. package/dist/editor/settings/panels/MemoryEntryDialog.js.map +1 -0
  82. package/dist/editor/settings/panels/RenderingHostsPanel.d.ts +2 -0
  83. package/dist/editor/settings/panels/RenderingHostsPanel.js +63 -0
  84. package/dist/editor/settings/panels/RenderingHostsPanel.js.map +1 -0
  85. package/dist/editor/settings/panels/projectTemplateDuplication.d.ts +5 -0
  86. package/dist/editor/settings/panels/projectTemplateDuplication.js +57 -0
  87. package/dist/editor/settings/panels/projectTemplateDuplication.js.map +1 -0
  88. package/dist/editor/sidebar/WorkboxFilters.d.ts +11 -0
  89. package/dist/editor/sidebar/WorkboxFilters.js +61 -0
  90. package/dist/editor/sidebar/WorkboxFilters.js.map +1 -0
  91. package/dist/editor/version-diff/versionDiffGeometryKeys.d.ts +10 -0
  92. package/dist/editor/version-diff/versionDiffGeometryKeys.js +16 -0
  93. package/dist/editor/version-diff/versionDiffGeometryKeys.js.map +1 -0
  94. package/dist/revision.d.ts +2 -2
  95. package/dist/revision.js +2 -2
  96. package/package.json +1 -1
  97. package/dist/editor/ai/terminal/components/AgentEditHistoryButton.d.ts +0 -5
  98. package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js +0 -12
  99. package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js.map +0 -1
  100. package/dist/editor/ai/terminal/components/AgentThinkingDots.d.ts +0 -5
  101. package/dist/editor/ai/terminal/components/AgentThinkingDots.js +0 -9
  102. package/dist/editor/ai/terminal/components/AgentThinkingDots.js.map +0 -1
  103. package/dist/editor/reviews/CommentsAndFeedback.d.ts +0 -8
  104. package/dist/editor/reviews/CommentsAndFeedback.js +0 -17
  105. package/dist/editor/reviews/CommentsAndFeedback.js.map +0 -1
  106. package/dist/editor/reviews/CreateReviewDetailsStep.d.ts +0 -53
  107. package/dist/editor/reviews/CreateReviewDetailsStep.js +0 -77
  108. package/dist/editor/reviews/CreateReviewDetailsStep.js.map +0 -1
  109. package/dist/splash-screen/ParheliaAssistantChat.d.ts +0 -8
  110. package/dist/splash-screen/ParheliaAssistantChat.js +0 -507
  111. package/dist/splash-screen/ParheliaAssistantChat.js.map +0 -1
  112. package/dist/splash-screen/SplashScreenAgentContext.d.ts +0 -28
  113. package/dist/splash-screen/SplashScreenAgentContext.js +0 -51
  114. package/dist/splash-screen/SplashScreenAgentContext.js.map +0 -1
@@ -0,0 +1,731 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, } from "react";
3
+ import { History, Loader2, Redo2, Undo2 } from "lucide-react";
4
+ import { useEditContext, useFieldsEditContext } from "../client/editContext";
5
+ import { INLINE_FIELD_HISTORY_STEP_EVENT } from "../page-viewer/bridgeKeyboardShortcuts";
6
+ import { formatDateOnly, normalizeMarkerId } from "../utils";
7
+ import { collapseFieldHistoryEntries, FieldHistory, shortenUserName, } from "../FieldHistory";
8
+ import { getFieldHistory } from "../services/editService";
9
+ import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
10
+ import { Tooltip, TooltipContent, TooltipTrigger, } from "../../components/ui/tooltip";
11
+ import { measureBridgeFieldRect, rectEquals, } from "./overlay/geometry";
12
+ import { useOverlayMeasurement } from "./overlay/useOverlayMeasurement";
13
+ export const INLINE_FIELD_HISTORY_BUTTON_GAP = 6;
14
+ export const INLINE_FIELD_HISTORY_BUTTON_SIZE = 24;
15
+ const QUICK_HISTORY_SESSION_TTL_MS = 15_000;
16
+ // Stepping patches the canvas immediately and coalesces the save, mirroring the
17
+ // trailing debounce typing already uses. Without it every step blocks the arrows
18
+ // on a server round-trip.
19
+ const QUICK_HISTORY_APPLY_DEBOUNCE_MS = 350;
20
+ // Long enough for the trailing typing save (its own debounce plus the server
21
+ // round-trip) to have appended the new history entry before we refetch.
22
+ const QUICK_HISTORY_REFRESH_DELAY_MS = 2_500;
23
+ /** Hovering the pill refetches at most this often; fresher data is kept. */
24
+ const QUICK_HISTORY_HOVER_STALE_MS = 10_000;
25
+ const QUICK_HISTORY_LABEL_MAX_WIDTH = 170;
26
+ const INLINE_FIELD_HISTORY_LABEL_GAP = 6;
27
+ /** The pill stays out of the way until the caret has been still this long. */
28
+ export const INLINE_FIELD_HISTORY_IDLE_DELAY_MS = 1_000;
29
+ /**
30
+ * Applying a step patches the canvas, which echoes back as a value change. That
31
+ * is our own doing, not the user typing, so it must not restart the idle wait
32
+ * and hide the pill mid-step. A refresh-driven re-render can trail the patch, so
33
+ * the window covers more than the echo itself.
34
+ */
35
+ const INLINE_FIELD_HISTORY_SELF_CHANGE_WINDOW_MS = 2_500;
36
+ const quickHistorySessions = new Map();
37
+ /**
38
+ * The step position survives the pill unmounting (clicking away and back) for
39
+ * {@link QUICK_HISTORY_SESSION_TTL_MS}. Tests reset it so one case cannot leave
40
+ * the next one mid-history.
41
+ */
42
+ export function clearInlineFieldHistoryQuickSessions() {
43
+ quickHistorySessions.clear();
44
+ }
45
+ function getQuickHistorySession(key) {
46
+ const session = quickHistorySessions.get(key);
47
+ if (!session)
48
+ return undefined;
49
+ if (session.expiresAt <= Date.now()) {
50
+ quickHistorySessions.delete(key);
51
+ return undefined;
52
+ }
53
+ return session;
54
+ }
55
+ /**
56
+ * Places the pill for a text field above the start of the field's block,
57
+ * left-aligned with it. The block's top-left corner does not move as values
58
+ * change, so stepping through history never relocates the buttons under the
59
+ * pointer (a caret- or line-relative anchor did exactly that). When the block
60
+ * is at the very top of the visible canvas the pill flips under the block —
61
+ * still outside the text. Media fields anchor to the same top-left corner but
62
+ * sit just inside their bounds: the picture chrome owns the space above the
63
+ * image, and the corner keeps the trigger in the same fixed spot as on text
64
+ * fields.
65
+ *
66
+ * The overlay is clipped by the panel hosting the canvas, so a pill that would
67
+ * reach past the visible width drops its label rather than disappearing.
68
+ */
69
+ export function calculateInlineFieldHistoryButtonPosition(anchorRect, options) {
70
+ const { anchor = "block", controlsWidth = 0, labelWidth = 0, viewportLeft = 0, viewportTop = 0, viewportWidth = 0, viewportHeight = 0, margin = 4, } = options ?? {};
71
+ const gap = INLINE_FIELD_HISTORY_BUTTON_GAP;
72
+ const labelledWidth = controlsWidth +
73
+ (labelWidth > 0 ? labelWidth + INLINE_FIELD_HISTORY_LABEL_GAP : 0);
74
+ const rightEdge = viewportLeft + viewportWidth - margin;
75
+ let left = anchor === "corner" ? anchorRect.x + gap : anchorRect.x;
76
+ let showLabel = true;
77
+ if (viewportWidth > 0 && controlsWidth > 0) {
78
+ showLabel = left + labelledWidth <= rightEdge;
79
+ const usedWidth = showLabel ? labelledWidth : controlsWidth;
80
+ left = Math.max(viewportLeft + margin, Math.min(left, rightEdge - usedWidth));
81
+ }
82
+ if (anchor === "corner") {
83
+ return {
84
+ left,
85
+ top: anchorRect.y + gap,
86
+ showLabel,
87
+ placement: "corner",
88
+ };
89
+ }
90
+ const aboveTop = anchorRect.y - INLINE_FIELD_HISTORY_BUTTON_SIZE - gap;
91
+ const fitsAbove = viewportHeight <= 0 || aboveTop >= viewportTop + margin;
92
+ return {
93
+ left,
94
+ top: fitsAbove ? aboveTop : anchorRect.y + anchorRect.height + gap,
95
+ showLabel,
96
+ placement: fitsAbove ? "above" : "below",
97
+ };
98
+ }
99
+ export function formatInlineHistoryEntryLabel(entry, index, total, now = Date.now()) {
100
+ if (!entry)
101
+ return "";
102
+ const who = entry.isBaseline
103
+ ? "previous value"
104
+ : (entry.agentId ?? shortenUserName(entry.user));
105
+ const when = formatRelativeHistoryTime(entry.date, now);
106
+ const position = total > 1 ? `${index + 1}/${total}` : "";
107
+ return [when, who, position].filter(Boolean).join(" · ");
108
+ }
109
+ function formatRelativeHistoryTime(date, now) {
110
+ if (!date)
111
+ return "";
112
+ const timestamp = new Date(date).getTime();
113
+ if (!Number.isFinite(timestamp))
114
+ return "";
115
+ const elapsedMs = now - timestamp;
116
+ if (elapsedMs < 45_000)
117
+ return "just now";
118
+ if (elapsedMs >= 6 * 86_400_000)
119
+ return formatDateOnly(new Date(timestamp));
120
+ const units = [
121
+ ["day", 86_400_000],
122
+ ["hour", 3_600_000],
123
+ ["minute", 60_000],
124
+ ];
125
+ try {
126
+ const formatter = new Intl.RelativeTimeFormat(undefined, {
127
+ numeric: "auto",
128
+ style: "narrow",
129
+ });
130
+ for (const [unit, unitMs] of units) {
131
+ if (elapsedMs >= unitMs) {
132
+ return formatter.format(-Math.round(elapsedMs / unitMs), unit);
133
+ }
134
+ }
135
+ }
136
+ catch {
137
+ // Fall through to the absolute date below.
138
+ }
139
+ return formatDateOnly(new Date(timestamp));
140
+ }
141
+ function idsMatch(left, right) {
142
+ if (!left || !right)
143
+ return false;
144
+ return normalizeMarkerId(left) === normalizeMarkerId(right);
145
+ }
146
+ function toItemDescriptor(item, fallback) {
147
+ const id = item?.id ?? fallback?.id;
148
+ const language = item?.language ?? fallback?.language;
149
+ const version = item?.version ?? fallback?.version;
150
+ if (!id || !language || version == null)
151
+ return undefined;
152
+ return {
153
+ id,
154
+ language,
155
+ version,
156
+ ...(item?.database || fallback?.database
157
+ ? { database: item?.database ?? fallback?.database }
158
+ : {}),
159
+ };
160
+ }
161
+ export function resolveInlineFieldDescriptor(pageViewContext, focusedField) {
162
+ const activeField = pageViewContext.bridgeSelection?.activeField;
163
+ if (!activeField?.inlineEditing || !activeField.fieldId)
164
+ return undefined;
165
+ const structureFields = pageViewContext.bridgeStructure?.fields ?? [];
166
+ const structureField = structureFields.find((field) => !!activeField.elementKey && field.elementKey === activeField.elementKey) ??
167
+ structureFields.find((field) => idsMatch(field.fieldId, activeField.fieldId) &&
168
+ (!activeField.item?.id ||
169
+ idsMatch(field.item?.id, activeField.item.id)));
170
+ const focusedFieldMatches = focusedField &&
171
+ idsMatch(focusedField.fieldId, activeField.fieldId) &&
172
+ (!activeField.item?.id ||
173
+ idsMatch(focusedField.item.id, activeField.item.id))
174
+ ? focusedField
175
+ : undefined;
176
+ const pageItem = pageViewContext.page?.item ?? pageViewContext.pageItemDescriptor;
177
+ const item = toItemDescriptor(structureField?.item ?? activeField.item, focusedFieldMatches?.item ?? pageItem);
178
+ if (!item)
179
+ return undefined;
180
+ return {
181
+ fieldId: structureField?.fieldId ?? activeField.fieldId,
182
+ item,
183
+ };
184
+ }
185
+ function fieldDescriptorKey(field) {
186
+ if (!field)
187
+ return "";
188
+ return [
189
+ normalizeMarkerId(field.fieldId),
190
+ normalizeMarkerId(field.item.id),
191
+ field.item.language,
192
+ field.item.version,
193
+ field.item.database ?? "",
194
+ ].join(":");
195
+ }
196
+ function anchorMeasurementsEqual(previous, next) {
197
+ return (rectEquals(previous.field, next.field) &&
198
+ previous.viewportLeft === next.viewportLeft &&
199
+ previous.viewportTop === next.viewportTop &&
200
+ previous.viewportWidth === next.viewportWidth &&
201
+ previous.viewportHeight === next.viewportHeight);
202
+ }
203
+ export function InlineFieldHistoryButton({ pageViewContext, field, fieldRect: suppliedFieldRect, testId = "inline-field-history-button", onOpenChange, onPointerPresenceChange, }) {
204
+ const editContext = useEditContext();
205
+ const fieldsContext = useFieldsEditContext();
206
+ const [open, setOpen] = useState(false);
207
+ // The pill idles as a single subtle trigger; the step controls and label
208
+ // only unfold once the user asks for them. The choice is remembered per
209
+ // field while it stays active.
210
+ const [expanded, setExpanded] = useState(false);
211
+ // pageViewContext keeps a stable identity and is mutated in place, so a
212
+ // memoized ancestor can skip re-rendering this overlay when only the
213
+ // selection changed — the pill then never learns a field was focused and
214
+ // appears unreliably (or not at all). Watch the bridge's own messages and
215
+ // re-render directly; the freshly mutated context is read on that render.
216
+ const [, bumpBridgeRevision] = useReducer((n) => n + 1, 0);
217
+ useEffect(() => {
218
+ if (typeof window === "undefined")
219
+ return;
220
+ const handleBridgeMessage = (event) => {
221
+ const name = event.data?.name;
222
+ if (name === "selectionChanged" ||
223
+ name === "structureUpdated" ||
224
+ name === "inlineEditEnded") {
225
+ bumpBridgeRevision();
226
+ }
227
+ };
228
+ window.addEventListener("message", handleBridgeMessage);
229
+ return () => window.removeEventListener("message", handleBridgeMessage);
230
+ }, []);
231
+ const [resolvedField, setResolvedField] = useState();
232
+ const [historyField, setHistoryField] = useState();
233
+ const [historyDescriptor, setHistoryDescriptor] = useState();
234
+ const [selectedHistoryEntry, setSelectedHistoryEntry] = useState();
235
+ const [quickHistory, setQuickHistory] = useState();
236
+ const [quickHistoryLoading, setQuickHistoryLoading] = useState(false);
237
+ const [quickHistoryIndex, setQuickHistoryIndex] = useState(0);
238
+ const [quickHistoryReloadToken, setQuickHistoryReloadToken] = useState(0);
239
+ const [controlsWidth, setControlsWidth] = useState(0);
240
+ const [labelWidth, setLabelWidth] = useState(0);
241
+ const pillRef = useRef(null);
242
+ const controlsRef = useRef(null);
243
+ const labelRef = useRef(null);
244
+ const inlineDescriptor = useMemo(() => resolveInlineFieldDescriptor(pageViewContext, fieldsContext?.focusedField), [
245
+ fieldsContext?.focusedField,
246
+ pageViewContext.bridgeSelection,
247
+ pageViewContext.bridgeStructure,
248
+ pageViewContext.page,
249
+ pageViewContext.pageItemDescriptor,
250
+ ]);
251
+ const activeDescriptor = field?.descriptor ?? inlineDescriptor;
252
+ const activeDescriptorKey = fieldDescriptorKey(activeDescriptor);
253
+ const activeFieldId = activeDescriptor?.fieldId;
254
+ const activeItemId = activeDescriptor?.item.id;
255
+ const activeItemLanguage = activeDescriptor?.item.language;
256
+ const activeItemVersion = activeDescriptor?.item.version;
257
+ const activeItemDatabase = activeDescriptor?.item.database;
258
+ const targetDescriptor = open ? historyDescriptor : activeDescriptor;
259
+ const targetDescriptorKey = fieldDescriptorKey(targetDescriptor);
260
+ // The edit context and the resolved descriptors are rebuilt on virtually every
261
+ // editor state change (each save bumps the items-repository revision, each
262
+ // caret move produces a fresh bridge selection). Depending on their identity
263
+ // re-ran the fetches below on every keystroke. Read them through refs and key
264
+ // the effects on the stable descriptor key instead.
265
+ const editContextRef = useRef(editContext);
266
+ editContextRef.current = editContext;
267
+ const targetDescriptorRef = useRef(targetDescriptor);
268
+ targetDescriptorRef.current = targetDescriptor;
269
+ const hasEditContext = !!editContext;
270
+ useEffect(() => {
271
+ if (!hasEditContext ||
272
+ !activeDescriptorKey ||
273
+ !activeFieldId ||
274
+ !activeItemId ||
275
+ !activeItemLanguage ||
276
+ activeItemVersion == null) {
277
+ setQuickHistory(undefined);
278
+ setQuickHistoryLoading(false);
279
+ setQuickHistoryIndex(0);
280
+ return;
281
+ }
282
+ let cancelled = false;
283
+ const existingSession = getQuickHistorySession(activeDescriptorKey);
284
+ if (existingSession) {
285
+ setQuickHistory({
286
+ key: activeDescriptorKey,
287
+ entries: existingSession.entries,
288
+ fetchedAt: Date.now(),
289
+ });
290
+ setQuickHistoryIndex(existingSession.index);
291
+ setQuickHistoryLoading(false);
292
+ return;
293
+ }
294
+ setQuickHistory((current) => current?.key === activeDescriptorKey ? current : undefined);
295
+ setQuickHistoryLoading(true);
296
+ void getFieldHistory({
297
+ id: activeItemId,
298
+ language: activeItemLanguage,
299
+ version: activeItemVersion,
300
+ ...(activeItemDatabase ? { database: activeItemDatabase } : {}),
301
+ }, activeFieldId)
302
+ .then((result) => {
303
+ if (cancelled)
304
+ return;
305
+ setQuickHistory({
306
+ key: activeDescriptorKey,
307
+ entries: collapseFieldHistoryEntries(result.data ?? []),
308
+ fetchedAt: Date.now(),
309
+ });
310
+ setQuickHistoryLoading(false);
311
+ setQuickHistoryIndex(0);
312
+ })
313
+ .catch(() => {
314
+ if (cancelled)
315
+ return;
316
+ setQuickHistory({
317
+ key: activeDescriptorKey,
318
+ entries: [],
319
+ fetchedAt: Date.now(),
320
+ });
321
+ setQuickHistoryLoading(false);
322
+ });
323
+ return () => {
324
+ cancelled = true;
325
+ };
326
+ }, [
327
+ activeDescriptorKey,
328
+ activeFieldId,
329
+ activeItemDatabase,
330
+ activeItemId,
331
+ activeItemLanguage,
332
+ activeItemVersion,
333
+ hasEditContext,
334
+ quickHistoryReloadToken,
335
+ ]);
336
+ useEffect(() => {
337
+ const currentEditContext = editContextRef.current;
338
+ const descriptor = targetDescriptorRef.current;
339
+ if (!currentEditContext || !descriptor) {
340
+ if (!descriptor)
341
+ setResolvedField(undefined);
342
+ return;
343
+ }
344
+ if (field && fieldDescriptorKey(field.descriptor) === targetDescriptorKey) {
345
+ setResolvedField({ key: targetDescriptorKey, field });
346
+ return;
347
+ }
348
+ let cancelled = false;
349
+ setResolvedField((current) => current?.key === targetDescriptorKey ? current : undefined);
350
+ void currentEditContext.itemsRepository
351
+ .getField(descriptor)
352
+ .then((resolved) => {
353
+ if (!cancelled && resolved) {
354
+ setResolvedField({ key: targetDescriptorKey, field: resolved });
355
+ }
356
+ });
357
+ return () => {
358
+ cancelled = true;
359
+ };
360
+ }, [hasEditContext, field, targetDescriptorKey]);
361
+ const activeField = resolvedField?.key === activeDescriptorKey
362
+ ? resolvedField.field
363
+ : undefined;
364
+ const displayedField = open && resolvedField?.key === targetDescriptorKey
365
+ ? resolvedField.field
366
+ : open
367
+ ? historyField
368
+ : activeField;
369
+ const measuredDescriptor = targetDescriptor;
370
+ const bridgeActiveField = pageViewContext.bridgeSelection?.activeField;
371
+ const bridgeElementKey = activeDescriptor &&
372
+ idsMatch(bridgeActiveField?.fieldId, activeDescriptor.fieldId) &&
373
+ (!bridgeActiveField?.item?.id ||
374
+ idsMatch(bridgeActiveField.item.id, activeDescriptor.item.id))
375
+ ? bridgeActiveField?.elementKey
376
+ : undefined;
377
+ const liveFieldValue = bridgeElementKey
378
+ ? bridgeActiveField?.value
379
+ : undefined;
380
+ // Typing keeps the pill away and schedules one history refresh for when the
381
+ // value settles (a changed value means a save will append a history entry).
382
+ // Only genuine value changes count as activity: caret moves and bridge
383
+ // re-publishes with the same value used to hide the pill for 2s around every
384
+ // click, and transient re-publishes without a matching field (canvas
385
+ // refreshes) made it flap. The timers live in refs so a dependency change
386
+ // that ends up a no-op cannot cancel a pending appearance.
387
+ const [idle, setIdle] = useState(false);
388
+ const idleRef = useRef(idle);
389
+ idleRef.current = idle;
390
+ const selfChangeUntilRef = useRef(0);
391
+ const idleTimerRef = useRef(undefined);
392
+ const reloadTimerRef = useRef(undefined);
393
+ const lastLiveValueRef = useRef(undefined);
394
+ useEffect(() => {
395
+ const restartIdleWait = () => {
396
+ if (idleTimerRef.current)
397
+ clearTimeout(idleTimerRef.current);
398
+ setIdle(false);
399
+ idleTimerRef.current = setTimeout(() => setIdle(true), INLINE_FIELD_HISTORY_IDLE_DELAY_MS);
400
+ };
401
+ if (!activeDescriptorKey) {
402
+ lastLiveValueRef.current = undefined;
403
+ if (idleTimerRef.current)
404
+ clearTimeout(idleTimerRef.current);
405
+ if (reloadTimerRef.current)
406
+ clearTimeout(reloadTimerRef.current);
407
+ setIdle(false);
408
+ return;
409
+ }
410
+ const last = lastLiveValueRef.current;
411
+ if (last?.key !== activeDescriptorKey) {
412
+ lastLiveValueRef.current = {
413
+ key: activeDescriptorKey,
414
+ ...(typeof liveFieldValue === "string"
415
+ ? { value: liveFieldValue }
416
+ : {}),
417
+ };
418
+ if (reloadTimerRef.current)
419
+ clearTimeout(reloadTimerRef.current);
420
+ restartIdleWait();
421
+ return;
422
+ }
423
+ // A vanished value (the bridge re-publishing without the field, e.g.
424
+ // during a canvas refresh) is not activity; keep the last known value so
425
+ // its return with the same text stays a no-op.
426
+ if (typeof liveFieldValue !== "string")
427
+ return;
428
+ const valueChanged = last.value !== liveFieldValue;
429
+ last.value = liveFieldValue;
430
+ if (!valueChanged)
431
+ return;
432
+ // An in-progress stepping session short-circuits the refetch, so this
433
+ // cannot move the index under the user.
434
+ if (reloadTimerRef.current)
435
+ clearTimeout(reloadTimerRef.current);
436
+ reloadTimerRef.current = setTimeout(() => {
437
+ reloadTimerRef.current = undefined;
438
+ setQuickHistoryReloadToken((token) => token + 1);
439
+ }, QUICK_HISTORY_REFRESH_DELAY_MS);
440
+ // Our own step echoing back from the canvas is not the user typing; it
441
+ // must not hide the pill mid-step.
442
+ if (Date.now() < selfChangeUntilRef.current && idleRef.current)
443
+ return;
444
+ restartIdleWait();
445
+ }, [activeDescriptorKey, liveFieldValue]);
446
+ useEffect(() => () => {
447
+ if (idleTimerRef.current)
448
+ clearTimeout(idleTimerRef.current);
449
+ if (reloadTimerRef.current)
450
+ clearTimeout(reloadTimerRef.current);
451
+ }, []);
452
+ useEffect(() => {
453
+ setExpanded(false);
454
+ }, [activeDescriptorKey]);
455
+ // Text fields anchor to the field block's own bounds — its top-left corner
456
+ // stays put while values change, so stepping never moves the buttons under
457
+ // the pointer. Media fields keep the supplied bounds.
458
+ const anchorsToText = !suppliedFieldRect && !!measuredDescriptor;
459
+ const measurement = useOverlayMeasurement((metrics) => {
460
+ const fieldRect = suppliedFieldRect ??
461
+ (measuredDescriptor
462
+ ? measureBridgeFieldRect(pageViewContext.bridgeGeometry, pageViewContext.bridgeStructure, measuredDescriptor, metrics)
463
+ : null);
464
+ return {
465
+ field: fieldRect,
466
+ viewportLeft: metrics.scrollLeft,
467
+ viewportTop: metrics.scrollTop,
468
+ viewportWidth: metrics.viewportWidth,
469
+ viewportHeight: metrics.viewportHeight,
470
+ };
471
+ }, [
472
+ measuredDescriptor,
473
+ pageViewContext.bridgeGeometry,
474
+ pageViewContext.bridgeStructure,
475
+ suppliedFieldRect,
476
+ ], {
477
+ initialValue: {
478
+ field: null,
479
+ viewportLeft: 0,
480
+ viewportTop: 0,
481
+ viewportWidth: 0,
482
+ viewportHeight: 0,
483
+ },
484
+ equals: anchorMeasurementsEqual,
485
+ interests: ["viewport", "layout", "text", "adornment-data"],
486
+ });
487
+ const quickHistoryEntries = quickHistory?.key === activeDescriptorKey ? quickHistory.entries : [];
488
+ const boundedIndex = Math.max(0, Math.min(quickHistoryIndex, quickHistoryEntries.length - 1));
489
+ const pendingApplyRef = useRef(undefined);
490
+ const applyTimerRef = useRef(undefined);
491
+ const flushPendingApply = async () => {
492
+ const pending = pendingApplyRef.current;
493
+ if (applyTimerRef.current) {
494
+ clearTimeout(applyTimerRef.current);
495
+ applyTimerRef.current = undefined;
496
+ }
497
+ if (!pending)
498
+ return;
499
+ pendingApplyRef.current = undefined;
500
+ const currentEditContext = editContextRef.current;
501
+ if (!currentEditContext)
502
+ return;
503
+ await currentEditContext.operations.editField({
504
+ field: pending.descriptor,
505
+ rawValue: pending.entry.rawValue ?? null,
506
+ // A canvas that was already patched shows the stepped value; forcing a
507
+ // refresh here tears down the inline-edit session (dropping selection
508
+ // and the pill with it). Only media fields, which take no patch, need
509
+ // the re-render to show the restored value.
510
+ refresh: pending.refresh,
511
+ });
512
+ };
513
+ const flushPendingApplyRef = useRef(flushPendingApply);
514
+ flushPendingApplyRef.current = flushPendingApply;
515
+ // A step that is still coalescing when the field is left (or the editor
516
+ // unmounts) must not be dropped — flush it instead.
517
+ useEffect(() => () => {
518
+ void flushPendingApplyRef.current();
519
+ }, [activeDescriptorKey]);
520
+ const applyQuickHistoryIndex = (index) => {
521
+ const nextIndex = Math.max(0, Math.min(index, quickHistoryEntries.length - 1));
522
+ const nextEntry = quickHistoryEntries[nextIndex];
523
+ if (!nextEntry || !activeDescriptor || !editContext)
524
+ return;
525
+ setQuickHistoryIndex(nextIndex);
526
+ setSelectedHistoryEntry(nextEntry);
527
+ selfChangeUntilRef.current =
528
+ Date.now() + INLINE_FIELD_HISTORY_SELF_CHANGE_WINDOW_MS;
529
+ quickHistorySessions.set(activeDescriptorKey, {
530
+ entries: quickHistoryEntries,
531
+ index: nextIndex,
532
+ expiresAt: Date.now() + QUICK_HISTORY_SESSION_TTL_MS,
533
+ });
534
+ const displayField = activeField ?? field;
535
+ const structureField = pageViewContext.bridgeStructure?.fields.find((candidate) => idsMatch(candidate.fieldId, activeDescriptor.fieldId) &&
536
+ (!candidate.item?.id ||
537
+ idsMatch(candidate.item.id, activeDescriptor.item.id)));
538
+ const elementKey = bridgeElementKey ?? structureField?.elementKey;
539
+ const fieldType = displayField?.type.toLowerCase();
540
+ // Show the value straight away; the save below is coalesced so holding the
541
+ // arrow down does not queue one round-trip per step.
542
+ const canPatchCanvas = !!elementKey && fieldType !== "image" && fieldType !== "picture";
543
+ if (canPatchCanvas) {
544
+ pageViewContext.requestBridgeFieldPatch?.({
545
+ elementKey,
546
+ fieldId: activeDescriptor.fieldId,
547
+ value: nextEntry.rawValue ?? "",
548
+ isRichText: structureField?.isRichText === true || fieldType === "rich text",
549
+ source: "reset",
550
+ });
551
+ }
552
+ pendingApplyRef.current = {
553
+ descriptor: activeDescriptor,
554
+ entry: nextEntry,
555
+ refresh: canPatchCanvas ? "none" : "immediate",
556
+ };
557
+ if (applyTimerRef.current)
558
+ clearTimeout(applyTimerRef.current);
559
+ applyTimerRef.current = setTimeout(() => {
560
+ applyTimerRef.current = undefined;
561
+ flushPendingApplyRef.current();
562
+ }, QUICK_HISTORY_APPLY_DEBOUNCE_MS);
563
+ };
564
+ const applyQuickHistoryIndexRef = useRef(applyQuickHistoryIndex);
565
+ applyQuickHistoryIndexRef.current = applyQuickHistoryIndex;
566
+ const canStepOlder = boundedIndex < quickHistoryEntries.length - 1;
567
+ const canStepNewer = boundedIndex > 0;
568
+ const stepEnabled = !open && quickHistoryEntries.length > 1;
569
+ const canStepOlderRef = useRef(canStepOlder);
570
+ canStepOlderRef.current = canStepOlder && stepEnabled;
571
+ const canStepNewerRef = useRef(canStepNewer);
572
+ canStepNewerRef.current = canStepNewer && stepEnabled;
573
+ const boundedIndexRef = useRef(boundedIndex);
574
+ boundedIndexRef.current = boundedIndex;
575
+ // Alt+Up / Alt+Down while the caret is in the field. Canvas keystrokes reach
576
+ // the parent as a bridge interaction that PageViewerFrame republishes as this
577
+ // event, so one listener covers both the canvas and the side panel.
578
+ useEffect(() => {
579
+ if (typeof document === "undefined")
580
+ return;
581
+ const handleStep = (event) => {
582
+ const direction = event.detail
583
+ ?.direction;
584
+ if (direction === "older" && canStepOlderRef.current) {
585
+ applyQuickHistoryIndexRef.current(boundedIndexRef.current + 1);
586
+ }
587
+ else if (direction === "newer" && canStepNewerRef.current) {
588
+ applyQuickHistoryIndexRef.current(boundedIndexRef.current - 1);
589
+ }
590
+ else {
591
+ return;
592
+ }
593
+ // Stepping from the keyboard unfolds the controls so the position label
594
+ // gives feedback on where in the history the field is.
595
+ setExpanded(true);
596
+ };
597
+ document.addEventListener(INLINE_FIELD_HISTORY_STEP_EVENT, handleStep);
598
+ return () => document.removeEventListener(INLINE_FIELD_HISTORY_STEP_EVENT, handleStep);
599
+ }, []);
600
+ const anchorRect = measurement.field;
601
+ // Both widths are remembered rather than read live: the label is measured
602
+ // while it is shown and its remembered width decides whether it may come back,
603
+ // so hiding it cannot make it immediately fit and flap. Measuring after every
604
+ // render is deliberate — the label's text (and width) changes as you step —
605
+ // and the thresholds below stop the state updates from chaining.
606
+ // eslint-disable-next-line react-hooks/exhaustive-deps
607
+ useLayoutEffect(() => {
608
+ const controls = controlsRef.current?.offsetWidth ?? 0;
609
+ if (controls > 0 && Math.abs(controls - controlsWidth) > 1) {
610
+ setControlsWidth(controls);
611
+ }
612
+ const label = labelRef.current?.offsetWidth ?? 0;
613
+ if (label > 0 && Math.abs(label - labelWidth) > 1) {
614
+ setLabelWidth(label);
615
+ }
616
+ });
617
+ if (!editContext ||
618
+ editContext.readonly ||
619
+ editContext.mode !== "edit" ||
620
+ !measuredDescriptor ||
621
+ !anchorRect ||
622
+ // Text fields hold the pill back until typing settles; an open popover
623
+ // outlives the idle window it was opened from. Media fields are not typed
624
+ // into and their pill is already gated by the hover overlay that owns it.
625
+ (anchorsToText && !idle && !open)) {
626
+ return null;
627
+ }
628
+ // Media pills collapse too: same subtle trigger, same unfold-on-click.
629
+ const collapsedTrigger = !expanded && !open;
630
+ const position = calculateInlineFieldHistoryButtonPosition(anchorRect, {
631
+ anchor: anchorsToText ? "block" : "corner",
632
+ controlsWidth,
633
+ labelWidth: collapsedTrigger ? 0 : labelWidth,
634
+ viewportLeft: measurement.viewportLeft,
635
+ viewportTop: measurement.viewportTop,
636
+ viewportWidth: measurement.viewportWidth,
637
+ viewportHeight: measurement.viewportHeight,
638
+ });
639
+ const entryLabel = formatInlineHistoryEntryLabel(quickHistoryEntries[boundedIndex], boundedIndex, quickHistoryEntries.length);
640
+ const showStepControls = !open && !collapsedTrigger && quickHistoryEntries.length > 0;
641
+ const showLabel = showStepControls &&
642
+ position.showLabel &&
643
+ (!!entryLabel || quickHistoryLoading);
644
+ const closeHistory = () => {
645
+ setOpen(false);
646
+ setSelectedHistoryEntry(undefined);
647
+ setHistoryField(undefined);
648
+ setHistoryDescriptor(undefined);
649
+ onOpenChange?.(false);
650
+ };
651
+ return (_jsxs("div", { ref: pillRef, className: "animate-in fade-in pointer-events-auto absolute flex items-center gap-1.5 duration-200", style: {
652
+ left: position.left,
653
+ top: position.top,
654
+ minHeight: INLINE_FIELD_HISTORY_BUTTON_SIZE,
655
+ zIndex: 900,
656
+ }, "data-field-history-trigger": "true", "data-testid": `${testId}-container`, onMouseDown: (event) => {
657
+ // The pill floats over the live canvas; a click that slips past a
658
+ // button must neither blur the iframe's edit session nor fall
659
+ // through as a canvas interaction.
660
+ event.preventDefault();
661
+ event.stopPropagation();
662
+ }, onPointerEnter: () => {
663
+ onPointerPresenceChange?.(true);
664
+ // Refresh stale data on approach, but never while a fetch is running
665
+ // and never for fresh data: an unconditional refetch here looped — the
666
+ // loading pill shrank out from under the cursor and re-entered when
667
+ // the response grew it back.
668
+ if (!getQuickHistorySession(activeDescriptorKey) &&
669
+ !quickHistoryLoading &&
670
+ quickHistory?.key === activeDescriptorKey &&
671
+ Date.now() - quickHistory.fetchedAt > QUICK_HISTORY_HOVER_STALE_MS) {
672
+ setQuickHistoryReloadToken((token) => token + 1);
673
+ }
674
+ }, onPointerLeave: () => onPointerPresenceChange?.(false), onMouseEnter: () => onPointerPresenceChange?.(true), onMouseLeave: () => onPointerPresenceChange?.(false), children: [_jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute -inset-2" }), collapsedTrigger ? (_jsx("div", { ref: controlsRef, className: "relative flex h-6 items-center", children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "border-border-default/60 text-neutral-grey-100 hover:border-border-default flex h-6 w-6 cursor-pointer items-center justify-center rounded-[4px] border bg-white/70 opacity-70 hover:bg-white hover:opacity-100 hover:shadow-sm", "aria-label": "Field History", "aria-expanded": false, "data-field-history-trigger": "true", "data-testid": testId, onMouseDown: (event) => {
675
+ // Keep the iframe's active field alive while unfolding.
676
+ event.preventDefault();
677
+ event.stopPropagation();
678
+ }, onClick: (event) => {
679
+ event.preventDefault();
680
+ event.stopPropagation();
681
+ setExpanded(true);
682
+ // The pill can mount while an edit is still being saved. Its
683
+ // eager request then contains no previous value, and the
684
+ // value-change effect cannot detect that save if the edited
685
+ // value was already present on mount. Opening the controls is
686
+ // an explicit request for current history, so refresh unless
687
+ // the user already has a quick-navigation session whose
688
+ // position must be preserved.
689
+ if (!getQuickHistorySession(activeDescriptorKey)) {
690
+ setQuickHistoryReloadToken((token) => token + 1);
691
+ }
692
+ }, children: _jsx(History, { "aria-hidden": "true", strokeWidth: 1, className: "h-3.5 w-3.5" }) }) }), _jsx(TooltipContent, { side: "top", children: "Field History" })] }) })) : (_jsxs("div", { ref: controlsRef, className: "border-border-default relative flex h-6 items-center overflow-hidden rounded-[4px] border bg-white shadow-sm", children: [showStepControls && (_jsxs(_Fragment, { children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "hover:bg-neutral-grey-5 text-neutral-grey-100 flex h-full w-[22px] cursor-pointer items-center justify-center disabled:cursor-not-allowed disabled:opacity-35", "aria-label": "Older history value", "data-testid": "field-history-step-older", disabled: !canStepOlder, onMouseDown: (event) => event.preventDefault(), onClick: (event) => {
693
+ event.preventDefault();
694
+ event.stopPropagation();
695
+ applyQuickHistoryIndex(boundedIndex + 1);
696
+ }, children: _jsx(Undo2, { "aria-hidden": "true", className: "h-3.5 w-3.5", strokeWidth: 1 }) }) }), _jsx(TooltipContent, { side: "top", children: "Older value (Alt+Down)" })] }), _jsx("span", { className: "bg-border-default h-6 w-px" }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "hover:bg-neutral-grey-5 text-neutral-grey-100 flex h-full w-[22px] cursor-pointer items-center justify-center disabled:cursor-not-allowed disabled:opacity-35", "aria-label": "Newer history value", "data-testid": "field-history-step-newer", disabled: !canStepNewer, onMouseDown: (event) => event.preventDefault(), onClick: (event) => {
697
+ event.preventDefault();
698
+ event.stopPropagation();
699
+ applyQuickHistoryIndex(boundedIndex - 1);
700
+ }, children: _jsx(Redo2, { "aria-hidden": "true", className: "h-3.5 w-3.5", strokeWidth: 1 }) }) }), _jsx(TooltipContent, { side: "top", children: "Newer value (Alt+Up)" })] }), _jsx("span", { className: "bg-border-default h-6 w-px" })] })), _jsxs(Tooltip, { children: [_jsxs(Popover, { open: open, onOpenChange: (nextOpen) => {
701
+ if (nextOpen) {
702
+ if (!activeDescriptor)
703
+ return;
704
+ const descriptor = activeDescriptor;
705
+ const requestedField = field ?? activeField;
706
+ void (async () => {
707
+ // Full history must be a persistence boundary. A quick
708
+ // step patches the canvas immediately and saves on a
709
+ // trailing debounce; fetching while that save is pending
710
+ // can preview the value from the preceding step. Flush
711
+ // both this component's coalescing timer and the editor's
712
+ // field-save queue before mounting FieldHistory (whose
713
+ // first effect performs the GET).
714
+ await flushPendingApply();
715
+ await editContext.operations.onFieldBlur();
716
+ setHistoryDescriptor(descriptor);
717
+ setHistoryField(requestedField);
718
+ setOpen(true);
719
+ onOpenChange?.(true);
720
+ })();
721
+ }
722
+ else {
723
+ closeHistory();
724
+ }
725
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "hover:bg-neutral-grey-5 text-neutral-grey-100 flex h-full w-6 shrink-0 cursor-pointer items-center justify-center", "aria-label": "Field History", "data-field-history-trigger": "true", "data-testid": testId, onMouseDown: (event) => {
726
+ // Keep the iframe's active field alive while the trigger opens.
727
+ event.preventDefault();
728
+ event.stopPropagation();
729
+ }, onClick: (event) => event.stopPropagation(), children: _jsx(History, { "aria-hidden": "true", strokeWidth: 1, className: "h-3.5 w-3.5" }) }) }) }), _jsx(PopoverContent, { className: "w-[700px] overflow-hidden p-0", align: "end", side: "left", sideOffset: 8, children: displayedField ? (_jsx(FieldHistory, { field: displayedField, selectedEntry: selectedHistoryEntry, onSelectedEntryChange: setSelectedHistoryEntry, configuration: editContext.configuration, showRawValue: false, onRevert: closeHistory, onClose: closeHistory })) : (_jsx("div", { className: "text-neutral-grey-50 flex h-[500px] items-center justify-center text-sm", "data-testid": "field-history-loading", children: "Loading field history\u2026" })) })] }), _jsx(TooltipContent, { side: "top", children: "Field History" })] })] })), showLabel && (_jsx("div", { ref: labelRef, className: "border-border-default text-neutral-grey-50 relative flex h-6 items-center gap-1 overflow-hidden rounded-[4px] border bg-white px-2 text-[10px] whitespace-nowrap shadow-sm", style: { maxWidth: QUICK_HISTORY_LABEL_MAX_WIDTH }, "data-testid": "field-history-entry-label", title: entryLabel, children: quickHistoryLoading && !entryLabel ? (_jsx(Loader2, { "aria-label": "Loading field history", className: "h-3 w-3 animate-spin", strokeWidth: 1 })) : (_jsx("span", { className: "truncate", children: entryLabel })) }))] }));
730
+ }
731
+ //# sourceMappingURL=InlineFieldHistoryButton.js.map