@almadar/ui 2.51.0 → 2.53.0

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.
@@ -19388,13 +19388,13 @@ var MapViewImpl = React125.lazy(async () => {
19388
19388
  shadowSize: [41, 41]
19389
19389
  });
19390
19390
  L.Marker.prototype.options.icon = defaultIcon;
19391
- const { useEffect: useEffect87, useRef: useRef87, useCallback: useCallback124, useState: useState124 } = React125__namespace.default;
19391
+ const { useEffect: useEffect88, useRef: useRef88, useCallback: useCallback124, useState: useState124 } = React125__namespace.default;
19392
19392
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
19393
19393
  const { useEventBus: useEventBus3 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
19394
19394
  function MapUpdater({ centerLat, centerLng, zoom }) {
19395
19395
  const map = useMap();
19396
- const prevRef = useRef87({ centerLat, centerLng, zoom });
19397
- useEffect87(() => {
19396
+ const prevRef = useRef88({ centerLat, centerLng, zoom });
19397
+ useEffect88(() => {
19398
19398
  const prev = prevRef.current;
19399
19399
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
19400
19400
  map.setView([centerLat, centerLng], zoom);
@@ -19405,7 +19405,7 @@ var MapViewImpl = React125.lazy(async () => {
19405
19405
  }
19406
19406
  function MapClickHandler({ onMapClick }) {
19407
19407
  const map = useMap();
19408
- useEffect87(() => {
19408
+ useEffect88(() => {
19409
19409
  if (!onMapClick) return;
19410
19410
  const handler = (e) => {
19411
19411
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -22132,6 +22132,26 @@ var CodeBlock = React125__namespace.default.memo(
22132
22132
  const codeRef = React125.useRef(null);
22133
22133
  const savedScrollLeftRef = React125.useRef(0);
22134
22134
  const [copied, setCopied] = React125.useState(false);
22135
+ const [editableValue, setEditableValue] = React125.useState(code);
22136
+ const [editableTextareaKey, setEditableTextareaKey] = React125.useState(0);
22137
+ const lastPropCodeRef = React125.useRef(code);
22138
+ const editableTextareaRef = React125.useRef(null);
22139
+ const editableOverlayRef = React125.useRef(null);
22140
+ React125.useEffect(() => {
22141
+ if (code !== lastPropCodeRef.current) {
22142
+ lastPropCodeRef.current = code;
22143
+ setEditableValue(code);
22144
+ setEditableTextareaKey((k) => k + 1);
22145
+ }
22146
+ }, [code]);
22147
+ const handleEditableScroll = React125.useCallback(() => {
22148
+ const ta = editableTextareaRef.current;
22149
+ const ov = editableOverlayRef.current;
22150
+ if (ta && ov) {
22151
+ ov.scrollTop = ta.scrollTop;
22152
+ ov.scrollLeft = ta.scrollLeft;
22153
+ }
22154
+ }, []);
22135
22155
  const isFoldable = foldableProp ?? (language === "orb" || language === "json");
22136
22156
  const [collapsed, setCollapsed] = React125.useState(() => /* @__PURE__ */ new Set());
22137
22157
  const foldRegions = React125.useMemo(
@@ -22292,33 +22312,106 @@ var CodeBlock = React125__namespace.default.memo(
22292
22312
  }
22293
22313
  ),
22294
22314
  editable ? (
22295
- /* GAP-51: editable mode composes the Textarea atom. Plain text editing,
22296
- no Prism highlighting overlay (follow-up). The textarea is uncontrolled
22297
- on the value side: we pass `code` as the initial value and forward
22298
- every keystroke via onChange the consumer is responsible for
22299
- debouncing and re-deriving `code` only after the user stops typing
22300
- so the cursor doesn't fight a re-render. */
22301
- /* @__PURE__ */ jsxRuntime.jsx(
22302
- Textarea,
22315
+ /* GAP-77: editable mode = transparent Textarea on top + Prism-highlighted
22316
+ overlay underneath. The textarea is uncontrolled (defaultValue + key)
22317
+ to avoid cursor jumps; the overlay reads `editableValue` which is
22318
+ mirrored from the textarea via onChange. Both elements share IDENTICAL
22319
+ font / line-height / padding so the highlighted text aligns with the
22320
+ textarea's invisible glyphs.
22321
+
22322
+ Scroll sync: the overlay has `pointer-events: none` and the textarea
22323
+ scrolls; `handleEditableScroll` keeps the overlay's scroll matched. */
22324
+ /* @__PURE__ */ jsxRuntime.jsxs(
22325
+ Box,
22303
22326
  {
22304
- defaultValue: code,
22305
- onChange: (e) => onChange?.(e.target.value),
22306
- spellCheck: false,
22307
22327
  style: {
22308
- fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22309
- fontSize: "13px",
22310
- lineHeight: "1.5",
22328
+ position: "relative",
22311
22329
  backgroundColor: "#1e1e1e",
22312
- color: "#e6e6e6",
22313
22330
  borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22314
- border: "none",
22315
- padding: "1rem",
22316
- resize: "none",
22317
22331
  minHeight: "160px",
22318
22332
  maxHeight,
22319
- width: "100%",
22320
- outline: "none"
22321
- }
22333
+ overflow: "hidden"
22334
+ },
22335
+ children: [
22336
+ /* @__PURE__ */ jsxRuntime.jsx(
22337
+ "div",
22338
+ {
22339
+ ref: editableOverlayRef,
22340
+ "aria-hidden": true,
22341
+ style: {
22342
+ position: "absolute",
22343
+ inset: 0,
22344
+ overflow: "hidden",
22345
+ pointerEvents: "none",
22346
+ maxHeight
22347
+ },
22348
+ children: /* @__PURE__ */ jsxRuntime.jsx(
22349
+ SyntaxHighlighter__default.default,
22350
+ {
22351
+ PreTag: "div",
22352
+ language,
22353
+ style: activeStyle,
22354
+ customStyle: {
22355
+ backgroundColor: "transparent",
22356
+ borderRadius: 0,
22357
+ padding: "1rem",
22358
+ margin: 0,
22359
+ whiteSpace: "pre",
22360
+ minWidth: "100%",
22361
+ minHeight: "160px",
22362
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22363
+ fontSize: "13px",
22364
+ lineHeight: "1.5"
22365
+ },
22366
+ codeTagProps: {
22367
+ style: {
22368
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22369
+ fontSize: "13px",
22370
+ lineHeight: "1.5"
22371
+ }
22372
+ },
22373
+ children: editableValue || " "
22374
+ }
22375
+ )
22376
+ }
22377
+ ),
22378
+ /* @__PURE__ */ jsxRuntime.jsx(
22379
+ Textarea,
22380
+ {
22381
+ ref: editableTextareaRef,
22382
+ defaultValue: code,
22383
+ onChange: (e) => {
22384
+ setEditableValue(e.target.value);
22385
+ onChange?.(e.target.value);
22386
+ },
22387
+ onScroll: handleEditableScroll,
22388
+ spellCheck: false,
22389
+ style: {
22390
+ position: "relative",
22391
+ zIndex: 1,
22392
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
22393
+ fontSize: "13px",
22394
+ lineHeight: "1.5",
22395
+ backgroundColor: "transparent",
22396
+ color: "transparent",
22397
+ caretColor: "#e6e6e6",
22398
+ borderRadius: hasHeader ? "0 0 0.5rem 0.5rem" : "0.5rem",
22399
+ border: "none",
22400
+ padding: "1rem",
22401
+ resize: "none",
22402
+ minHeight: "160px",
22403
+ maxHeight,
22404
+ width: "100%",
22405
+ height: "100%",
22406
+ outline: "none",
22407
+ whiteSpace: "pre",
22408
+ overflowWrap: "normal",
22409
+ overflow: "auto"
22410
+ }
22411
+ },
22412
+ editableTextareaKey
22413
+ )
22414
+ ]
22322
22415
  }
22323
22416
  )
22324
22417
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -47775,12 +47868,18 @@ var OrbPreviewNodeInner = (props) => {
47775
47868
  el = el.parentElement;
47776
47869
  if (!el || el === contentRef.current) break;
47777
47870
  }
47871
+ const containerNode = {
47872
+ orbitalName: data.orbitalName,
47873
+ traitName: data.traitName,
47874
+ transitionEvent: data.transitionEvent
47875
+ };
47778
47876
  const containerPath = el?.dataset?.patternPath;
47779
47877
  if (!containerPath) {
47780
- eventBus.emit("UI:PATTERN_INSERT", {
47878
+ eventBus.emit("UI:PATTERN_DROP", {
47781
47879
  parentPath: "root",
47782
47880
  patternType: payload.data.type,
47783
- index: 0
47881
+ index: 0,
47882
+ containerNode
47784
47883
  });
47785
47884
  return;
47786
47885
  }
@@ -47797,12 +47896,13 @@ var OrbPreviewNodeInner = (props) => {
47797
47896
  break;
47798
47897
  }
47799
47898
  }
47800
- eventBus.emit("UI:PATTERN_INSERT", {
47899
+ eventBus.emit("UI:PATTERN_DROP", {
47801
47900
  parentPath: containerPath,
47802
47901
  patternType: payload.data.type,
47803
- index: insertIndex
47902
+ index: insertIndex,
47903
+ containerNode
47804
47904
  });
47805
- }, [eventBus]);
47905
+ }, [eventBus, data.orbitalName, data.traitName, data.transitionEvent]);
47806
47906
  const handlePreviewDragOver = React125.useCallback((e) => {
47807
47907
  if (!e.dataTransfer.types.includes(ALMADAR_DND_MIME)) return;
47808
47908
  e.preventDefault();
@@ -48442,7 +48542,7 @@ function OrbInspector({ node, schema, editable = false, onSchemaChange, onClose
48442
48542
  onBlur: (e) => handlePropChange(propName, e.target.value)
48443
48543
  }
48444
48544
  ) : /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "text-[11px] text-muted-foreground", children: [
48445
- displayValue || (ps.types?.join(" | ") ?? "string"),
48545
+ displayValue || "\u2014",
48446
48546
  ps.required ? " *" : ""
48447
48547
  ] })
48448
48548
  ] }, propName);
@@ -48713,6 +48813,7 @@ function FlowCanvasInner({
48713
48813
  onNodeClick,
48714
48814
  onLevelChange,
48715
48815
  onOrbitalDoubleClick,
48816
+ cosmicEntryLevel = "expanded",
48716
48817
  initialOrbital,
48717
48818
  initialLevel,
48718
48819
  initialSelectedNode,
@@ -48786,19 +48887,24 @@ function FlowCanvasInner({
48786
48887
  }
48787
48888
  if (level === "overview") {
48788
48889
  const d = node.data;
48789
- setExpandedOrbital(d.orbitalName ?? node.id);
48890
+ const orbitalName = d.orbitalName ?? node.id;
48891
+ if (onOrbitalDoubleClick && (cosmicEntryLevel === "overview" || cosmicEntryLevel === "both")) {
48892
+ onOrbitalDoubleClick(orbitalName);
48893
+ return;
48894
+ }
48895
+ setExpandedOrbital(orbitalName);
48790
48896
  setLevel("expanded");
48791
- onLevelChange?.("expanded", d.orbitalName ?? node.id);
48897
+ onLevelChange?.("expanded", orbitalName);
48792
48898
  return;
48793
48899
  }
48794
48900
  if (level === "expanded") {
48795
48901
  const d = node.data;
48796
48902
  const orbitalName = d.orbitalName ?? node.id;
48797
- if (orbitalName && onOrbitalDoubleClick) {
48903
+ if (orbitalName && onOrbitalDoubleClick && (cosmicEntryLevel === "expanded" || cosmicEntryLevel === "both")) {
48798
48904
  onOrbitalDoubleClick(orbitalName);
48799
48905
  }
48800
48906
  }
48801
- }, [level, onLevelChange, onOrbitalDoubleClick, atBehaviorLevel, composeLevel]);
48907
+ }, [level, onLevelChange, onOrbitalDoubleClick, cosmicEntryLevel, atBehaviorLevel, composeLevel]);
48802
48908
  const handleNodeClick = React125.useCallback((_, node) => {
48803
48909
  const nodeData = node.data;
48804
48910
  if (level === "expanded") {
@@ -49334,7 +49440,10 @@ var AvlOrbitalsCosmicZoom = ({
49334
49440
  animated = true,
49335
49441
  width = "100%",
49336
49442
  height = 450,
49337
- highlightedOrbital
49443
+ highlightedOrbital,
49444
+ onOrbitalSelect,
49445
+ minZoom = 0.4,
49446
+ maxZoom = 3
49338
49447
  }) => {
49339
49448
  const parsedSchema = React125.useMemo(() => {
49340
49449
  if (typeof schemaProp === "string") return JSON.parse(schemaProp);
@@ -49365,10 +49474,83 @@ var AvlOrbitalsCosmicZoom = ({
49365
49474
  );
49366
49475
  const [selected, setSelected] = React125.useState(null);
49367
49476
  const handleSelect = React125.useCallback(
49368
- (name) => setSelected((prev) => prev === name ? null : name),
49369
- []
49477
+ (name) => {
49478
+ setSelected((prev) => prev === name ? null : name);
49479
+ onOrbitalSelect?.(name);
49480
+ },
49481
+ [onOrbitalSelect]
49370
49482
  );
49371
49483
  const selectedView = orbitalViews.find((o) => o.name === selected);
49484
+ const [zoom, setZoom] = React125.useState(1);
49485
+ const [pan, setPan] = React125.useState({ x: 0, y: 0 });
49486
+ const dragStateRef = React125.useRef(null);
49487
+ const transformWrapperRef = React125.useRef(null);
49488
+ const clampZoom = React125.useCallback(
49489
+ (z) => Math.max(minZoom, Math.min(maxZoom, z)),
49490
+ [minZoom, maxZoom]
49491
+ );
49492
+ const handlePointerDown = React125.useCallback((e) => {
49493
+ if (e.target.closest("[data-orbital-tile]")) return;
49494
+ dragStateRef.current = {
49495
+ startX: e.clientX,
49496
+ startY: e.clientY,
49497
+ panX: pan.x,
49498
+ panY: pan.y
49499
+ };
49500
+ e.target.setPointerCapture(e.pointerId);
49501
+ }, [pan]);
49502
+ const handlePointerMove = React125.useCallback((e) => {
49503
+ const drag = dragStateRef.current;
49504
+ if (!drag) return;
49505
+ setPan({
49506
+ x: drag.panX + (e.clientX - drag.startX),
49507
+ y: drag.panY + (e.clientY - drag.startY)
49508
+ });
49509
+ }, []);
49510
+ const handlePointerUp = React125.useCallback((e) => {
49511
+ if (!dragStateRef.current) return;
49512
+ dragStateRef.current = null;
49513
+ try {
49514
+ e.target.releasePointerCapture(e.pointerId);
49515
+ } catch {
49516
+ }
49517
+ }, []);
49518
+ const panRef = React125.useRef(pan);
49519
+ const zoomRef = React125.useRef(zoom);
49520
+ React125.useEffect(() => {
49521
+ panRef.current = pan;
49522
+ }, [pan]);
49523
+ React125.useEffect(() => {
49524
+ zoomRef.current = zoom;
49525
+ }, [zoom]);
49526
+ React125.useEffect(() => {
49527
+ const wrapper = transformWrapperRef.current;
49528
+ if (!wrapper) return;
49529
+ const wheelListener = (e) => {
49530
+ e.preventDefault();
49531
+ const rect = wrapper.getBoundingClientRect();
49532
+ const cursorX = e.clientX - rect.left;
49533
+ const cursorY = e.clientY - rect.top;
49534
+ const currentZoom = zoomRef.current;
49535
+ const currentPan = panRef.current;
49536
+ const worldX = (cursorX - currentPan.x) / currentZoom;
49537
+ const worldY = (cursorY - currentPan.y) / currentZoom;
49538
+ const delta = e.deltaY > 0 ? -0.1 : 0.1;
49539
+ const nextZoom = clampZoom(currentZoom * (1 + delta));
49540
+ const nextPanX = cursorX - worldX * nextZoom;
49541
+ const nextPanY = cursorY - worldY * nextZoom;
49542
+ setZoom(nextZoom);
49543
+ setPan({ x: nextPanX, y: nextPanY });
49544
+ };
49545
+ wrapper.addEventListener("wheel", wheelListener, { passive: false });
49546
+ return () => wrapper.removeEventListener("wheel", wheelListener);
49547
+ }, [clampZoom]);
49548
+ const zoomIn = React125.useCallback(() => setZoom((z) => clampZoom(z * 1.2)), [clampZoom]);
49549
+ const zoomOut = React125.useCallback(() => setZoom((z) => clampZoom(z / 1.2)), [clampZoom]);
49550
+ const resetZoom = React125.useCallback(() => {
49551
+ setZoom(1);
49552
+ setPan({ x: 0, y: 0 });
49553
+ }, []);
49372
49554
  return /* @__PURE__ */ jsxRuntime.jsxs(
49373
49555
  Box,
49374
49556
  {
@@ -49378,65 +49560,115 @@ var AvlOrbitalsCosmicZoom = ({
49378
49560
  style: { width, height: containerH },
49379
49561
  children: [
49380
49562
  /* @__PURE__ */ jsxRuntime.jsx(
49381
- EventWireOverlay,
49563
+ "div",
49382
49564
  {
49383
- orbitalViews,
49384
- crossLinks,
49385
- color,
49386
- animated,
49387
- containerW,
49388
- containerH
49389
- }
49390
- ),
49391
- orbitalViews.map((view) => {
49392
- const isHighlighted = view.name === highlightedOrbital;
49393
- return /* @__PURE__ */ jsxRuntime.jsx(
49394
- Box,
49395
- {
49396
- role: "button",
49397
- tabIndex: 0,
49398
- onClick: () => handleSelect(view.name),
49399
- onKeyDown: (e) => {
49400
- if (e.key === "Enter" || e.key === " ") handleSelect(view.name);
49401
- },
49402
- "aria-label": `Orbital: ${view.name}${isHighlighted ? " (highlighted)" : ""}`,
49565
+ ref: transformWrapperRef,
49566
+ onPointerDown: handlePointerDown,
49567
+ onPointerMove: handlePointerMove,
49568
+ onPointerUp: handlePointerUp,
49569
+ onPointerCancel: handlePointerUp,
49570
+ style: {
49403
49571
  position: "absolute",
49404
- style: {
49405
- left: view.cx - UNIT_DISPLAY_W / 2,
49406
- top: view.cy - UNIT_DISPLAY_H / 2,
49407
- width: UNIT_DISPLAY_W,
49408
- height: UNIT_DISPLAY_H,
49409
- cursor: "pointer",
49410
- transition: "transform 0.2s ease, filter 0.2s ease, box-shadow 0.3s ease",
49411
- transform: selected === view.name ? "scale(1.05)" : "scale(1)",
49412
- filter: selected && selected !== view.name ? "opacity(0.5)" : "none",
49413
- // GAP-52: persistent highlight ring (independent from user selection)
49414
- boxShadow: isHighlighted ? `0 0 0 3px ${color}, 0 0 24px 4px ${color}` : "none",
49415
- borderRadius: isHighlighted ? "12px" : void 0,
49416
- zIndex: isHighlighted ? 11 : selected === view.name ? 10 : 1
49417
- },
49418
- children: /* @__PURE__ */ jsxRuntime.jsx(
49419
- AvlOrbitalUnit,
49420
- {
49421
- entityName: view.entityName,
49422
- fields: view.fieldCount,
49423
- persistence: view.persistence,
49424
- traits: view.traits,
49425
- pages: view.pages,
49426
- color,
49427
- animated: animated && (selected === view.name || isHighlighted)
49428
- }
49429
- )
49572
+ inset: 0,
49573
+ overflow: "hidden",
49574
+ cursor: dragStateRef.current ? "grabbing" : "grab",
49575
+ touchAction: "none"
49430
49576
  },
49431
- view.name
49432
- );
49433
- }),
49434
- selectedView && /* @__PURE__ */ jsxRuntime.jsx(
49435
- InfoPanel,
49577
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
49578
+ "div",
49579
+ {
49580
+ style: {
49581
+ position: "absolute",
49582
+ inset: 0,
49583
+ transform: `translate(${pan.x}px, ${pan.y}px) scale(${zoom})`,
49584
+ transformOrigin: "0 0"
49585
+ },
49586
+ children: [
49587
+ /* @__PURE__ */ jsxRuntime.jsx(
49588
+ EventWireOverlay,
49589
+ {
49590
+ orbitalViews,
49591
+ crossLinks,
49592
+ color,
49593
+ animated,
49594
+ containerW,
49595
+ containerH
49596
+ }
49597
+ ),
49598
+ orbitalViews.map((view) => {
49599
+ const isHighlighted = view.name === highlightedOrbital;
49600
+ return /* @__PURE__ */ jsxRuntime.jsx(
49601
+ Box,
49602
+ {
49603
+ role: "button",
49604
+ tabIndex: 0,
49605
+ "data-orbital-tile": "true",
49606
+ onClick: () => handleSelect(view.name),
49607
+ onKeyDown: (e) => {
49608
+ if (e.key === "Enter" || e.key === " ") handleSelect(view.name);
49609
+ },
49610
+ "aria-label": `Orbital: ${view.name}${isHighlighted ? " (highlighted)" : ""}`,
49611
+ position: "absolute",
49612
+ style: {
49613
+ left: view.cx - UNIT_DISPLAY_W / 2,
49614
+ top: view.cy - UNIT_DISPLAY_H / 2,
49615
+ width: UNIT_DISPLAY_W,
49616
+ height: UNIT_DISPLAY_H,
49617
+ cursor: "pointer",
49618
+ transition: "transform 0.2s ease, filter 0.2s ease, box-shadow 0.3s ease",
49619
+ transform: selected === view.name ? "scale(1.05)" : "scale(1)",
49620
+ filter: selected && selected !== view.name ? "opacity(0.5)" : "none",
49621
+ // GAP-52: persistent highlight ring (independent from user selection)
49622
+ boxShadow: isHighlighted ? `0 0 0 3px ${color}, 0 0 24px 4px ${color}` : "none",
49623
+ borderRadius: isHighlighted ? "12px" : void 0,
49624
+ zIndex: isHighlighted ? 11 : selected === view.name ? 10 : 1
49625
+ },
49626
+ children: /* @__PURE__ */ jsxRuntime.jsx(
49627
+ AvlOrbitalUnit,
49628
+ {
49629
+ entityName: view.entityName,
49630
+ fields: view.fieldCount,
49631
+ persistence: view.persistence,
49632
+ traits: view.traits,
49633
+ pages: view.pages,
49634
+ color,
49635
+ animated: animated && (selected === view.name || isHighlighted)
49636
+ }
49637
+ )
49638
+ },
49639
+ view.name
49640
+ );
49641
+ }),
49642
+ selectedView && /* @__PURE__ */ jsxRuntime.jsx(
49643
+ InfoPanel,
49644
+ {
49645
+ view: selectedView,
49646
+ crossLinks,
49647
+ color
49648
+ }
49649
+ )
49650
+ ]
49651
+ }
49652
+ )
49653
+ }
49654
+ ),
49655
+ /* @__PURE__ */ jsxRuntime.jsxs(
49656
+ Box,
49436
49657
  {
49437
- view: selectedView,
49438
- crossLinks,
49439
- color
49658
+ position: "absolute",
49659
+ style: {
49660
+ top: 12,
49661
+ right: 12,
49662
+ display: "flex",
49663
+ flexDirection: "column",
49664
+ gap: 4,
49665
+ zIndex: 30
49666
+ },
49667
+ children: [
49668
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", size: "sm", onClick: zoomIn, title: "Zoom in", action: "COSMIC_ZOOM_IN", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "plus", size: "sm" }) }),
49669
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", size: "sm", onClick: zoomOut, title: "Zoom out", action: "COSMIC_ZOOM_OUT", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "minus", size: "sm" }) }),
49670
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "secondary", size: "sm", onClick: resetZoom, title: "Reset", action: "COSMIC_ZOOM_RESET", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "maximize", size: "sm" }) })
49671
+ ]
49440
49672
  }
49441
49673
  )
49442
49674
  ]
@@ -1211,15 +1211,29 @@ interface FlowCanvasProps {
1211
1211
  }) => void;
1212
1212
  onLevelChange?: (level: ViewLevel, orbital?: string) => void;
1213
1213
  /**
1214
- * GAP-52: fired when the user double-clicks an orbital while ALREADY at
1215
- * `level === 'expanded'`. Consumers (e.g. the builder workspace) use this as
1216
- * the trigger to enter cosmic mode (`AvlOrbitalsCosmicZoom`) for the focused
1217
- * orbital. This does NOT replace the existing overview→expanded drill —
1218
- * that path still fires `onLevelChange('expanded', ...)` as before.
1219
- * The callback runs unconditionally; persona / permission gating is the
1214
+ * GAP-52: fired when the user double-clicks an orbital. Consumers (e.g. the
1215
+ * builder workspace) use this as the trigger to enter cosmic mode
1216
+ * (`AvlOrbitalsCosmicZoom`) for the focused orbital.
1217
+ *
1218
+ * The level at which this fires is controlled by `cosmicEntryLevel` (default
1219
+ * `'expanded'`). At `'expanded'` the existing overview→expanded drill is
1220
+ * preserved — the callback fires only on the second double-click. At
1221
+ * `'overview'` the callback fires on the FIRST double-click and the existing
1222
+ * drill is suppressed for that interaction. `'both'` fires at either level.
1223
+ *
1224
+ * The callback runs unconditionally — persona / permission gating is the
1220
1225
  * consumer's responsibility.
1221
1226
  */
1222
1227
  onOrbitalDoubleClick?: (orbital: string) => void;
1228
+ /**
1229
+ * GAP-53: which level the `onOrbitalDoubleClick` callback fires at.
1230
+ * - `'expanded'` (default, non-breaking) — fires only at L2 expanded; the
1231
+ * first overview double-click still drills overview→expanded.
1232
+ * - `'overview'` — fires at L1 overview on the FIRST double-click. The
1233
+ * overview→expanded drill is suppressed when the callback is provided.
1234
+ * - `'both'` — fires at either level.
1235
+ */
1236
+ cosmicEntryLevel?: 'expanded' | 'overview' | 'both';
1223
1237
  initialOrbital?: string;
1224
1238
  /** Start at Level 2 (expanded) when initialOrbital is set. Default: 'overview'. */
1225
1239
  initialLevel?: ViewLevel;
@@ -1385,6 +1399,22 @@ interface AvlOrbitalsCosmicZoomProps {
1385
1399
  * highlighted while the user can still click any other orbital to select it.
1386
1400
  */
1387
1401
  highlightedOrbital?: string;
1402
+ /**
1403
+ * GAP-55: fired when the user clicks an orbital tile. Consumers (e.g. the
1404
+ * builder workspace) use this as the trigger to drill INTO the clicked
1405
+ * orbital — typically by switching back to the canvas tab and opening the
1406
+ * clicked orbital at L2 expanded. Local `selected` toggle (visual highlight +
1407
+ * info panel) still fires regardless of whether the callback is provided.
1408
+ */
1409
+ onOrbitalSelect?: (orbital: string) => void;
1410
+ /**
1411
+ * GAP-54: minimum zoom factor when scroll-wheel zooming. Default 0.4.
1412
+ */
1413
+ minZoom?: number;
1414
+ /**
1415
+ * GAP-54: maximum zoom factor when scroll-wheel zooming. Default 3.
1416
+ */
1417
+ maxZoom?: number;
1388
1418
  }
1389
1419
  declare const AvlOrbitalsCosmicZoom: React__default.FC<AvlOrbitalsCosmicZoomProps>;
1390
1420