@alpaca-editor/core 1.0.4110 → 1.0.4112

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 (46) hide show
  1. package/dist/components/ui/context-menu.d.ts +45 -17
  2. package/dist/components/ui/context-menu.js +224 -32
  3. package/dist/components/ui/context-menu.js.map +1 -1
  4. package/dist/components/ui/dialog.d.ts +2 -1
  5. package/dist/components/ui/dialog.js +2 -2
  6. package/dist/components/ui/dialog.js.map +1 -1
  7. package/dist/editor/ContextMenu.js +5 -2
  8. package/dist/editor/ContextMenu.js.map +1 -1
  9. package/dist/editor/MainLayout.js +1 -1
  10. package/dist/editor/MainLayout.js.map +1 -1
  11. package/dist/editor/client/EditorShell.js +0 -5
  12. package/dist/editor/client/EditorShell.js.map +1 -1
  13. package/dist/editor/commands/itemCommands.js +5 -10
  14. package/dist/editor/commands/itemCommands.js.map +1 -1
  15. package/dist/editor/context-menu/InsertMenu.js +24 -3
  16. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  17. package/dist/editor/page-viewer/PageViewerFrame.js +4 -1
  18. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  19. package/dist/editor/sidebar/SidebarView.js +1 -1
  20. package/dist/editor/sidebar/SidebarView.js.map +1 -1
  21. package/dist/editor/ui/ItemNameDialogNew.js +1 -7
  22. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  23. package/dist/editor/ui/SimpleMenu.js.map +1 -1
  24. package/dist/editor/ui/Splitter.js +96 -23
  25. package/dist/editor/ui/Splitter.js.map +1 -1
  26. package/dist/revision.d.ts +2 -2
  27. package/dist/revision.js +2 -2
  28. package/dist/styles.css +12 -6
  29. package/package.json +1 -1
  30. package/src/components/ui/context-menu.tsx +420 -143
  31. package/src/components/ui/dialog.tsx +3 -1
  32. package/src/editor/ContextMenu.tsx +9 -1
  33. package/src/editor/MainLayout.tsx +9 -13
  34. package/src/editor/client/EditorShell.tsx +1 -6
  35. package/src/editor/commands/itemCommands.tsx +11 -12
  36. package/src/editor/context-menu/InsertMenu.tsx +33 -3
  37. package/src/editor/page-viewer/PageViewerFrame.tsx +6 -1
  38. package/src/editor/sidebar/SidebarView.tsx +6 -9
  39. package/src/editor/ui/ItemNameDialogNew.tsx +2 -12
  40. package/src/editor/ui/SimpleMenu.tsx +0 -1
  41. package/src/editor/ui/Splitter.tsx +109 -25
  42. package/src/revision.ts +2 -2
  43. package/dist/editor/client/hooks/useGlobalEditorEvents.d.ts +0 -4
  44. package/dist/editor/client/hooks/useGlobalEditorEvents.js +0 -15
  45. package/dist/editor/client/hooks/useGlobalEditorEvents.js.map +0 -1
  46. package/src/editor/client/hooks/useGlobalEditorEvents.ts +0 -18
