@alpaca-editor/core 1.0.4093 → 1.0.4094

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 (100) hide show
  1. package/dist/components/ui/input.js +1 -1
  2. package/dist/components/ui/popover.d.ts +3 -1
  3. package/dist/components/ui/popover.js +1 -1
  4. package/dist/components/ui/popover.js.map +1 -1
  5. package/dist/components/ui/select.js +5 -3
  6. package/dist/components/ui/select.js.map +1 -1
  7. package/dist/config/config.js +1 -9
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/editor/Editor.js +1 -1
  10. package/dist/editor/Editor.js.map +1 -1
  11. package/dist/editor/FieldList.js +1 -1
  12. package/dist/editor/FieldList.js.map +1 -1
  13. package/dist/editor/FieldListField.js +9 -3
  14. package/dist/editor/FieldListField.js.map +1 -1
  15. package/dist/editor/ItemInfo.js +1 -1
  16. package/dist/editor/ItemInfo.js.map +1 -1
  17. package/dist/editor/LinkEditorDialog.js +6 -6
  18. package/dist/editor/LinkEditorDialog.js.map +1 -1
  19. package/dist/editor/ai/AgentTerminal.js +32 -3
  20. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  21. package/dist/editor/ai/AiResponseMessage.js +2 -2
  22. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  23. package/dist/editor/control-center/setup-steps/AiSetupStep.js +251 -7
  24. package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -1
  25. package/dist/editor/field-types/DateFieldEditor.js +1 -1
  26. package/dist/editor/field-types/DateFieldEditor.js.map +1 -1
  27. package/dist/editor/field-types/DateTimeFieldEditor.js +1 -1
  28. package/dist/editor/field-types/DateTimeFieldEditor.js.map +1 -1
  29. package/dist/editor/field-types/DropLinkEditor.js +1 -1
  30. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  31. package/dist/editor/field-types/DropListEditor.js +1 -1
  32. package/dist/editor/field-types/DropListEditor.js.map +1 -1
  33. package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
  34. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  35. package/dist/editor/field-types/LinkFieldEditor.js +2 -1
  36. package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
  37. package/dist/editor/field-types/TreeListEditor.js +75 -70
  38. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  39. package/dist/editor/menubar/PageSelector.js +2 -1
  40. package/dist/editor/menubar/PageSelector.js.map +1 -1
  41. package/dist/editor/page-editor-chrome/FrameMenu.js +2 -2
  42. package/dist/editor/page-editor-chrome/InlineEditor.js +8 -0
  43. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  44. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +8 -0
  45. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  46. package/dist/editor/page-viewer/DeviceToolbar.js +2 -1
  47. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  48. package/dist/editor/page-viewer/EditorForm.js +1 -5
  49. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  50. package/dist/editor/page-viewer/PageViewerFrame.js +17 -0
  51. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  52. package/dist/editor/pageModel.d.ts +1 -0
  53. package/dist/editor/services/aiService.d.ts +1 -0
  54. package/dist/editor/services/aiService.js.map +1 -1
  55. package/dist/editor/ui/Splitter.d.ts +1 -0
  56. package/dist/editor/ui/Splitter.js +9 -4
  57. package/dist/editor/ui/Splitter.js.map +1 -1
  58. package/dist/page-wizard/steps/ComponentTypesSelector.js +1 -1
  59. package/dist/page-wizard/steps/ComponentTypesSelector.js.map +1 -1
  60. package/dist/page-wizard/steps/ContentStep.js +81 -69
  61. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  62. package/dist/page-wizard/utils/dataAccessor.js +11 -5
  63. package/dist/page-wizard/utils/dataAccessor.js.map +1 -1
  64. package/dist/revision.d.ts +2 -2
  65. package/dist/revision.js +2 -2
  66. package/dist/styles.css +12 -3
  67. package/package.json +1 -1
  68. package/src/components/ui/input.tsx +1 -1
  69. package/src/components/ui/popover.tsx +22 -15
  70. package/src/components/ui/select.tsx +44 -29
  71. package/src/config/config.tsx +0 -12
  72. package/src/editor/Editor.tsx +1 -1
  73. package/src/editor/FieldList.tsx +4 -1
  74. package/src/editor/FieldListField.tsx +10 -4
  75. package/src/editor/ItemInfo.tsx +1 -1
  76. package/src/editor/LinkEditorDialog.tsx +25 -19
  77. package/src/editor/ai/AgentTerminal.tsx +58 -28
  78. package/src/editor/ai/AiResponseMessage.tsx +4 -4
  79. package/src/editor/control-center/setup-steps/AiSetupStep.tsx +362 -40
  80. package/src/editor/field-types/DateFieldEditor.tsx +2 -1
  81. package/src/editor/field-types/DateTimeFieldEditor.tsx +1 -1
  82. package/src/editor/field-types/DropLinkEditor.tsx +8 -6
  83. package/src/editor/field-types/DropListEditor.tsx +11 -9
  84. package/src/editor/field-types/InternalLinkFieldEditor.tsx +1 -1
  85. package/src/editor/field-types/LinkFieldEditor.tsx +2 -1
  86. package/src/editor/field-types/TreeListEditor.tsx +178 -178
  87. package/src/editor/menubar/PageSelector.tsx +3 -3
  88. package/src/editor/page-editor-chrome/FrameMenu.tsx +1 -1
  89. package/src/editor/page-editor-chrome/InlineEditor.tsx +10 -0
  90. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +11 -0
  91. package/src/editor/page-viewer/DeviceToolbar.tsx +9 -2
  92. package/src/editor/page-viewer/EditorForm.tsx +1 -6
  93. package/src/editor/page-viewer/PageViewerFrame.tsx +28 -0
  94. package/src/editor/pageModel.ts +1 -0
  95. package/src/editor/services/aiService.ts +2 -0
  96. package/src/editor/ui/Splitter.tsx +26 -2
  97. package/src/page-wizard/steps/ComponentTypesSelector.tsx +1 -1
  98. package/src/page-wizard/steps/ContentStep.tsx +129 -106
  99. package/src/page-wizard/utils/dataAccessor.ts +13 -5
  100. package/src/revision.ts +2 -2