@@ -50,13 +50,15 @@ function DialogContent({
50
50
  className,
51
51
  children,
52
52
  showCloseButton = true,
53
+ overlayClassName,
53
54
  ...props
54
55
  }: React.ComponentProps<typeof DialogPrimitive.Content> & {
55
56
  showCloseButton?: boolean;
57
+ overlayClassName?: string;
56
58
  }) {
57
59
  return (
58
60
  <DialogPortal data-slot="dialog-portal">
59
- <DialogOverlay />
61
+ <DialogOverlay className={overlayClassName} />
60
62
  <DialogPrimitive.Content
61
63
  data-slot="dialog-content"
62
64
  className={cn(
@@ -203,7 +203,7 @@ export const EditContextMenu = forwardRef<
203
203
  try {
204
204
  // Close Radix ContextMenu by sending Escape to the document
205
205
  document.dispatchEvent(
206
- new KeyboardEvent("keydown", { key: "Escape", bubbles: true })
206
+ new KeyboardEvent("keydown", { key: "Escape", bubbles: true }),
207
207
  );
208
208
  } catch {}
209
209
  },
@@ -239,6 +239,12 @@ export const EditContextMenu = forwardRef<
239
239
  <div
240
240
  key={index}
241
241
  className={cn("px-2 py-1.5 hover:bg-gray-100", item.className)}
242
+ onPointerDownCapture={(e) => e.stopPropagation()}
243
+ onMouseDownCapture={(e) => e.stopPropagation()}
244
+ onClick={(e) => {
245
+ e.stopPropagation();
246
+ e.preventDefault();
247
+ }}
242
248
  >
243
249
  {item.template}
244
250
  </div>
@@ -284,6 +290,8 @@ export const EditContextMenu = forwardRef<
284
290
  e.stopPropagation();
285
291
  e.preventDefault();
286
292
  }}
293
+ onPointerDownCapture={(e) => e.stopPropagation()}
294
+ onMouseDownCapture={(e) => e.stopPropagation()}
287
295
  >
288
296
  {subItem.template}
289
297
  </div>
@@ -38,19 +38,15 @@ export default function MainLayout(props: MainLayoutProps) {
38
38
  name: "leftSidebar",
39
39
  defaultSize: props.view.leftSidebar ? 350 : 0,
40
40
  hidden: !props.view.leftSidebar,
41
- content: (
42
- <div className="relative h-full w-full" style={{ wordBreak: "normal" }}>
43
- {viewsWithLeftSidebar.map((v) => (
44
- <SidebarView
45
- key={v.name}
46
- sidebar={v.leftSidebar!}
47
- editContext={editContext}
48
- active={props.view.name === v.name && !!v.leftSidebar}
49
- onClose={() => editContext.switchView("page-editor")}
50
- />
51
- ))}
52
- </div>
53
- ),
41
+ content: viewsWithLeftSidebar.map((v) => (
42
+ <SidebarView
43
+ key={v.name}
44
+ sidebar={v.leftSidebar!}
45
+ editContext={editContext}
46
+ active={props.view.name === v.name && !!v.leftSidebar}
47
+ onClose={() => editContext.switchView("page-editor")}
48
+ />
49
+ )),
54
50
  });
55
51
  }
56
52
  panels.push({
@@ -137,7 +137,7 @@ import { useQuota } from "./hooks/useQuota";
137
137
  import { useEditorUrlSync } from "./hooks/useEditorUrlSync";
138
138
 
139
139
  import { useMediaQuery } from "./hooks/useMediaQuery";
140
- import { useGlobalEditorEvents } from "./hooks/useGlobalEditorEvents";
140
+
141
141
  import { FullscreenControls } from "./ui/FullscreenControls";
142
142
  import { EditorChrome } from "./ui/EditorChrome";
143
143
  import { useWorkbox } from "./hooks/useWorkbox";
@@ -1525,11 +1525,6 @@ export function EditorShell({
1525
1525
  executeCommand,
1526
1526
  });
1527
1527
 
1528
- useGlobalEditorEvents({
1529
- onKeyDown: handleKeyDown,
1530
- onWindowClick: () => contextMenuRef.current?.close({}),
1531
- });
1532
-
1533
1528
  useEffect(() => {
1534
1529
  const handleGlobalBlur = () => {
1535
1530
  operations.onFieldBlur?.();
@@ -164,18 +164,17 @@ export const insertItemCommand: ItemCommand = {
164
164
  },
165
165
  );
166
166
  if (name) {
167
- // const result = await context.editContext.operations.createItem(
168
- // parentItem.descriptor,
169
- // templateId,
170
- // name,
171
- // );
172
-
173
- // if (result) {
174
- // context.editContext.loadItem(result);
175
- // }
176
- console.log("name", name);
177
-
178
- // return result;
167
+ const result = await context.editContext.operations.createItem(
168
+ parentItem.descriptor,
169
+ templateId,
170
+ name,
171
+ );
172
+
173
+ if (result) {
174
+ context.editContext.loadItem(result);
175
+ }
176
+
177
+ return result;
179
178
  }
180
179
  },
181
180
  };
@@ -44,6 +44,7 @@ export const InsertMenuTemplate = ({
44
44
 
45
45
  const hideTimeoutRef = useRef<NodeJS.Timeout>(undefined);
46
46
  const opRef = useRef<HTMLDivElement>(null);
47
+ const [isPanelOpen, setIsPanelOpen] = useState(false);
47
48
 
48
49
  useEffect(() => {
49
50
  const loadRecentItems = async () => {
@@ -372,19 +373,32 @@ export const InsertMenuTemplate = ({
372
373
  if (hideTimeoutRef.current) {
373
374
  clearTimeout(hideTimeoutRef.current);
374
375
  }
375
- opRef.current && (opRef.current.style.display = "block");
376
+ setIsPanelOpen(true);
376
377
  // Do not focus immediately to avoid interfering with outer menu focus handling
377
378
  setTimeout(() => filterRef.current && filterRef.current.focus(), 0);
378
379
  }}
379
380
  onMouseLeave={() => {
380
381
  hideTimeoutRef.current = setTimeout(() => {
381
- opRef.current && (opRef.current.style.display = "none");
382
+ setIsPanelOpen(false);
382
383
  }, 200);
383
384
  }}
384
385
  onClick={(ev) => {
385
386
  ev.stopPropagation();
386
387
  ev.preventDefault();
387
388
  }}
389
+ onPointerDownCapture={(ev) => {
390
+ // Keep parent context menu open when interacting with the insert panel
391
+ ev.stopPropagation();
392
+ }}
393
+ onMouseDownCapture={(ev) => {
394
+ ev.stopPropagation();
395
+ }}
396
+ onPointerUpCapture={(ev) => {
397
+ ev.stopPropagation();
398
+ }}
399
+ onMouseUpCapture={(ev) => {
400
+ ev.stopPropagation();
401
+ }}
388
402
  >
389
403
  <Plus strokeWidth={1} size={16} className="text-gray-2" />
390
404
  <span>Insert</span>
@@ -398,8 +412,24 @@ export const InsertMenuTemplate = ({
398
412
  onKeyUpCapture={(ev) => {
399
413
  ev.stopPropagation();
400
414
  }}
415
+ onPointerDownCapture={(ev) => {
416
+ ev.stopPropagation();
417
+ }}
418
+ onMouseDownCapture={(ev) => {
419
+ ev.stopPropagation();
420
+ }}
421
+ onPointerUpCapture={(ev) => {
422
+ ev.stopPropagation();
423
+ }}
424
+ onMouseUpCapture={(ev) => {
425
+ ev.stopPropagation();
426
+ }}
427
+ onClick={(ev) => {
428
+ // Prevent bubbling to parent menu container
429
+ ev.stopPropagation();
430
+ }}
401
431
  style={{
402
- display: opRef.current ? "block" : "none",
432
+ display: isPanelOpen ? "block" : "none",
403
433
  top: "-50px",
404
434
  left: "100%",
405
435
  minWidth: "450px",
@@ -505,7 +505,12 @@ export function PageViewerFrame({
505
505
  currentOverlayName.endsWith("_generators")
506
506
  );
507
507
 
508
- if (editContextRef.current?.currentOverlay && !isGeneratorOverlay)
508
+ // Don't close context-menu overlay on mousedown - let it handle its own closing
509
+ if (
510
+ editContextRef.current?.currentOverlay &&
511
+ !isGeneratorOverlay &&
512
+ editContextRef.current.currentOverlay !== "context-menu"
513
+ )
509
514
  editContextRef.current?.setCurrentOverlay(undefined);
510
515
 
511
516
  if (componentId) {
@@ -114,14 +114,11 @@ export function SidebarView({
114
114
  );
115
115
 
116
116
  return (
117
- <div
118
- className={cn("h-full", detached ? "p-2" : "", !active ? "hidden" : "")}
119
- >
120
- <Splitter
121
- panels={splitterPanels}
122
- direction="vertical"
123
- localStorageKey={`sidebar-${sidebar.panels.length}-panels`}
124
- />
125
- </div>
117
+ <Splitter
118
+ panels={splitterPanels}
119
+ direction="vertical"
120
+ localStorageKey={`sidebar-${sidebar.panels.length}-panels`}
121
+ className={cn(detached ? "p-2" : "", !active ? "hidden" : "")}
122
+ />
126
123
  );
127
124
  }
@@ -105,20 +105,10 @@ export function ItemNameDialog(
105
105
  };
106
106
 
107
107
  return (
108
- <Dialog
109
- open={true}
110
- >
108
+ <Dialog open={true}>
111
109
  <DialogContent
110
+ overlayClassName="pointer-events-none"
112
111
  style={{ width: "400px" }}
113
- onInteractOutside={(e) => {
114
- // Prevent outside interactions from closing while we stabilize focus
115
- e.preventDefault();
116
- }}
117
- onEscapeKeyDown={(e) => {
118
- // Avoid accidental escape propagation from prior overlays
119
- e.preventDefault();
120
- }}
121
- showCloseButton={false}
122
112
  >
123
113
  <DialogHeader>
124
114
  <DialogTitle>{props?.title ?? "Name"}</DialogTitle>
@@ -1,4 +1,3 @@
1
- import { ReactNode } from "react";
2
1
  import { MenuItem } from "../../config/types";
3
2
  export function SimpleMenu({
4
3
  items,
@@ -1,6 +1,6 @@
1
- import React, { useState, useRef, useEffect, useCallback } from "react";
1
+ import React, { useState, useRef, useEffect, useLayoutEffect } from "react";
2
2
  import { flushSync } from "react-dom";
3
-
3
+ import { cn } from "../../lib/utils";
4
4
  export type SplitterPanel = {
5
5
  defaultSize: number | "auto";
6
6
  name: string;
@@ -68,17 +68,104 @@ export const Splitter: React.FC<SplitterProps> = ({
68
68
 
69
69
  const [isResizing, setIsResizing] = useState(false);
70
70
 
71
- useEffect(() => {
72
- if (!panelSizes && splitterRef.current) {
73
- const initialSizes: PanelSizes = {};
71
+ useLayoutEffect(() => {
72
+ if (panelSizes || !splitterRef.current) return;
74
73
 
75
- panels.forEach((panel) => {
76
- initialSizes[panel.name] = panel.defaultSize;
77
- });
74
+ const container = splitterRef.current;
75
+ const isHorizontal = direction === "horizontal";
76
+
77
+ const computeAndSet = (containerSize: number) => {
78
+ const visiblePanels = panels.filter((p) => !p.hidden);
79
+ if (visiblePanels.length === 0) return;
80
+
81
+ const minPanelSize = 50;
82
+
83
+ const fixedPanels = visiblePanels.filter(
84
+ (p) => typeof p.defaultSize === "number",
85
+ );
86
+ const autoPanels = visiblePanels.filter((p) => p.defaultSize === "auto");
87
+
88
+ const sumFixed = fixedPanels.reduce((sum, p) => {
89
+ const size = p.defaultSize as number;
90
+ return sum + (typeof size === "number" ? size : 0);
91
+ }, 0);
92
+
93
+ const updated: PanelSizes = {};
94
+
95
+ if (autoPanels.length === 0) {
96
+ const scale =
97
+ containerSize > 0 && sumFixed > 0 ? containerSize / sumFixed : 1;
98
+ visiblePanels.forEach((p) => {
99
+ updated[p.name] = Math.max(
100
+ minPanelSize,
101
+ Math.round((p.defaultSize as number) * scale),
102
+ );
103
+ });
104
+ } else if (containerSize > 0) {
105
+ const minAutoTotal = autoPanels.length * minPanelSize;
106
+ const remainder = containerSize - sumFixed;
107
+
108
+ if (remainder >= minAutoTotal) {
109
+ const per = Math.floor(remainder / autoPanels.length);
110
+ fixedPanels.forEach((p) => {
111
+ updated[p.name] = p.defaultSize as number;
112
+ });
113
+ autoPanels.forEach((p) => {
114
+ updated[p.name] = per;
115
+ });
116
+ } else {
117
+ const availableForFixed = Math.max(0, containerSize - minAutoTotal);
118
+ const scale = sumFixed > 0 ? availableForFixed / sumFixed : 0;
119
+
120
+ fixedPanels.forEach((p) => {
121
+ updated[p.name] = Math.max(
122
+ minPanelSize,
123
+ Math.round((p.defaultSize as number) * scale),
124
+ );
125
+ });
126
+ autoPanels.forEach((p) => {
127
+ updated[p.name] = minPanelSize;
128
+ });
129
+ }
130
+ } else {
131
+ // Fallback when container size is 0
132
+ fixedPanels.forEach((p) => {
133
+ updated[p.name] = p.defaultSize as number;
134
+ });
135
+ autoPanels.forEach((p) => {
136
+ updated[p.name] = minPanelSize;
137
+ });
138
+ }
139
+
140
+ setPanelSizes(updated);
141
+ };
142
+
143
+ const initialSize = isHorizontal
144
+ ? container.clientWidth
145
+ : container.clientHeight;
78
146
 
79
- setPanelSizes(initialSizes);
147
+ if (initialSize > 0) {
148
+ computeAndSet(initialSize);
149
+ return;
80
150
  }
81
- }, [panels]);
151
+
152
+ const ro = new ResizeObserver((entries) => {
153
+ for (const entry of entries) {
154
+ const size = isHorizontal
155
+ ? entry.contentRect.width
156
+ : entry.contentRect.height;
157
+ if (size > 0) {
158
+ computeAndSet(size);
159
+ ro.disconnect();
160
+ break;
161
+ }
162
+ }
163
+ });
164
+ ro.observe(container);
165
+ return () => {
166
+ ro.disconnect();
167
+ };
168
+ }, [panels, panelSizes, direction]);
82
169
 
83
170
  useEffect(() => {
84
171
  if (panelSizes) {
@@ -191,23 +278,22 @@ export const Splitter: React.FC<SplitterProps> = ({
191
278
  const isLastResizer = (index: number) => index === panels.length - 2;
192
279
 
193
280
  const getFlexBasis = (index: number): string => {
194
- if (!panelSizes) return "1fr"; // Or a default like "1fr"
281
+ // Fractional sizing: always use 0 basis so flex-grow weights control size
282
+ return "0px";
283
+ };
195
284
 
285
+ const getFlexGrow = (index: number): number => {
196
286
  const panelName = panels[index]!.name;
197
- const size =
287
+ const raw =
198
288
  index === panels.length - 1 &&
199
289
  isLastPanelCollapsed &&
200
290
  lastPanelCollapsible
201
291
  ? 0
202
- : panelSizes[panelName] || panels[index]!.defaultSize;
203
-
204
- if (typeof size === "number") {
205
- return `${size}px`;
206
- } else if (size === "auto") {
207
- return "auto";
208
- }
292
+ : (panelSizes?.[panelName] ?? panels[index]!.defaultSize);
209
293
 
210
- return "1fr"; //default
294
+ if (raw === "auto") return 1;
295
+ if (typeof raw === "number") return Math.max(0, raw);
296
+ return 1;
211
297
  };
212
298
 
213
299
  const getExpandButton = () => {
@@ -455,7 +541,7 @@ export const Splitter: React.FC<SplitterProps> = ({
455
541
 
456
542
  return (
457
543
  <div
458
- className={`flex ${
544
+ className={`flex items-stretch justify-stretch ${
459
545
  direction === "horizontal" ? "flex-row" : "flex-col"
460
546
  } ${
461
547
  className && /(^|\s)h-/.test(className) ? "" : "h-full"
@@ -468,11 +554,9 @@ export const Splitter: React.FC<SplitterProps> = ({
468
554
  ref={(el) => {
469
555
  panelRefs.current[index] = el;
470
556
  }}
471
- className={`relative ${panel.className || ""}`}
557
+ className={cn(`relative ${panel.className || ""}`)}
472
558
  style={{
473
- flex: `${panel.defaultSize === "auto" ? 1 : 0} 1 ${getFlexBasis(
474
- index,
475
- )}`,
559
+ flex: `${getFlexGrow(index)} 1 ${getFlexBasis(index)}`,
476
560
  zIndex: panels.length - index,
477
561
  minWidth: direction === "horizontal" ? 0 : undefined,
478
562
  minHeight: direction === "vertical" ? 0 : undefined,
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4110";
2
- export const buildDate = "2025-09-25 02:03:12";
1
+ export const version = "1.0.4112";
2
+ export const buildDate = "2025-09-25 12:32:26";
@@ -1,4 +0,0 @@
1
- export declare function useGlobalEditorEvents(params: {
2
- onKeyDown: (ev: KeyboardEvent) => void;
3
- onWindowClick: () => void;
4
- }): void;
@@ -1,15 +0,0 @@
1
- import { useEffect } from "react";
2
- export function useGlobalEditorEvents(params) {
3
- const { onKeyDown, onWindowClick } = params;
4
- useEffect(() => {
5
- if (typeof window === "undefined")
6
- return;
7
- window.addEventListener("keydown", onKeyDown, true);
8
- window.addEventListener("click", onWindowClick, true);
9
- return () => {
10
- window.removeEventListener("keydown", onKeyDown, true);
11
- window.removeEventListener("click", onWindowClick, true);
12
- };
13
- }, [onKeyDown, onWindowClick]);
14
- }
15
- //# sourceMappingURL=useGlobalEditorEvents.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useGlobalEditorEvents.js","sourceRoot":"","sources":["../../../../src/editor/client/hooks/useGlobalEditorEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,UAAU,qBAAqB,CAAC,MAGrC;IACC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAE5C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAC1C,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QACpD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QACtD,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;YACvD,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QAC3D,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;AACjC,CAAC"}
@@ -1,18 +0,0 @@
1
- import { useEffect } from "react";
2
-
3
- export function useGlobalEditorEvents(params: {
4
- onKeyDown: (ev: KeyboardEvent) => void;
5
- onWindowClick: () => void;
6
- }) {
7
- const { onKeyDown, onWindowClick } = params;
8
-
9
- useEffect(() => {
10
- if (typeof window === "undefined") return;
11
- window.addEventListener("keydown", onKeyDown, true);
12
- window.addEventListener("click", onWindowClick, true);
13
- return () => {
14
- window.removeEventListener("keydown", onKeyDown, true);
15
- window.removeEventListener("click", onWindowClick, true);
16
- };
17
- }, [onKeyDown, onWindowClick]);
18
- }