@@ -1,4 +1,7 @@
1
- import { Splitter, SplitterPanel } from "primereact/splitter";
1
+ import {
2
+ Splitter,
3
+ SplitterPanel as InternalSplitterPanel,
4
+ } from "../ui/Splitter";
2
5
  import { useEditContext } from "../client/editContext";
3
6
  import { useEffect, useState } from "react";
4
7
 
@@ -8,6 +11,7 @@ import { SimpleIconButton } from "../ui/SimpleIconButton";
8
11
  import { normalizeGuid } from "../utils";
9
12
  import ItemSearch from "../ui/ItemSearch";
10
13
  import { Button } from "../../components/ui/button";
14
+ import { ChevronLeft, ChevronRight } from "lucide-react";
11
15
  import {
12
16
  Tooltip,
13
17
  TooltipContent,
@@ -193,9 +197,154 @@ export default function TreeListEditor({
193
197
  setDraggedIndex(null);
194
198
  };
195
199
 
200
+ const panels: InternalSplitterPanel[] = [
201
+ {
202
+ name: "tree",
203
+ defaultSize: "auto",
204
+ className: "relative",
205
+ content: (
206
+ <div
207
+ className={`absolute inset-0 overflow-auto ${readOnly ? "bg-gray-5" : ""}`}
208
+ >
209
+ <ContentTree
210
+ expandIdPath={hoveredItem?.idPath}
211
+ language={editContext.currentItemDescriptor?.language ?? "en"}
212
+ onDoubleClick={(node) => {
213
+ if (!readOnly) addToList([node]);
214
+ }}
215
+ selectionMode="multiple"
216
+ selectedItemIds={
217
+ selectedItemNodesInTree.length
218
+ ? selectedItemNodesInTree.map((x) => x.id)
219
+ : hoveredItemId
220
+ ? [hoveredItemId]
221
+ : []
222
+ }
223
+ onSelectionChange={(e) => {
224
+ setSelectedItemNodesInTree(e as ItemData[]);
225
+ }}
226
+ rootItemIds={rootItemIds}
227
+ includeItemPath={true}
228
+ />
229
+ </div>
230
+ ),
231
+ },
232
+ {
233
+ name: "selectedList",
234
+ defaultSize: "auto",
235
+ className: "relative",
236
+ content: (
237
+ <div className="relative h-full w-full">
238
+ <div
239
+ className={`absolute inset-0 overflow-auto text-xs ${readOnly ? "bg-gray-5" : "bg-white"}`}
240
+ >
241
+ {values.length === 0 ? (
242
+ <div className="p-3 text-center text-gray-500">None selected</div>
243
+ ) : (
244
+ values.map((option, index) => (
245
+ <Tooltip key={`${option.id}-${index}`} delayDuration={1000}>
246
+ <TooltipTrigger asChild>
247
+ <div
248
+ draggable={!readOnly}
249
+ onDragStart={(e) => handleDragStart(e, index)}
250
+ onDragOver={handleDragOver}
251
+ onDrop={(e) => handleDrop(e, index)}
252
+ onDragEnd={handleDragEnd}
253
+ className={`group relative flex cursor-pointer items-center gap-1.5 border-b border-gray-100 p-1 select-none last:border-b-0 hover:bg-gray-50 ${
254
+ selectedFromList.includes(option) ? "bg-blue-100" : ""
255
+ } ${draggedIndex === index ? "opacity-50" : ""} ${
256
+ !readOnly ? "cursor-move" : ""
257
+ }`}
258
+ onClick={(e) => {
259
+ if (e.shiftKey && lastClickedIndex !== null) {
260
+ const startIndex = Math.min(lastClickedIndex, index);
261
+ const endIndex = Math.max(lastClickedIndex, index);
262
+ const rangeItems = values.slice(
263
+ startIndex,
264
+ endIndex + 1,
265
+ );
266
+ setSelectedFromList(rangeItems);
267
+ } else if (e.ctrlKey) {
268
+ if (selectedFromList.includes(option)) {
269
+ setSelectedFromList(
270
+ selectedFromList.filter(
271
+ (item) => item !== option,
272
+ ),
273
+ );
274
+ } else {
275
+ setSelectedFromList([...selectedFromList, option]);
276
+ }
277
+ setLastClickedIndex(index);
278
+ } else {
279
+ if (
280
+ selectedFromList.length === 1 &&
281
+ selectedFromList.includes(option)
282
+ ) {
283
+ setSelectedFromList([]);
284
+ setLastClickedIndex(null);
285
+ } else {
286
+ setSelectedFromList([option]);
287
+ setLastClickedIndex(index);
288
+ }
289
+ }
290
+ }}
291
+ onMouseEnter={async () => {
292
+ setHoveredItemId(option.id);
293
+ setSelectedItemNodesInTree([
294
+ {
295
+ id: option.id,
296
+ path: option.path,
297
+ name: option.name,
298
+ icon: option.icon,
299
+ idPath: option.idPath || "",
300
+ },
301
+ ]);
302
+ }}
303
+ onDoubleClick={() => {
304
+ if (!readOnly) removeFromList([option]);
305
+ }}
306
+ >
307
+ <img src={option.icon} className="h-4 w-4" />
308
+ {trimPath(option.path)}{" "}
309
+ {activeOpenButtonId === option.id ? (
310
+ <Button
311
+ className="h-4 p-1"
312
+ size="sm"
313
+ onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
314
+ e.stopPropagation();
315
+ editContext.loadItem(option.id);
316
+ setActiveOpenButtonId("");
317
+ }}
318
+ >
319
+ Open
320
+ </Button>
321
+ ) : (
322
+ <button
323
+ className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100"
324
+ onClick={(e) => {
325
+ e.stopPropagation();
326
+ setActiveOpenButtonId(option.id);
327
+ }}
328
+ title="Open"
329
+ >
330
+ <i className="pi pi-arrow-up-right text-xs text-gray-500" />
331
+ </button>
332
+ )}
333
+ </div>
334
+ </TooltipTrigger>
335
+ <TooltipContent>{option.path}</TooltipContent>
336
+ </Tooltip>
337
+ ))
338
+ )}
339
+ </div>
340
+ </div>
341
+ ),
342
+ },
343
+ ];
344
+
196
345
  return (
197
346
  <div
198
- className={`focus-shadow border border-gray-200 ${readOnly ? "bg-gray-5" : ""}`}
347
+ className={`focus-shadow rounded-sm border border-gray-200 ${readOnly ? "bg-gray-5" : ""}`}
199
348
  >
200
349
  <div
201
350
  className={`border-b border-gray-200 p-2 ${readOnly ? "bg-gray-5" : "bg-gray-5"}`}
@@ -216,183 +365,34 @@ export default function TreeListEditor({
216
365
  }}
217
366
  />
218
367
  </div>
219
- <Splitter className="h-60">
220
- <SplitterPanel size={50} className="relative">
221
- <div
222
- className={`absolute inset-0 overflow-auto ${readOnly ? "bg-gray-5" : ""}`}
223
- >
224
- <ContentTree
225
- expandIdPath={hoveredItem?.idPath}
226
- language={editContext.currentItemDescriptor?.language ?? "en"}
227
- onDoubleClick={(node) => {
228
- if (!readOnly) addToList([node]);
229
- }}
230
- selectionMode="multiple"
231
- selectedItemIds={
232
- selectedItemNodesInTree.length
233
- ? selectedItemNodesInTree.map((x) => x.id)
234
- : hoveredItemId
235
- ? [hoveredItemId]
236
- : []
237
- }
238
- onSelectionChange={(e) => {
239
- setSelectedItemNodesInTree(e as ItemData[]);
240
- }}
241
- rootItemIds={rootItemIds}
242
- includeItemPath={true}
243
- />
244
- </div>
245
- </SplitterPanel>
246
- <SplitterPanel>
247
- <div className="relative h-full w-full">
248
- <div className="absolute inset-0 flex">
249
- <div
250
- className={`flex flex-col justify-center gap-1 p-1 ${readOnly ? "bg-gray-5" : "bg-gray-100"}`}
251
- >
252
- <SimpleIconButton
253
- label="Add"
254
- icon="pi pi-angle-right"
255
- onClick={() => addToList(selectedItemNodesInTree)}
256
- disabled={readOnly}
257
- />
258
- <SimpleIconButton
259
- label="Remove"
260
- icon="pi pi-angle-left"
261
- onClick={() => removeFromList(selectedFromList)}
262
- disabled={readOnly}
263
- />
264
- </div>
265
- <div className="relative h-full flex-1">
266
- <div
267
- className={`absolute inset-0 overflow-auto text-xs ${readOnly ? "bg-gray-5" : "bg-white"}`}
268
- >
269
- {values.length === 0 ? (
270
- <div className="p-3 text-center text-gray-500">
271
- None selected
272
- </div>
273
- ) : (
274
- values.map((option, index) => (
275
- <Tooltip
276
- key={`${option.id}-${index}`}
277
- delayDuration={1000}
278
- >
279
- <TooltipTrigger asChild>
280
- <div
281
- draggable={!readOnly}
282
- onDragStart={(e) => handleDragStart(e, index)}
283
- onDragOver={handleDragOver}
284
- onDrop={(e) => handleDrop(e, index)}
285
- onDragEnd={handleDragEnd}
286
- className={`group relative flex cursor-pointer items-center gap-1.5 border-b border-gray-100 p-1 select-none last:border-b-0 hover:bg-gray-50 ${
287
- selectedFromList.includes(option)
288
- ? "bg-blue-100"
289
- : ""
290
- } ${draggedIndex === index ? "opacity-50" : ""} ${
291
- !readOnly ? "cursor-move" : ""
292
- }`}
293
- onClick={(e) => {
294
- if (e.shiftKey && lastClickedIndex !== null) {
295
- // Range selection mode when Shift is pressed
296
- const startIndex = Math.min(
297
- lastClickedIndex,
298
- index,
299
- );
300
- const endIndex = Math.max(
301
- lastClickedIndex,
302
- index,
303
- );
304
- const rangeItems = values.slice(
305
- startIndex,
306
- endIndex + 1,
307
- );
308
- setSelectedFromList(rangeItems);
309
- } else if (e.ctrlKey) {
310
- // Multi-select mode when Ctrl is pressed
311
- if (selectedFromList.includes(option)) {
312
- setSelectedFromList(
313
- selectedFromList.filter(
314
- (item) => item !== option,
315
- ),
316
- );
317
- } else {
318
- setSelectedFromList([
319
- ...selectedFromList,
320
- option,
321
- ]);
322
- }
323
- setLastClickedIndex(index);
324
- } else {
325
- // Single-select mode when Ctrl is not pressed
326
- if (
327
- selectedFromList.length === 1 &&
328
- selectedFromList.includes(option)
329
- ) {
330
- // Deselect if this is the only selected item
331
- setSelectedFromList([]);
332
- setLastClickedIndex(null);
333
- } else {
334
- // Replace selection with just this item
335
- setSelectedFromList([option]);
336
- setLastClickedIndex(index);
337
- }
338
- }
339
- }}
340
- onMouseEnter={async () => {
341
- setHoveredItemId(option.id);
342
- setSelectedItemNodesInTree([
343
- {
344
- id: option.id,
345
- path: option.path,
346
- name: option.name,
347
- icon: option.icon,
348
- idPath: option.idPath || "",
349
- },
350
- ]);
351
- }}
352
- onDoubleClick={() => {
353
- if (!readOnly) removeFromList([option]);
354
- }}
355
- >
356
- <img src={option.icon} className="h-4 w-4" />
357
- {trimPath(option.path)}{" "}
358
- {activeOpenButtonId === option.id ? (
359
- <Button
360
- className="h-4 p-1"
361
- size="sm"
362
- onClick={(
363
- e: React.MouseEvent<HTMLButtonElement>,
364
- ) => {
365
- e.stopPropagation();
366
- editContext.loadItem(option.id);
367
- setActiveOpenButtonId("");
368
- }}
369
- >
370
- Open
371
- </Button>
372
- ) : (
373
- <button
374
- className="cursor-pointer opacity-0 transition-opacity group-hover:opacity-100"
375
- onClick={(e) => {
376
- e.stopPropagation();
377
- setActiveOpenButtonId(option.id);
378
- }}
379
- title="Open"
380
- >
381
- <i className="pi pi-arrow-up-right text-xs text-gray-500" />
382
- </button>
383
- )}
384
- </div>
385
- </TooltipTrigger>
386
- <TooltipContent>{option.path}</TooltipContent>
387
- </Tooltip>
388
- ))
389
- )}
390
- </div>
391
- </div>
368
+ <Splitter
369
+ className="h-60"
370
+ panels={panels}
371
+ handleContent={(index) =>
372
+ index === 0 ? (
373
+ <div
374
+ className={`flex flex-col items-center gap-1 rounded bg-gray-100 py-1 ${readOnly ? "opacity-50" : ""}`}
375
+ >
376
+ <SimpleIconButton
377
+ label="Add"
378
+ icon={<ChevronRight size={14} strokeWidth={1} />}
379
+ onClick={() => addToList(selectedItemNodesInTree)}
380
+ disabled={readOnly}
381
+ showTooltip={false}
382
+ className="px-0"
383
+ />
384
+ <SimpleIconButton
385
+ label="Remove"
386
+ icon={<ChevronLeft size={14} strokeWidth={1} />}
387
+ onClick={() => removeFromList(selectedFromList)}
388
+ disabled={readOnly}
389
+ showTooltip={false}
390
+ className="px-0"
391
+ />
392
392
  </div>
393
- </div>
394
- </SplitterPanel>
395
- </Splitter>
393
+ ) : null
394
+ }
395
+ />
396
396
  </div>
397
397
  );
398
398
  }
@@ -13,7 +13,7 @@ import { ItemTreeNodeData } from "../services/contentService";
13
13
  import { SimpleIconButton } from "../ui/SimpleIconButton";
14
14
  import { executeSearch } from "../services/searchService";
15
15
  import { ItemList } from "../ui/ItemList";
16
- import { DotsVerticalIcon } from "@radix-ui/react-icons";
16
+ import { History, FolderTree } from "lucide-react";
17
17
  import {
18
18
  Popover,
19
19
  PopoverContent,
@@ -211,14 +211,14 @@ export function PageSelector({
211
211
  </div>
212
212
  <div className="flex gap-1">
213
213
  <SimpleIconButton
214
- icon="pi pi-sitemap"
214
+ icon={<FolderTree className="h-4 w-4" strokeWidth={1} />}
215
215
  label="Search Content"
216
216
  selected={!showHistory}
217
217
  onClick={() => setShowHistory(false)}
218
218
  size="small"
219
219
  />
220
220
  <SimpleIconButton
221
- icon="pi pi-history"
221
+ icon={<History className="h-4 w-4" strokeWidth={1} />}
222
222
  label="Filter History"
223
223
  selected={showHistory}
224
224
  onClick={() => setShowHistory(true)}
@@ -241,8 +241,8 @@ export function FrameMenu({
241
241
  function getColor() {
242
242
  if (isReadonly) return "readonly";
243
243
  if (editContext?.mode === "suggestions") return "suggestions";
244
- if (isShared) return "shared";
245
244
  if (isLayout) return "layout";
245
+ if (isShared) return "shared";
246
246
  if (component.canBeMoved) return "default";
247
247
  return "nonMovable";
248
248
  }
@@ -12,6 +12,7 @@ import { createPatch } from "diff";
12
12
  // import { useDebouncedCallback } from "use-debounce";
13
13
  // import { executePrompt } from "../services/aiService";
14
14
  import { useInlineAiCompletion } from "./useInlineAICompletion";
15
+ import { getComponentById } from "../componentTreeHelper";
15
16
 
16
17
  export function InlineEditor({
17
18
  pageViewContext,
@@ -112,6 +113,15 @@ export function InlineEditor({
112
113
 
113
114
  if (!fieldId || !itemId || !language || !version) return;
114
115
 
116
+ // Guard: if layout components are hidden, do not allow editing fields of layout components
117
+ if (
118
+ contextRef.current?.showLayoutComponents === false &&
119
+ contextRef.current?.page
120
+ ) {
121
+ const owner = getComponentById(itemId, contextRef.current.page);
122
+ if (owner?.layoutId) return;
123
+ }
124
+
115
125
  const fieldDescriptor = getFieldDescriptorFromElement(element);
116
126
 
117
127
  const hasFielLock =
@@ -57,6 +57,17 @@ export function PictureEditorOverlay() {
57
57
 
58
58
  if (component == null) return;
59
59
 
60
+ // Guard: if layout components are hidden, do not show picture editor overlay for layout components
61
+ if (
62
+ editContextRef.current?.showLayoutComponents === false &&
63
+ component.layoutId
64
+ ) {
65
+ setField(undefined);
66
+ setVariantName(undefined);
67
+ setElement(undefined);
68
+ return;
69
+ }
70
+
60
71
  const item = await editContext!.itemsRepository.getItem(
61
72
  component.datasourceItem!,
62
73
  );
@@ -6,6 +6,7 @@ import { useDebouncedCallback } from "use-debounce";
6
6
  import { PageViewContext } from "./pageViewContext";
7
7
  import { EditorConfiguration } from "../../config/types";
8
8
  import { cn } from "../../lib/utils";
9
+ import { Lock, LockOpen } from "lucide-react";
9
10
 
10
11
  export function DeviceToolbar({
11
12
  pageViewContext,
@@ -53,9 +54,15 @@ export function DeviceToolbar({
53
54
  onChange={(e) => debouncedSetDeviceHeight(e.value || undefined)}
54
55
  />
55
56
  <SimpleIconButton
56
- icon="pi pi-lock"
57
+ icon={
58
+ pageViewContext.lockHeight ? (
59
+ <Lock className="h-4 w-4" strokeWidth={1} />
60
+ ) : (
61
+ <LockOpen className="h-4 w-4" strokeWidth={1} />
62
+ )
63
+ }
57
64
  className="hover:bg-gray-3"
58
- selected={pageViewContext.lockHeight || false}
65
+ selected={false}
59
66
  onClick={() =>
60
67
  pageViewContext.setLockHeight(!pageViewContext.lockHeight)
61
68
  }
@@ -180,13 +180,8 @@ export function EditorForm({
180
180
  fullItem.language === item.language &&
181
181
  fullItem.version === item.version;
182
182
 
183
- // While the new full item is loading, render nothing to avoid showing stale content
184
- if (hasDatasource && (!item || !fullItemMatchesSelection)) {
185
- return <div className="h-full w-full"></div>;
186
- }
187
-
188
183
  // Keep showing a spinner only when loading additional component items
189
- if (isLoadingComponentItems)
184
+ if (!fullItemMatchesSelection)
190
185
  return (
191
186
  <div className="grid h-full w-full items-center justify-center">
192
187
  <Spinner />
@@ -428,6 +428,19 @@ export function PageViewerFrame({
428
428
 
429
429
  const range = sel.getRangeAt(0);
430
430
 
431
+ // Guard: if layout components are hidden, do not set focus for fields of layout components
432
+ if (
433
+ editContextRef.current?.showLayoutComponents === false &&
434
+ pageViewContextRef.current?.page
435
+ ) {
436
+ const ownerId = fieldElement.getAttribute("data-itemid") || "";
437
+ const owner = getComponentById(
438
+ ownerId,
439
+ pageViewContextRef.current!.page!,
440
+ );
441
+ if (owner?.layoutId) return;
442
+ }
443
+
431
444
  fieldsContextRef.current?.setFocusedField(
432
445
  {
433
446
  fieldId: fieldId,
@@ -554,6 +567,21 @@ export function PageViewerFrame({
554
567
  ) {
555
568
  const fieldElement = findParentWithAttribute(target, "data-fieldid");
556
569
  if (fieldElement?.hasAttribute("data-itemid")) {
570
+ // Guard: if layout components are hidden, do not allow editing fields of layout components
571
+ if (
572
+ editContextRef.current?.showLayoutComponents === false &&
573
+ pageViewContextRef.current?.page
574
+ ) {
575
+ const owningItemId = fieldElement.getAttribute("data-itemid") || "";
576
+ const ownerComponent = getComponentById(
577
+ owningItemId,
578
+ pageViewContextRef.current!.page!,
579
+ );
580
+ if (ownerComponent?.layoutId) {
581
+ return;
582
+ }
583
+ }
584
+
557
585
  blockBlurEventRef.current = Date.now() + 500;
558
586
 
559
587
  const hasLock =
@@ -67,6 +67,7 @@ export type Field = {
67
67
  isFallback?: boolean;
68
68
  isStandardValue?: boolean;
69
69
  isShared?: boolean;
70
+ hasValue?: boolean;
70
71
  sourceItems: ItemRef[];
71
72
  buttons?: FieldButton[];
72
73
  fallbackChain?: FieldDescriptor[];
@@ -18,6 +18,8 @@ export type AiProfile = {
18
18
  models: AiModel[]; // Array of model objects with id and name
19
19
  prompts: { prompt: string; title: string }[];
20
20
  errorMessage?: string;
21
+ // Whether a new agent should be seeded with current item/selection/field
22
+ includeEditorContextOnCreate?: boolean;
21
23
  };
22
24
 
23
25
  export async function loadAiProfiles(
@@ -19,6 +19,9 @@ interface SplitterProps {
19
19
  className?: string;
20
20
  splitterClassName?: string;
21
21
  expandButtonClassName?: string;
22
+ // Optional render function to inject custom content into a splitter handle.
23
+ // Receives the handle index (between panel i and i+1). Return null to render nothing.
24
+ handleContent?: (index: number) => React.ReactNode;
22
25
  }
23
26
 
24
27
  type StoredSizes = { panels: PanelSizes; lastCollapsed: boolean };
@@ -32,6 +35,7 @@ export const Splitter: React.FC<SplitterProps> = ({
32
35
  className,
33
36
  splitterClassName,
34
37
  expandButtonClassName,
38
+ handleContent,
35
39
  }) => {
36
40
  const totalPanels = panels.length;
37
41
 
@@ -357,7 +361,7 @@ export const Splitter: React.FC<SplitterProps> = ({
357
361
  minWidth: isHorizontal ? "4px" : undefined,
358
362
  boxSizing: "border-box",
359
363
  userSelect: "none",
360
- zIndex: 9999, // Ensure we're above any nested splitters
364
+ zIndex: 1, // Ensure we're above any nested splitters
361
365
  }}
362
366
  onDoubleClick={
363
367
  isLastResizer(index) ? toggleLastPanelCollapse : undefined
@@ -383,6 +387,22 @@ export const Splitter: React.FC<SplitterProps> = ({
383
387
  }`}
384
388
  />
385
389
 
390
+ {/* Custom handle content */}
391
+ {handleContent && (
392
+ <div
393
+ className="pointer-events-auto flex items-center justify-center"
394
+ onMouseDown={(e) => {
395
+ // Prevent starting drag when interacting with handle content
396
+ e.stopPropagation();
397
+ }}
398
+ onClick={(e) => {
399
+ e.stopPropagation();
400
+ }}
401
+ >
402
+ {handleContent(index)}
403
+ </div>
404
+ )}
405
+
386
406
  {/* Small collapse button for collapsible panels */}
387
407
  {lastPanelCollapsible &&
388
408
  !isLastPanelCollapsed &&
@@ -435,7 +455,11 @@ export const Splitter: React.FC<SplitterProps> = ({
435
455
 
436
456
  return (
437
457
  <div
438
- className={`flex ${direction === "horizontal" ? "flex-row" : "flex-col"} h-full w-full overflow-hidden ${className || ""}`}
458
+ className={`flex ${
459
+ direction === "horizontal" ? "flex-row" : "flex-col"
460
+ } ${
461
+ className && /(^|\s)h-/.test(className) ? "" : "h-full"
462
+ } ${className && /(^|\s)w-/.test(className) ? "" : "w-full"} overflow-hidden ${className || ""}`}
439
463
  ref={splitterRef}
440
464
  >
441
465
  {panels.map((panel, index) => (
@@ -171,7 +171,7 @@ export function ComponentTypeSelector({
171
171
  {/* Component Types Section */}
172
172
  <div className="min-w-0 flex-1 rounded">
173
173
  <div className="mb-2 flex items-center justify-between">
174
- <h4 className="text-sm font-bold">Component Types</h4>
174
+ <h4 className="text-sm">Component Types</h4>
175
175
  </div>
176
176
 
177
177
  {/* Selected component tags */}