@ceebee/ui 1.5.4 → 1.7.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.
@@ -20,6 +20,14 @@ Copyright © 2015-present Ant UED
20
20
  Icon components come from `@ant-design/icons` 6.x, a peer dependency. It is licensed under the MIT
21
21
  License.
22
22
 
23
+ ## React Flow (@xyflow/react)
24
+
25
+ Copyright © 2019-present webkid GmbH
26
+
27
+ `Diagram` and `DiagramEditor` in `@ceebee/ui/client` are built on the `@xyflow/react` 12.11.6 runtime,
28
+ a dependency. Its base stylesheet is included in `styles.css`, and its appearance is set by Ceebee's
29
+ Tokens. React Flow is licensed under the MIT License, and its copyright notice is retained here.
30
+
23
31
  ## dayjs
24
32
 
25
33
  Copyright © 2018-present iamkun
package/dist/client.d.ts CHANGED
@@ -11,6 +11,7 @@ export { default as enUSDatePickerLocale } from 'antd/lib/date-picker/locale/en_
11
11
  export { default as idIDLocale } from 'antd/locale/id_ID.js';
12
12
  export { default as zhCNLocale } from 'antd/locale/zh_CN.js';
13
13
  import * as _base_ui_react from '@base-ui/react';
14
+ import { ReactFlowProps } from '@xyflow/react';
14
15
 
15
16
  interface ModalProps extends ModalProps$1 {
16
17
  /** Separate explanatory copy announced after the dialog title. */
@@ -367,11 +368,103 @@ interface PanZoomCanvasProps {
367
368
  zoomOutLabel?: string;
368
369
  resetLabel?: string;
369
370
  zoomStatusLabel?: string;
371
+ fullscreenLabel?: string;
372
+ exitFullscreenLabel?: string;
370
373
  motion?: boolean;
371
374
  }
372
- declare function PanZoomCanvasRoot({ label, children, hint, zoomInLabel, zoomOutLabel, resetLabel, zoomStatusLabel, motion: motionEnabled, }: PanZoomCanvasProps): react.JSX.Element;
375
+ declare function PanZoomCanvasRoot({ label, children, hint, zoomInLabel, zoomOutLabel, resetLabel, zoomStatusLabel, fullscreenLabel, exitFullscreenLabel, motion: motionEnabled, }: PanZoomCanvasProps): react.JSX.Element;
373
376
  declare const PanZoomCanvas: typeof PanZoomCanvasRoot & {
374
377
  Skeleton: typeof PanZoomCanvasSkeleton;
375
378
  };
376
379
 
377
- export { type CeebeeAntStyleCache, CeebeeAntStyleProvider, type CeebeeAntStyleProviderProps, Checklist, type ChecklistProps, type ChecklistTask, type Command, CommandPalette, type CommandPaletteProps, DEFAULT_LABELS, type DurationToken, type Labels, LabelsProvider, type LabelsProviderProps, Modal, type ModalProps, type MotionHelpers, MotionProvider, type MotionProviderProps, type MotionSettings, type NavItem, type NavSection, type PaletteCommand, PanZoomCanvas, type PanZoomCanvasProps, type PanZoomCanvasSkeletonProps, type RankedCommand, Reveal, type RevealProps, Sidebar, type SidebarProps, type SpringPreset, Stagger, type StaggerProps, StickerGroup, type StickerGroupProps, type StickerGroupSkeletonProps, type StickerItem, ThemeBridge, type ThemeBridgeProps, type ThemeChoice, ThemeProvider, type ThemeProviderProps, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, TopBar, type TopBarProps, createCeebeeAntStyleCache, extractCeebeeAntStyles, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
380
+ interface DiagramSkeletonProps {
381
+ label?: string;
382
+ }
383
+ declare function DiagramSkeleton({ label }: DiagramSkeletonProps): react.JSX.Element;
384
+
385
+ interface DiagramPosition {
386
+ x: number;
387
+ y: number;
388
+ }
389
+ type DiagramShape = 'rect' | 'pill' | 'diamond';
390
+ interface DiagramNode {
391
+ id: string;
392
+ label: string;
393
+ /** Grid cells from the diagram's origin. The caller owns and stores positions. */
394
+ position: DiagramPosition;
395
+ shape?: DiagramShape;
396
+ tone?: Tone;
397
+ /** Static supplementary content under the label. It must not be interactive. */
398
+ content?: ReactNode;
399
+ }
400
+ interface DiagramEdge {
401
+ id: string;
402
+ from: string;
403
+ to: string;
404
+ label?: string;
405
+ tone?: Tone;
406
+ }
407
+
408
+ interface DiagramProps {
409
+ /** Accessible name for the diagram viewport. */
410
+ label: string;
411
+ nodes: readonly DiagramNode[];
412
+ edges: readonly DiagramEdge[];
413
+ hint?: string;
414
+ /** Accessible name for the list a screen reader walks instead of the drawing. */
415
+ outlineLabel?: string;
416
+ /** Localised names for the substrate's own controls and descriptions. */
417
+ ariaLabels?: ReactFlowProps['ariaLabelConfig'];
418
+ }
419
+ /**
420
+ * A read-only drawing of nodes joined by arrows, on the React Flow runtime. Pan and zoom are the
421
+ * viewport's; nothing in the drawing is focusable, draggable or selectable. The diagram is also given as an
422
+ * outline list, which the viewport is described by.
423
+ */
424
+ declare function DiagramRoot({ label, nodes, edges, hint, outlineLabel, ariaLabels }: DiagramProps): react.JSX.Element;
425
+ declare const Diagram: typeof DiagramRoot & {
426
+ Skeleton: typeof DiagramSkeleton;
427
+ };
428
+
429
+ interface DiagramRemoval {
430
+ nodeIds: string[];
431
+ edgeIds: string[];
432
+ }
433
+ interface DiagramRenameTarget {
434
+ kind: 'node' | 'edge';
435
+ id: string;
436
+ }
437
+ interface DiagramEditorProps {
438
+ label: string;
439
+ nodes: readonly DiagramNode[];
440
+ edges: readonly DiagramEdge[];
441
+ hint?: string;
442
+ selectedId?: string | null;
443
+ onSelectionChange?: (id: string) => void;
444
+ /** A request, in whole cells: the caller moves the node by updating `nodes`, or does not. */
445
+ onMoveNode?: (id: string, position: DiagramPosition) => void;
446
+ onConnect?: (from: string, to: string) => void;
447
+ /** A request: removing a node names the edges touching it too. Nothing is removed until `nodes`/`edges` change. */
448
+ onRemove?: (removal: DiagramRemoval) => void;
449
+ /** The caller renders the rename input; the editor only says what is to be renamed. */
450
+ onRename?: (target: DiagramRenameTarget) => void;
451
+ connectingStatus?: (nodeLabel: string) => string;
452
+ outlineLabel?: string;
453
+ /** Localised names for the substrate's own controls and keyboard descriptions. */
454
+ ariaLabels?: ReactFlowProps['ariaLabelConfig'];
455
+ }
456
+ /**
457
+ * Draws and edits a diagram on the React Flow runtime. React Flow owns dragging, panning, zooming,
458
+ * selection, focus and its keyboard model; this component keeps the caller in charge of the data by turning
459
+ * every change into a request, and adds keyboard connecting, which the runtime does not offer.
460
+ *
461
+ * Every value handed to the runtime keeps its identity between renders. The runtime pushes any prop whose
462
+ * identity changed into its store after each render, and some of those — the selection callback among them —
463
+ * fire again when they change, so a fresh function or array per render is enough to loop.
464
+ */
465
+ declare function DiagramEditorRoot(props: DiagramEditorProps): react.JSX.Element;
466
+ declare const DiagramEditor: typeof DiagramEditorRoot & {
467
+ Skeleton: typeof DiagramSkeleton;
468
+ };
469
+
470
+ export { type CeebeeAntStyleCache, CeebeeAntStyleProvider, type CeebeeAntStyleProviderProps, Checklist, type ChecklistProps, type ChecklistTask, type Command, CommandPalette, type CommandPaletteProps, DEFAULT_LABELS, Diagram, type DiagramEdge, DiagramEditor, type DiagramEditorProps, type DiagramNode, type DiagramPosition, type DiagramProps, type DiagramRemoval, type DiagramRenameTarget, type DiagramShape, type DiagramSkeletonProps, type DurationToken, type Labels, LabelsProvider, type LabelsProviderProps, Modal, type ModalProps, type MotionHelpers, MotionProvider, type MotionProviderProps, type MotionSettings, type NavItem, type NavSection, type PaletteCommand, PanZoomCanvas, type PanZoomCanvasProps, type PanZoomCanvasSkeletonProps, type RankedCommand, Reveal, type RevealProps, Sidebar, type SidebarProps, type SpringPreset, Stagger, type StaggerProps, StickerGroup, type StickerGroupProps, type StickerGroupSkeletonProps, type StickerItem, ThemeBridge, type ThemeBridgeProps, type ThemeChoice, ThemeProvider, type ThemeProviderProps, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, TopBar, type TopBarProps, createCeebeeAntStyleCache, extractCeebeeAntStyles, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
package/dist/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { Modal as Modal$1, Button, theme, ConfigProvider, Progress } from 'antd';
3
3
  export * from 'antd';
4
- import { createContext, useId, useRef, useCallback, useInsertionEffect, useLayoutEffect, useState, useContext, useMemo, useEffect, Children } from 'react';
4
+ import { createContext, useId, useRef, useCallback, useInsertionEffect, useLayoutEffect, useState, useEffect, useMemo, useContext, Children } from 'react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
  import { StyleProvider, createCache, extractStyle } from '@ant-design/cssinjs';
7
7
  export { default as enUSLocale } from 'antd/locale/en_US.js';
@@ -9,10 +9,11 @@ export { default as enUSDatePickerLocale } from 'antd/lib/date-picker/locale/en_
9
9
  export { default as idIDLocale } from 'antd/locale/id_ID.js';
10
10
  export { default as zhCNLocale } from 'antd/locale/zh_CN.js';
11
11
  import { Dialog } from '@base-ui/react/dialog';
12
- import { X, Minus, Plus, RotateCcw, Search, Check, ChevronRight, PanelLeftOpen, PanelLeftClose, Info, XCircle, AlertTriangle, CheckCircle2 } from 'lucide-react';
12
+ import { X, Minus, Plus, RotateCcw, Minimize2, Maximize2, Search, Check, ChevronRight, PanelLeftOpen, PanelLeftClose, Info, XCircle, AlertTriangle, CheckCircle2 } from 'lucide-react';
13
13
  import { Toast } from '@base-ui/react/toast';
14
14
  import { Popover } from '@base-ui/react/popover';
15
15
  import { AnimatePresence, motion } from 'motion/react';
16
+ import { ReactFlow, ConnectionMode, Background, Controls, applyNodeChanges, MarkerType, Handle, Position } from '@xyflow/react';
16
17
 
17
18
  // src/lib/cn.ts
18
19
  function cn(...parts) {
@@ -662,7 +663,7 @@ var generatedCeebeeAntSeeds = {
662
663
  "colorBgContainer": "rgba(255, 253, 248, 1)",
663
664
  "colorBgElevated": "rgba(255, 255, 255, 1)",
664
665
  "colorBorder": "rgba(83, 84, 97, 1)",
665
- "colorBorderSecondary": "rgba(156, 158, 167, 1)",
666
+ "colorBorderSecondary": "rgba(126, 115, 107, 1)",
666
667
  "fontSize": 14,
667
668
  "fontSizeSM": 12,
668
669
  "fontSizeLG": 16,
@@ -742,7 +743,7 @@ var generatedCeebeeAntSeeds = {
742
743
  "colorBgContainer": "rgba(40, 40, 50, 1)",
743
744
  "colorBgElevated": "rgba(51, 45, 41, 1)",
744
745
  "colorBorder": "rgba(160, 163, 184, 1)",
745
- "colorBorderSecondary": "rgba(119, 121, 138, 1)",
746
+ "colorBorderSecondary": "rgba(156, 150, 166, 1)",
746
747
  "fontSize": 14,
747
748
  "fontSizeSM": 12,
748
749
  "fontSizeLG": 16,
@@ -1517,9 +1518,14 @@ function PanZoomCanvasRoot({
1517
1518
  zoomOutLabel = "Zoom out",
1518
1519
  resetLabel = "Reset canvas",
1519
1520
  zoomStatusLabel = "Canvas zoom",
1521
+ fullscreenLabel = "Enter fullscreen",
1522
+ exitFullscreenLabel = "Exit fullscreen",
1520
1523
  motion: motionEnabled = true
1521
1524
  }) {
1522
1525
  const motionSettings = useMotionSettings();
1526
+ const rootRef = useRef(null);
1527
+ const [fullscreenAvailable, setFullscreenAvailable] = useState(false);
1528
+ const [fullscreen, setFullscreen] = useState(false);
1523
1529
  const [pan, setPan] = useState({ x: 0, y: 0 });
1524
1530
  const [scale, setScale] = useState(1);
1525
1531
  const pointers = useRef(/* @__PURE__ */ new Map());
@@ -1527,6 +1533,21 @@ function PanZoomCanvasRoot({
1527
1533
  const pinchOrigin = useRef(null);
1528
1534
  const stepRef = useRef(null);
1529
1535
  const shouldAnimate = motionEnabled && motionSettings.enabled;
1536
+ useEffect(() => {
1537
+ setFullscreenAvailable(typeof document !== "undefined" && document.fullscreenEnabled === true && typeof rootRef.current?.requestFullscreen === "function");
1538
+ function onChange() {
1539
+ setFullscreen(document.fullscreenElement === rootRef.current);
1540
+ }
1541
+ document.addEventListener("fullscreenchange", onChange);
1542
+ return () => document.removeEventListener("fullscreenchange", onChange);
1543
+ }, []);
1544
+ function toggleFullscreen() {
1545
+ if (document.fullscreenElement === rootRef.current) {
1546
+ void document.exitFullscreen();
1547
+ return;
1548
+ }
1549
+ void rootRef.current?.requestFullscreen();
1550
+ }
1530
1551
  function reset() {
1531
1552
  setPan({ x: 0, y: 0 });
1532
1553
  setScale(1);
@@ -1600,7 +1621,7 @@ function PanZoomCanvasRoot({
1600
1621
  "--cb-pan-zoom-y": pan.y,
1601
1622
  "--cb-pan-zoom-scale": scale
1602
1623
  };
1603
- return /* @__PURE__ */ jsxs("section", { className: "cb-pan-zoom-canvas", "data-motion": String(shouldAnimate), children: [
1624
+ return /* @__PURE__ */ jsxs("section", { ref: rootRef, className: "cb-pan-zoom-canvas", "data-motion": String(shouldAnimate), "data-fullscreen": String(fullscreen), children: [
1604
1625
  /* @__PURE__ */ jsxs("div", { className: "cb-pan-zoom-canvas__toolbar", children: [
1605
1626
  hint ? /* @__PURE__ */ jsx("p", { className: "cb-pan-zoom-canvas__hint", children: hint }) : /* @__PURE__ */ jsx("span", {}),
1606
1627
  /* @__PURE__ */ jsxs("div", { className: "cb-pan-zoom-canvas__controls", children: [
@@ -1610,7 +1631,18 @@ function PanZoomCanvasRoot({
1610
1631
  "%"
1611
1632
  ] }),
1612
1633
  /* @__PURE__ */ jsx(Button, { type: "text", size: "small", icon: /* @__PURE__ */ jsx(Plus, { "aria-hidden": "true" }), "aria-label": zoomInLabel, onClick: () => zoom(SCALE_STEP), disabled: scale >= MAX_SCALE }),
1613
- /* @__PURE__ */ jsx(Button, { type: "text", size: "small", icon: /* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true" }), "aria-label": resetLabel, onClick: reset })
1634
+ /* @__PURE__ */ jsx(Button, { type: "text", size: "small", icon: /* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true" }), "aria-label": resetLabel, onClick: reset }),
1635
+ fullscreenAvailable ? /* @__PURE__ */ jsx(
1636
+ Button,
1637
+ {
1638
+ type: "text",
1639
+ size: "small",
1640
+ icon: fullscreen ? /* @__PURE__ */ jsx(Minimize2, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Maximize2, { "aria-hidden": "true" }),
1641
+ "aria-label": fullscreen ? exitFullscreenLabel : fullscreenLabel,
1642
+ "aria-pressed": fullscreen,
1643
+ onClick: toggleFullscreen
1644
+ }
1645
+ ) : null
1614
1646
  ] })
1615
1647
  ] }),
1616
1648
  /* @__PURE__ */ jsxs(
@@ -1637,5 +1669,311 @@ function PanZoomCanvasRoot({
1637
1669
  ] });
1638
1670
  }
1639
1671
  var PanZoomCanvas = Object.assign(PanZoomCanvasRoot, { Skeleton: PanZoomCanvasSkeleton });
1672
+ function DiagramNodeView({ data, selected }) {
1673
+ return /* @__PURE__ */ jsxs(
1674
+ "div",
1675
+ {
1676
+ className: "cb-diagram__node",
1677
+ "data-shape": data.shape,
1678
+ "data-tone": data.tone,
1679
+ "data-selected": String(Boolean(selected)),
1680
+ "data-connecting": String(data.connecting),
1681
+ children: [
1682
+ /* @__PURE__ */ jsx(Handle, { id: "left", type: "target", position: Position.Left, className: "cb-diagram__handle", isConnectable: data.connectable }),
1683
+ /* @__PURE__ */ jsx(Handle, { id: "top", type: "target", position: Position.Top, className: "cb-diagram__handle", isConnectable: data.connectable }),
1684
+ /* @__PURE__ */ jsx("span", { className: "cb-diagram__label", children: data.label }),
1685
+ data.content,
1686
+ /* @__PURE__ */ jsx(Handle, { id: "right", type: "source", position: Position.Right, className: "cb-diagram__handle", isConnectable: data.connectable }),
1687
+ /* @__PURE__ */ jsx(Handle, { id: "bottom", type: "source", position: Position.Bottom, className: "cb-diagram__handle", isConnectable: data.connectable })
1688
+ ]
1689
+ }
1690
+ );
1691
+ }
1692
+ var FLOW_NODE_TYPE = "cb-diagram";
1693
+ var FALLBACK_CELL = 20;
1694
+ function cellSize(measured) {
1695
+ return measured !== void 0 && measured > 0 ? measured : FALLBACK_CELL;
1696
+ }
1697
+ function toCells(position, cell) {
1698
+ return { x: Math.round(position.x / cell), y: Math.round(position.y / cell) };
1699
+ }
1700
+ function toFlowNodes(nodes, cell, state) {
1701
+ return nodes.map((node) => ({
1702
+ id: node.id,
1703
+ type: FLOW_NODE_TYPE,
1704
+ position: { x: node.position.x * cell, y: node.position.y * cell },
1705
+ selected: state.selectedId === node.id,
1706
+ data: {
1707
+ label: node.label,
1708
+ content: node.content,
1709
+ shape: node.shape ?? "rect",
1710
+ tone: node.tone,
1711
+ connecting: state.connectingFrom === node.id,
1712
+ connectable: state.connectable
1713
+ }
1714
+ }));
1715
+ }
1716
+ function centreOf(node) {
1717
+ const [columns, rows] = node.shape === "diamond" ? [4, 4] : [8, 3];
1718
+ return { x: node.position.x + columns / 2, y: node.position.y + rows / 2 };
1719
+ }
1720
+ function edgeHandles(from, to) {
1721
+ const a = centreOf(from);
1722
+ const b = centreOf(to);
1723
+ const dx = b.x - a.x;
1724
+ const dy = b.y - a.y;
1725
+ if (Math.abs(dx) >= Math.abs(dy)) {
1726
+ return dx >= 0 ? { sourceHandle: "right", targetHandle: "left" } : { sourceHandle: "left", targetHandle: "right" };
1727
+ }
1728
+ return dy >= 0 ? { sourceHandle: "bottom", targetHandle: "top" } : { sourceHandle: "top", targetHandle: "bottom" };
1729
+ }
1730
+ function toFlowEdges(edges, nodes) {
1731
+ const byId = new Map(nodes.map((node) => [node.id, node]));
1732
+ return edges.flatMap((edge) => {
1733
+ const from = byId.get(edge.from);
1734
+ const to = byId.get(edge.to);
1735
+ if (!from || !to) return [];
1736
+ return [
1737
+ {
1738
+ id: edge.id,
1739
+ source: edge.from,
1740
+ target: edge.to,
1741
+ ...edgeHandles(from, to),
1742
+ label: edge.label,
1743
+ className: edge.tone ? `cb-diagram__edge cb-diagram__edge--${edge.tone}` : "cb-diagram__edge",
1744
+ markerEnd: { type: MarkerType.ArrowClosed }
1745
+ }
1746
+ ];
1747
+ });
1748
+ }
1749
+ function settledMoves(changes, cell) {
1750
+ return changes.flatMap(
1751
+ (change) => change.type === "position" && change.position && change.dragging !== true ? [{ id: change.id, position: toCells(change.position, cell) }] : []
1752
+ );
1753
+ }
1754
+ function changedMoves(moves, nodes) {
1755
+ return moves.filter((move) => {
1756
+ const current = nodes.find((node) => node.id === move.id)?.position;
1757
+ return !current || current.x !== move.position.x || current.y !== move.position.y;
1758
+ });
1759
+ }
1760
+ function mergeFlowNodes(current, next) {
1761
+ const byId = new Map(current.map((node) => [node.id, node]));
1762
+ let changed = current.length !== next.length;
1763
+ const merged = next.map((node, index) => {
1764
+ const previous = byId.get(node.id);
1765
+ if (!previous) {
1766
+ changed = true;
1767
+ return node;
1768
+ }
1769
+ const same = current[index] === previous && previous.position.x === node.position.x && previous.position.y === node.position.y && previous.data.label === node.data.label && previous.data.content === node.data.content && previous.data.shape === node.data.shape && previous.data.tone === node.data.tone && previous.data.connecting === node.data.connecting && previous.data.connectable === node.data.connectable;
1770
+ if (same) return previous;
1771
+ changed = true;
1772
+ return { ...node, selected: previous.selected, measured: previous.measured };
1773
+ });
1774
+ return changed ? merged : current;
1775
+ }
1776
+ function keepIfUnchanged(current, next) {
1777
+ return next.length === current.length && next.every((item, index) => item === current[index]) ? current : next;
1778
+ }
1779
+ function applySelection(current, selectedId) {
1780
+ let changed = false;
1781
+ const next = current.map((node) => {
1782
+ const selected = node.id === selectedId;
1783
+ if (Boolean(node.selected) === selected) return node;
1784
+ changed = true;
1785
+ return { ...node, selected };
1786
+ });
1787
+ return changed ? next : current;
1788
+ }
1789
+ function connectStep(state, event) {
1790
+ if (event.type === "cancel") return { state: { mode: "idle" } };
1791
+ if (event.type === "start" || state.mode === "idle") return { state: { mode: "connecting", from: event.nodeId } };
1792
+ if (event.nodeId === state.from) return { state: { mode: "idle" } };
1793
+ return { state: { mode: "idle" }, connect: { from: state.from, to: event.nodeId } };
1794
+ }
1795
+ var NODE_TYPES = { [FLOW_NODE_TYPE]: DiagramNodeView };
1796
+ function useCellSize(ref) {
1797
+ const [cell, setCell] = useState(FALLBACK_CELL);
1798
+ useEffect(() => {
1799
+ setCell(cellSize(ref.current?.getBoundingClientRect().width));
1800
+ }, [ref]);
1801
+ return cell;
1802
+ }
1803
+ function DiagramOutline({
1804
+ id,
1805
+ nodes,
1806
+ edges,
1807
+ label
1808
+ }) {
1809
+ const byId = new Map(nodes.map((n) => [n.id, n]));
1810
+ return /* @__PURE__ */ jsx("ul", { id, className: "cb-diagram__outline", "aria-label": label, children: nodes.map((node) => {
1811
+ const leaving = edges.filter((e) => e.from === node.id && byId.has(e.to));
1812
+ return /* @__PURE__ */ jsxs("li", { children: [
1813
+ node.label,
1814
+ leaving.length > 0 ? /* @__PURE__ */ jsx("ul", { children: leaving.map((edge) => /* @__PURE__ */ jsx("li", { children: `\u2192 ${byId.get(edge.to)?.label ?? ""}${edge.label ? `: ${edge.label}` : ""}` }, edge.id)) }) : null
1815
+ ] }, node.id);
1816
+ }) });
1817
+ }
1818
+ function safeId(id) {
1819
+ return id.replace(/[^a-zA-Z0-9_-]/g, "");
1820
+ }
1821
+ function DiagramSkeleton({ label = "Loading diagram" }) {
1822
+ return /* @__PURE__ */ jsx(PanZoomCanvasSkeleton, { label });
1823
+ }
1824
+ function DiagramRoot({ label, nodes, edges, hint, outlineLabel = "Diagram outline", ariaLabels }) {
1825
+ const base = safeId(useId());
1826
+ const cellRef = useRef(null);
1827
+ const cell = useCellSize(cellRef);
1828
+ const flowNodes = useMemo(() => toFlowNodes(nodes, cell, { connectable: false }), [nodes, cell]);
1829
+ const flowEdges = useMemo(() => toFlowEdges(edges, nodes), [edges, nodes]);
1830
+ return /* @__PURE__ */ jsxs("div", { className: "cb-diagram", children: [
1831
+ /* @__PURE__ */ jsx("span", { ref: cellRef, className: "cb-diagram__cell", "aria-hidden": "true" }),
1832
+ hint ? /* @__PURE__ */ jsx("p", { className: "cb-diagram__hint", children: hint }) : null,
1833
+ /* @__PURE__ */ jsx("div", { className: "cb-diagram__viewport", role: "region", "aria-label": label, "aria-describedby": `${base}-outline`, children: /* @__PURE__ */ jsxs(
1834
+ ReactFlow,
1835
+ {
1836
+ nodes: flowNodes,
1837
+ edges: flowEdges,
1838
+ nodeTypes: NODE_TYPES,
1839
+ connectionMode: ConnectionMode.Loose,
1840
+ nodesDraggable: false,
1841
+ nodesConnectable: false,
1842
+ elementsSelectable: false,
1843
+ nodesFocusable: false,
1844
+ edgesFocusable: false,
1845
+ fitView: true,
1846
+ minZoom: 0.5,
1847
+ maxZoom: 2,
1848
+ ariaLabelConfig: ariaLabels,
1849
+ children: [
1850
+ /* @__PURE__ */ jsx(Background, { gap: cell }),
1851
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false })
1852
+ ]
1853
+ }
1854
+ ) }),
1855
+ /* @__PURE__ */ jsx(DiagramOutline, { id: `${base}-outline`, nodes, edges, label: outlineLabel })
1856
+ ] });
1857
+ }
1858
+ var Diagram = Object.assign(DiagramRoot, { Skeleton: DiagramSkeleton });
1859
+ var CONNECT_KEY = "c";
1860
+ var DELETE_KEYS = ["Delete", "Backspace"];
1861
+ var defaultConnectingStatus = (node) => `Connecting from ${node}. Focus another node and press C, or press Escape to cancel.`;
1862
+ function elementAt(target) {
1863
+ if (!(target instanceof Element)) return null;
1864
+ const node = target.closest(".react-flow__node")?.getAttribute("data-id");
1865
+ if (node) return { kind: "node", id: node };
1866
+ const edge = target.closest(".react-flow__edge")?.getAttribute("data-id");
1867
+ return edge ? { kind: "edge", id: edge } : null;
1868
+ }
1869
+ function DiagramEditorRoot(props) {
1870
+ const { label, nodes, edges, hint, selectedId, connectingStatus = defaultConnectingStatus, outlineLabel = "Diagram outline", ariaLabels } = props;
1871
+ const base = safeId(useId());
1872
+ const cellRef = useRef(null);
1873
+ const cell = useCellSize(cellRef);
1874
+ const [connect, setConnect] = useState({ mode: "idle" });
1875
+ const connectingFrom = connect.mode === "connecting" ? connect.from : null;
1876
+ const latest = useRef({ props, cell, connect });
1877
+ latest.current = { props, cell, connect };
1878
+ const reportedSelection = useRef(selectedId ?? null);
1879
+ const [resync, setResync] = useState(0);
1880
+ const [flowNodes, setFlowNodes] = useState(() => toFlowNodes(nodes, cell, { selectedId, connectable: true }));
1881
+ useEffect(() => {
1882
+ setFlowNodes((current) => mergeFlowNodes(current, toFlowNodes(nodes, cell, { connectingFrom, connectable: true })));
1883
+ }, [nodes, cell, connectingFrom, resync]);
1884
+ useEffect(() => {
1885
+ if (selectedId === void 0) return;
1886
+ reportedSelection.current = selectedId;
1887
+ setFlowNodes((current) => applySelection(current, selectedId));
1888
+ }, [selectedId]);
1889
+ const flowEdges = useMemo(() => toFlowEdges(edges, nodes), [edges, nodes]);
1890
+ const snapGrid = useMemo(() => [cell, cell], [cell]);
1891
+ const step = useCallback((event) => {
1892
+ const { connect: state, props: current } = latest.current;
1893
+ const next = connectStep(state, event);
1894
+ setConnect(next.state);
1895
+ if (next.connect) current.onConnect?.(next.connect.from, next.connect.to);
1896
+ }, []);
1897
+ const onNodesChange = useCallback((changes) => {
1898
+ const { props: current, cell: size } = latest.current;
1899
+ setFlowNodes((mirror) => keepIfUnchanged(mirror, applyNodeChanges(changes.filter((change) => change.type !== "remove"), mirror)));
1900
+ const moves = changedMoves(settledMoves(changes, size), current.nodes);
1901
+ for (const move of moves) current.onMoveNode?.(move.id, move.position);
1902
+ if (moves.length > 0) setResync((n) => n + 1);
1903
+ }, []);
1904
+ const onConnectEdge = useCallback((connection) => {
1905
+ if (connection.source !== connection.target) latest.current.props.onConnect?.(connection.source, connection.target);
1906
+ }, []);
1907
+ const onBeforeDelete = useCallback(async ({ nodes: gone, edges: goneEdges }) => {
1908
+ latest.current.props.onRemove?.({ nodeIds: gone.map((n) => n.id), edgeIds: goneEdges.map((e) => e.id) });
1909
+ return false;
1910
+ }, []);
1911
+ const onSelectionChange = useCallback(({ nodes: chosen }) => {
1912
+ const id = chosen[0]?.id;
1913
+ if (!id || id === reportedSelection.current) return;
1914
+ reportedSelection.current = id;
1915
+ latest.current.props.onSelectionChange?.(id);
1916
+ }, []);
1917
+ const onNodeClick = useCallback(
1918
+ (_, node) => {
1919
+ if (latest.current.connect.mode === "connecting") step({ type: "choose", nodeId: node.id });
1920
+ },
1921
+ [step]
1922
+ );
1923
+ const onPaneClick = useCallback(() => {
1924
+ if (latest.current.connect.mode === "connecting") step({ type: "cancel" });
1925
+ }, [step]);
1926
+ const onKeyDown = useCallback(
1927
+ (event) => {
1928
+ const target = elementAt(event.target);
1929
+ if (event.key.toLowerCase() === CONNECT_KEY && target?.kind === "node" && !event.metaKey && !event.ctrlKey && !event.altKey) {
1930
+ event.preventDefault();
1931
+ step({ type: "choose", nodeId: target.id });
1932
+ } else if (event.key === "Escape" && latest.current.connect.mode === "connecting") {
1933
+ step({ type: "cancel" });
1934
+ } else if (event.key === "F2" && target) {
1935
+ event.preventDefault();
1936
+ latest.current.props.onRename?.(target);
1937
+ }
1938
+ },
1939
+ [step]
1940
+ );
1941
+ const labelOf = (id) => nodes.find((n) => n.id === id)?.label ?? "";
1942
+ return /* @__PURE__ */ jsxs("div", { className: "cb-diagram cb-diagram--editor", children: [
1943
+ /* @__PURE__ */ jsx("span", { ref: cellRef, className: "cb-diagram__cell", "aria-hidden": "true" }),
1944
+ hint ? /* @__PURE__ */ jsx("p", { className: "cb-diagram__hint", children: hint }) : null,
1945
+ /* @__PURE__ */ jsx("div", { className: "cb-diagram__viewport", role: "region", "aria-label": label, "aria-describedby": `${base}-outline`, children: /* @__PURE__ */ jsxs(
1946
+ ReactFlow,
1947
+ {
1948
+ nodes: flowNodes,
1949
+ edges: flowEdges,
1950
+ nodeTypes: NODE_TYPES,
1951
+ onNodesChange,
1952
+ onConnect: onConnectEdge,
1953
+ onBeforeDelete,
1954
+ onSelectionChange,
1955
+ onNodeClick,
1956
+ onPaneClick,
1957
+ onKeyDown,
1958
+ connectionMode: ConnectionMode.Loose,
1959
+ connectOnClick: true,
1960
+ snapToGrid: true,
1961
+ snapGrid,
1962
+ deleteKeyCode: DELETE_KEYS,
1963
+ fitView: true,
1964
+ minZoom: 0.5,
1965
+ maxZoom: 2,
1966
+ ariaLabelConfig: ariaLabels,
1967
+ children: [
1968
+ /* @__PURE__ */ jsx(Background, { gap: cell }),
1969
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false })
1970
+ ]
1971
+ }
1972
+ ) }),
1973
+ /* @__PURE__ */ jsx("p", { className: "cb-diagram__status", role: "status", children: connectingFrom ? connectingStatus(labelOf(connectingFrom)) : "" }),
1974
+ /* @__PURE__ */ jsx(DiagramOutline, { id: `${base}-outline`, nodes, edges, label: outlineLabel })
1975
+ ] });
1976
+ }
1977
+ var DiagramEditor = Object.assign(DiagramEditorRoot, { Skeleton: DiagramSkeleton });
1640
1978
 
1641
- export { CeebeeAntStyleProvider, Checklist, CommandPalette, DEFAULT_LABELS, LabelsProvider, Modal, MotionProvider, PanZoomCanvas, Reveal, Sidebar, Stagger, StickerGroup, ThemeBridge, ThemeProvider, ToastProvider, TopBar, createCeebeeAntStyleCache, extractCeebeeAntStyles, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
1979
+ export { CeebeeAntStyleProvider, Checklist, CommandPalette, DEFAULT_LABELS, Diagram, DiagramEditor, LabelsProvider, Modal, MotionProvider, PanZoomCanvas, Reveal, Sidebar, Stagger, StickerGroup, ThemeBridge, ThemeProvider, ToastProvider, TopBar, createCeebeeAntStyleCache, extractCeebeeAntStyles, filterCommands, groupCommands, rankCommand, useLabels, useMotionSettings, useTheme, useToast };
package/dist/index.js CHANGED
@@ -550,7 +550,7 @@ var generatedCeebeeAntSeeds = {
550
550
  "colorBgContainer": "rgba(255, 253, 248, 1)",
551
551
  "colorBgElevated": "rgba(255, 255, 255, 1)",
552
552
  "colorBorder": "rgba(83, 84, 97, 1)",
553
- "colorBorderSecondary": "rgba(156, 158, 167, 1)",
553
+ "colorBorderSecondary": "rgba(126, 115, 107, 1)",
554
554
  "fontSize": 14,
555
555
  "fontSizeSM": 12,
556
556
  "fontSizeLG": 16,
@@ -630,7 +630,7 @@ var generatedCeebeeAntSeeds = {
630
630
  "colorBgContainer": "rgba(40, 40, 50, 1)",
631
631
  "colorBgElevated": "rgba(51, 45, 41, 1)",
632
632
  "colorBorder": "rgba(160, 163, 184, 1)",
633
- "colorBorderSecondary": "rgba(119, 121, 138, 1)",
633
+ "colorBorderSecondary": "rgba(156, 150, 166, 1)",
634
634
  "fontSize": 14,
635
635
  "fontSizeSM": 12,
636
636
  "fontSizeLG": 16,
@@ -90,3 +90,24 @@
90
90
  --cb-sticker-shadow: 0 var(--cb-space-1) var(--cb-space-2) oklch(0 0 0 / 0.42);
91
91
  }
92
92
  }
93
+
94
+ /* Someone on this Skin who asks for more contrast should get an edge in this
95
+ Skin's own warmth, not the neutral one the base falls back to. These values
96
+ are the ones CeeBee List's Flutter client had tuned locally and shipped —
97
+ what already worked, abstracted so every product on this Skin gets it.
98
+
99
+ `html:root` matches the base's own specificity and comes later, which is how
100
+ this wins. The base claims that weight so a Skin cannot weaken an
101
+ accessibility edge; both of these are stronger than what they replace —
102
+ 0.5632 against the base's 0.70 in light, 0.6834 against 0.58 in dark — so the
103
+ rule is honoured rather than worked around. */
104
+ @media (prefers-contrast: more) {
105
+ html:root {
106
+ --cb-border: oklch(0.5632 0.0183 58.4);
107
+ }
108
+
109
+ html:root[data-theme="dark"],
110
+ html:root:not([data-theme="light"]) {
111
+ --cb-border: oklch(0.6834 0.0241 302.8);
112
+ }
113
+ }
package/dist/styles.css CHANGED
@@ -363,6 +363,513 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
363
363
  --cb-skeleton-opacity-max: 1;
364
364
  }
365
365
 
366
+ /* @xyflow/react/dist/base.css (MIT, see THIRD_PARTY_NOTICES.md) */
367
+ /* this will be exported as base.css and can be used for a basic styling */
368
+ /* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */
369
+ .react-flow {
370
+ direction: ltr;
371
+
372
+ --xy-edge-stroke-default: #b1b1b7;
373
+ --xy-edge-stroke-width-default: 1;
374
+ --xy-edge-stroke-selected-default: #555;
375
+
376
+ --xy-connectionline-stroke-default: #b1b1b7;
377
+ --xy-connectionline-stroke-width-default: 1;
378
+
379
+ --xy-attribution-background-color-default: rgba(255, 255, 255, 0.5);
380
+
381
+ --xy-minimap-background-color-default: #fff;
382
+ --xy-minimap-mask-background-color-default: rgba(240, 240, 240, 0.6);
383
+ --xy-minimap-mask-stroke-color-default: transparent;
384
+ --xy-minimap-mask-stroke-width-default: 1;
385
+ --xy-minimap-node-background-color-default: #e2e2e2;
386
+ --xy-minimap-node-stroke-color-default: transparent;
387
+ --xy-minimap-node-stroke-width-default: 2;
388
+
389
+ --xy-background-color-default: transparent;
390
+ --xy-background-pattern-dots-color-default: #91919a;
391
+ --xy-background-pattern-lines-color-default: #eee;
392
+ --xy-background-pattern-cross-color-default: #e2e2e2;
393
+ background-color: var(--xy-background-color, var(--xy-background-color-default));
394
+ --xy-node-border-default: 1px solid #bbb;
395
+ --xy-node-border-selected-default: 1px solid #555;
396
+
397
+ --xy-handle-background-color-default: #333;
398
+
399
+ --xy-selection-background-color-default: rgba(150, 150, 180, 0.1);
400
+ --xy-selection-border-default: 1px dotted rgba(155, 155, 155, 0.8);
401
+ --xy-resize-background-color-default: #3367d9;
402
+ }
403
+ .react-flow.dark {
404
+ --xy-edge-stroke-default: #3e3e3e;
405
+ --xy-edge-stroke-width-default: 1;
406
+ --xy-edge-stroke-selected-default: #727272;
407
+
408
+ --xy-connectionline-stroke-default: #b1b1b7;
409
+ --xy-connectionline-stroke-width-default: 1;
410
+
411
+ --xy-attribution-background-color-default: rgba(150, 150, 150, 0.25);
412
+
413
+ --xy-minimap-background-color-default: #141414;
414
+ --xy-minimap-mask-background-color-default: rgba(60, 60, 60, 0.6);
415
+ --xy-minimap-mask-stroke-color-default: transparent;
416
+ --xy-minimap-mask-stroke-width-default: 1;
417
+ --xy-minimap-node-background-color-default: #2b2b2b;
418
+ --xy-minimap-node-stroke-color-default: transparent;
419
+ --xy-minimap-node-stroke-width-default: 2;
420
+
421
+ --xy-background-color-default: #141414;
422
+ --xy-background-pattern-dots-color-default: #555;
423
+ --xy-background-pattern-lines-color-default: #333;
424
+ --xy-background-pattern-cross-color-default: #333;
425
+ --xy-node-color-default: #f8f8f8;
426
+ }
427
+ .react-flow__background {
428
+ background-color: var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));
429
+ pointer-events: none;
430
+ z-index: -1;
431
+ }
432
+ .react-flow__container {
433
+ position: absolute;
434
+ width: 100%;
435
+ height: 100%;
436
+ top: 0;
437
+ left: 0;
438
+ }
439
+ .react-flow__pane {
440
+ z-index: 1;
441
+ touch-action: none;
442
+ }
443
+ .react-flow__pane.draggable {
444
+ cursor: grab;
445
+ }
446
+ .react-flow__pane.dragging {
447
+ cursor: grabbing;
448
+ }
449
+ .react-flow__pane.selection {
450
+ cursor: pointer;
451
+ }
452
+ .react-flow__viewport {
453
+ transform-origin: 0 0;
454
+ z-index: 2;
455
+ pointer-events: none;
456
+ }
457
+ .react-flow__renderer {
458
+ z-index: 4;
459
+ }
460
+ .react-flow__selection {
461
+ z-index: 6;
462
+ }
463
+ .react-flow__nodesselection-rect:focus,
464
+ .react-flow__nodesselection-rect:focus-visible {
465
+ outline: none;
466
+ }
467
+ .react-flow__edge-path {
468
+ stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
469
+ stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));
470
+ fill: none;
471
+ }
472
+ .react-flow__connection-path {
473
+ stroke: var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));
474
+ stroke-width: var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));
475
+ fill: none;
476
+ }
477
+ .react-flow .react-flow__edges {
478
+ position: absolute;
479
+ }
480
+ .react-flow .react-flow__edges svg {
481
+ overflow: visible;
482
+ position: absolute;
483
+ pointer-events: none;
484
+ }
485
+ .react-flow__edge {
486
+ pointer-events: visibleStroke;
487
+ }
488
+ .react-flow__edge.selectable {
489
+ cursor: pointer;
490
+ }
491
+ .react-flow__edge.animated path {
492
+ stroke-dasharray: 5;
493
+ animation: dashdraw 0.5s linear infinite;
494
+ }
495
+ .react-flow__edge.animated path.react-flow__edge-interaction {
496
+ stroke-dasharray: none;
497
+ animation: none;
498
+ }
499
+ .react-flow__edge.inactive {
500
+ pointer-events: none;
501
+ }
502
+ .react-flow__edge.selected,
503
+ .react-flow__edge:focus,
504
+ .react-flow__edge:focus-visible {
505
+ outline: none;
506
+ }
507
+ .react-flow__edge.selected .react-flow__edge-path,
508
+ .react-flow__edge.selectable:focus .react-flow__edge-path,
509
+ .react-flow__edge.selectable:focus-visible .react-flow__edge-path {
510
+ stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
511
+ }
512
+ .react-flow__edge-textwrapper {
513
+ pointer-events: all;
514
+ }
515
+ .react-flow__edge .react-flow__edge-text {
516
+ pointer-events: none;
517
+ -webkit-user-select: none;
518
+ -moz-user-select: none;
519
+ user-select: none;
520
+ }
521
+ /* Arrowhead marker styles - use CSS custom properties as default */
522
+ .react-flow__arrowhead polyline {
523
+ stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
524
+ }
525
+ .react-flow__arrowhead polyline.arrowclosed {
526
+ fill: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
527
+ }
528
+ .react-flow__connection {
529
+ pointer-events: none;
530
+ }
531
+ .react-flow__connection .animated {
532
+ stroke-dasharray: 5;
533
+ animation: dashdraw 0.5s linear infinite;
534
+ }
535
+ svg.react-flow__connectionline {
536
+ z-index: 1001;
537
+ overflow: visible;
538
+ position: absolute;
539
+ }
540
+ .react-flow__nodes {
541
+ pointer-events: none;
542
+ transform-origin: 0 0;
543
+ }
544
+ .react-flow__node {
545
+ position: absolute;
546
+ -webkit-user-select: none;
547
+ -moz-user-select: none;
548
+ user-select: none;
549
+ pointer-events: all;
550
+ transform-origin: 0 0;
551
+ box-sizing: border-box;
552
+ cursor: default;
553
+ }
554
+ .react-flow__node.selectable {
555
+ cursor: pointer;
556
+ }
557
+ .react-flow__node.draggable {
558
+ cursor: grab;
559
+ pointer-events: all;
560
+ }
561
+ .react-flow__node.draggable.dragging {
562
+ cursor: grabbing;
563
+ }
564
+ .react-flow__nodesselection {
565
+ z-index: 3;
566
+ transform-origin: left top;
567
+ pointer-events: none;
568
+ }
569
+ .react-flow__nodesselection-rect {
570
+ position: absolute;
571
+ pointer-events: all;
572
+ cursor: grab;
573
+ }
574
+ .react-flow__handle {
575
+ position: absolute;
576
+ pointer-events: none;
577
+ min-width: 5px;
578
+ min-height: 5px;
579
+ background-color: var(--xy-handle-background-color, var(--xy-handle-background-color-default));
580
+ }
581
+ .react-flow__handle.connectingfrom {
582
+ pointer-events: all;
583
+ }
584
+ .react-flow__handle.connectionindicator {
585
+ pointer-events: all;
586
+ cursor: crosshair;
587
+ }
588
+ .react-flow__handle-bottom {
589
+ top: auto;
590
+ left: 50%;
591
+ bottom: 0;
592
+ transform: translate(-50%, 50%);
593
+ }
594
+ .react-flow__handle-top {
595
+ top: 0;
596
+ left: 50%;
597
+ transform: translate(-50%, -50%);
598
+ }
599
+ .react-flow__handle-left {
600
+ top: 50%;
601
+ left: 0;
602
+ transform: translate(-50%, -50%);
603
+ }
604
+ .react-flow__handle-right {
605
+ top: 50%;
606
+ right: 0;
607
+ transform: translate(50%, -50%);
608
+ }
609
+ .react-flow__edgeupdater {
610
+ cursor: move;
611
+ pointer-events: all;
612
+ }
613
+ .react-flow__pane.selection .react-flow__panel {
614
+ pointer-events: none;
615
+ }
616
+ .react-flow__panel {
617
+ position: absolute;
618
+ z-index: 5;
619
+ margin: 15px;
620
+ }
621
+ .react-flow__panel.top {
622
+ top: 0;
623
+ }
624
+ .react-flow__panel.bottom {
625
+ bottom: 0;
626
+ }
627
+ .react-flow__panel.top.center, .react-flow__panel.bottom.center {
628
+ left: 50%;
629
+ transform: translateX(-15px) translateX(-50%);
630
+ }
631
+ .react-flow__panel.left {
632
+ left: 0;
633
+ }
634
+ .react-flow__panel.right {
635
+ right: 0;
636
+ }
637
+ .react-flow__panel.left.center, .react-flow__panel.right.center {
638
+ top: 50%;
639
+ transform: translateY(-15px) translateY(-50%);
640
+ }
641
+ .react-flow__attribution {
642
+ font-size: 10px;
643
+ background: var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));
644
+ padding: 2px 3px;
645
+ margin: 0;
646
+ }
647
+ .react-flow__attribution a {
648
+ text-decoration: none;
649
+ color: #999;
650
+ }
651
+ @keyframes dashdraw {
652
+ from {
653
+ stroke-dashoffset: 10;
654
+ }
655
+ }
656
+ .react-flow__edgelabel-renderer {
657
+ position: absolute;
658
+ width: 100%;
659
+ height: 100%;
660
+ pointer-events: none;
661
+ -webkit-user-select: none;
662
+ -moz-user-select: none;
663
+ user-select: none;
664
+ left: 0;
665
+ top: 0;
666
+ }
667
+ .react-flow__viewport-portal {
668
+ position: absolute;
669
+ width: 100%;
670
+ height: 100%;
671
+ left: 0;
672
+ top: 0;
673
+ -webkit-user-select: none;
674
+ -moz-user-select: none;
675
+ user-select: none;
676
+ }
677
+ .react-flow__minimap {
678
+ background: var(
679
+ --xy-minimap-background-color-props,
680
+ var(--xy-minimap-background-color, var(--xy-minimap-background-color-default))
681
+ );
682
+ }
683
+ .react-flow__minimap-svg {
684
+ display: block;
685
+ }
686
+ .react-flow__minimap-mask {
687
+ fill: var(
688
+ --xy-minimap-mask-background-color-props,
689
+ var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default))
690
+ );
691
+ stroke: var(
692
+ --xy-minimap-mask-stroke-color-props,
693
+ var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default))
694
+ );
695
+ stroke-width: var(
696
+ --xy-minimap-mask-stroke-width-props,
697
+ var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default))
698
+ );
699
+ }
700
+ .react-flow__minimap-node {
701
+ fill: var(
702
+ --xy-minimap-node-background-color-props,
703
+ var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default))
704
+ );
705
+ stroke: var(
706
+ --xy-minimap-node-stroke-color-props,
707
+ var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default))
708
+ );
709
+ stroke-width: var(
710
+ --xy-minimap-node-stroke-width-props,
711
+ var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default))
712
+ );
713
+ }
714
+ .react-flow__background-pattern.dots {
715
+ fill: var(
716
+ --xy-background-pattern-color-props,
717
+ var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default))
718
+ );
719
+ }
720
+ .react-flow__background-pattern.lines {
721
+ stroke: var(
722
+ --xy-background-pattern-color-props,
723
+ var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default))
724
+ );
725
+ }
726
+ .react-flow__background-pattern.cross {
727
+ stroke: var(
728
+ --xy-background-pattern-color-props,
729
+ var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default))
730
+ );
731
+ }
732
+ .react-flow__controls {
733
+ display: flex;
734
+ flex-direction: column;
735
+ }
736
+ .react-flow__controls.horizontal {
737
+ flex-direction: row;
738
+ }
739
+ .react-flow__controls-button {
740
+ display: flex;
741
+ justify-content: center;
742
+ align-items: center;
743
+ height: 26px;
744
+ width: 26px;
745
+ padding: 4px;
746
+ }
747
+ .react-flow__controls-button svg {
748
+ width: 100%;
749
+ max-width: 12px;
750
+ max-height: 12px;
751
+ fill: currentColor;
752
+ }
753
+ .react-flow__node-input,
754
+ .react-flow__node-default,
755
+ .react-flow__node-output,
756
+ .react-flow__node-group {
757
+ border: var(--xy-node-border, var(--xy-node-border-default));
758
+ color: var(--xy-node-color, var(--xy-node-color-default));
759
+ }
760
+ .react-flow__node-input.selected,
761
+ .react-flow__node-input:focus,
762
+ .react-flow__node-input:focus-visible,
763
+ .react-flow__node-default.selected,
764
+ .react-flow__node-default:focus,
765
+ .react-flow__node-default:focus-visible,
766
+ .react-flow__node-output.selected,
767
+ .react-flow__node-output:focus,
768
+ .react-flow__node-output:focus-visible,
769
+ .react-flow__node-group.selected,
770
+ .react-flow__node-group:focus,
771
+ .react-flow__node-group:focus-visible {
772
+ outline: none;
773
+ border: var(--xy-node-border-selected, var(--xy-node-border-selected-default));
774
+ }
775
+ .react-flow__nodesselection-rect,
776
+ .react-flow__selection {
777
+ background: var(--xy-selection-background-color, var(--xy-selection-background-color-default));
778
+ border: var(--xy-selection-border, var(--xy-selection-border-default));
779
+ }
780
+ .react-flow__resize-control {
781
+ position: absolute;
782
+ }
783
+ .react-flow__resize-control.left,
784
+ .react-flow__resize-control.right {
785
+ cursor: ew-resize;
786
+ }
787
+ .react-flow__resize-control.top,
788
+ .react-flow__resize-control.bottom {
789
+ cursor: ns-resize;
790
+ }
791
+ .react-flow__resize-control.top.left,
792
+ .react-flow__resize-control.bottom.right {
793
+ cursor: nwse-resize;
794
+ }
795
+ .react-flow__resize-control.bottom.left,
796
+ .react-flow__resize-control.top.right {
797
+ cursor: nesw-resize;
798
+ }
799
+ /* handle styles */
800
+ .react-flow__resize-control.handle {
801
+ width: 5px;
802
+ height: 5px;
803
+ border: 1px solid #fff;
804
+ border-radius: 1px;
805
+ background-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
806
+ translate: -50% -50%;
807
+ }
808
+ .react-flow__resize-control.handle.left {
809
+ left: 0;
810
+ top: 50%;
811
+ }
812
+ .react-flow__resize-control.handle.right {
813
+ left: 100%;
814
+ top: 50%;
815
+ }
816
+ .react-flow__resize-control.handle.top {
817
+ left: 50%;
818
+ top: 0;
819
+ }
820
+ .react-flow__resize-control.handle.bottom {
821
+ left: 50%;
822
+ top: 100%;
823
+ }
824
+ .react-flow__resize-control.handle.top.left {
825
+ left: 0;
826
+ }
827
+ .react-flow__resize-control.handle.bottom.left {
828
+ left: 0;
829
+ }
830
+ .react-flow__resize-control.handle.top.right {
831
+ left: 100%;
832
+ }
833
+ .react-flow__resize-control.handle.bottom.right {
834
+ left: 100%;
835
+ }
836
+ /* line styles */
837
+ .react-flow__resize-control.line {
838
+ border-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
839
+ border-width: 0;
840
+ border-style: solid;
841
+ }
842
+ .react-flow__resize-control.line.left,
843
+ .react-flow__resize-control.line.right {
844
+ width: 1px;
845
+ transform: translate(-50%, 0);
846
+ top: 0;
847
+ height: 100%;
848
+ }
849
+ .react-flow__resize-control.line.left {
850
+ left: 0;
851
+ border-left-width: 1px;
852
+ }
853
+ .react-flow__resize-control.line.right {
854
+ left: 100%;
855
+ border-right-width: 1px;
856
+ }
857
+ .react-flow__resize-control.line.top,
858
+ .react-flow__resize-control.line.bottom {
859
+ height: 1px;
860
+ transform: translate(0, -50%);
861
+ left: 0;
862
+ width: 100%;
863
+ }
864
+ .react-flow__resize-control.line.top {
865
+ top: 0;
866
+ border-top-width: 1px;
867
+ }
868
+ .react-flow__resize-control.line.bottom {
869
+ border-bottom-width: 1px;
870
+ top: 100%;
871
+ }
872
+
366
873
  /* foundation/layout.css */
367
874
  .cb-flex { display: flex; gap: var(--cb-flex-gap); }
368
875
  .cb-flex--column { flex-direction: column; }
@@ -829,6 +1336,201 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
829
1336
  line-height: var(--cb-leading-normal);
830
1337
  }
831
1338
 
1339
+ /* data/diagram/diagram.css */
1340
+ /* Diagram and DiagramEditor run on React Flow. Its base stylesheet (bundled before this file) owns layout and
1341
+ reads its colours from `--xy-*` custom properties; here those are pointed at Tokens, and the node React
1342
+ Flow wraps is drawn by this library. */
1343
+
1344
+ .cb-diagram {
1345
+ --cb-diagram-cell: var(--cb-space-5);
1346
+ min-inline-size: 0;
1347
+ position: relative;
1348
+ }
1349
+
1350
+ .cb-diagram__cell {
1351
+ inline-size: var(--cb-diagram-cell);
1352
+ pointer-events: none;
1353
+ position: absolute;
1354
+ visibility: hidden;
1355
+ }
1356
+
1357
+ .cb-diagram__hint {
1358
+ color: var(--cb-fg-muted);
1359
+ font-size: var(--cb-text-xs);
1360
+ margin: 0 0 var(--cb-space-2);
1361
+ }
1362
+
1363
+ .cb-diagram__viewport {
1364
+ background: var(--cb-surface);
1365
+ block-size: var(--cb-canvas-block-size);
1366
+ border: var(--cb-border-width) solid var(--cb-border);
1367
+ border-radius: var(--cb-radius-lg);
1368
+ overflow: hidden;
1369
+ }
1370
+
1371
+ .cb-diagram .react-flow {
1372
+ --xy-background-color: var(--cb-surface);
1373
+ --xy-background-pattern-color: var(--cb-border);
1374
+ --xy-edge-stroke: var(--cb-border-strong);
1375
+ --xy-edge-stroke-selected: var(--cb-tone-brand);
1376
+ --xy-edge-stroke-width: var(--cb-border-width);
1377
+ --xy-connectionline-stroke: var(--cb-tone-info);
1378
+ --xy-connectionline-stroke-width: var(--cb-border-width);
1379
+ --xy-handle-background-color: var(--cb-surface);
1380
+ --xy-handle-border-color: var(--cb-border-strong);
1381
+ --xy-attribution-background-color: transparent;
1382
+ --xy-controls-button-background-color: var(--cb-surface);
1383
+ --xy-controls-button-background-color-hover: var(--cb-bg-subtle);
1384
+ --xy-controls-button-color: var(--cb-fg);
1385
+ --xy-controls-button-color-hover: var(--cb-fg);
1386
+ --xy-controls-button-border-color: var(--cb-border);
1387
+ --xy-controls-box-shadow: none;
1388
+ --xy-edge-label-background-color: var(--cb-surface);
1389
+ --xy-edge-label-color: var(--cb-fg-muted);
1390
+ color: var(--cb-fg);
1391
+ font-family: inherit;
1392
+ }
1393
+
1394
+ .cb-diagram .react-flow__edge-text {
1395
+ fill: var(--cb-fg-muted);
1396
+ font-size: var(--cb-text-xs);
1397
+ }
1398
+
1399
+ .cb-diagram .react-flow__edge-textbg { fill: var(--cb-surface); }
1400
+
1401
+ .cb-diagram .cb-diagram__edge--neutral .react-flow__edge-path { stroke: var(--cb-tone-neutral); }
1402
+ .cb-diagram .cb-diagram__edge--brand .react-flow__edge-path { stroke: var(--cb-tone-brand); }
1403
+ .cb-diagram .cb-diagram__edge--info .react-flow__edge-path { stroke: var(--cb-tone-info); }
1404
+ .cb-diagram .cb-diagram__edge--success .react-flow__edge-path { stroke: var(--cb-tone-success); }
1405
+ .cb-diagram .cb-diagram__edge--warning .react-flow__edge-path { stroke: var(--cb-tone-warning); }
1406
+ .cb-diagram .cb-diagram__edge--danger .react-flow__edge-path { stroke: var(--cb-tone-danger); }
1407
+
1408
+ .cb-diagram .react-flow__node:focus-visible,
1409
+ .cb-diagram .react-flow__edge:focus-visible {
1410
+ outline: var(--cb-focus-width) solid var(--cb-focus);
1411
+ outline-offset: var(--cb-focus-offset);
1412
+ }
1413
+
1414
+ .cb-diagram__node {
1415
+ align-items: center;
1416
+ background: var(--cb-surface);
1417
+ block-size: calc(var(--cb-diagram-cell) * 3);
1418
+ border: var(--cb-border-width) solid var(--cb-border-strong);
1419
+ border-radius: var(--cb-radius-md);
1420
+ box-sizing: border-box;
1421
+ color: var(--cb-fg);
1422
+ display: flex;
1423
+ flex-direction: column;
1424
+ font-size: var(--cb-text-sm);
1425
+ gap: var(--cb-space-1);
1426
+ inline-size: calc(var(--cb-diagram-cell) * 8);
1427
+ isolation: isolate;
1428
+ justify-content: center;
1429
+ padding: var(--cb-space-1) var(--cb-space-2);
1430
+ position: relative;
1431
+ text-align: center;
1432
+ }
1433
+
1434
+ .cb-diagram--editor .cb-diagram__node { cursor: grab; }
1435
+
1436
+ .cb-diagram__node[data-shape="pill"] { border-radius: var(--cb-radius-full); }
1437
+
1438
+ /* A diamond's box is square, so the handles React Flow places at the middle of each side sit exactly on the
1439
+ diamond's tips and arrows meet the drawn outline. The rotated square's diagonal equals the box's side. */
1440
+ .cb-diagram__node[data-shape="diamond"] {
1441
+ background: transparent;
1442
+ block-size: calc(var(--cb-diagram-cell) * 4);
1443
+ border-color: transparent;
1444
+ inline-size: calc(var(--cb-diagram-cell) * 4);
1445
+ }
1446
+
1447
+ .cb-diagram__node[data-shape="diamond"]::before {
1448
+ aspect-ratio: 1;
1449
+ background: var(--cb-surface);
1450
+ block-size: 70.7%;
1451
+ border: var(--cb-border-width) solid var(--cb-border-strong);
1452
+ content: "";
1453
+ inset: 0;
1454
+ margin: auto;
1455
+ position: absolute;
1456
+ rotate: 45deg;
1457
+ z-index: -1;
1458
+ }
1459
+
1460
+ .cb-diagram__node[data-tone="neutral"],
1461
+ .cb-diagram__node[data-tone="neutral"]::before { border-color: var(--cb-tone-neutral); }
1462
+ .cb-diagram__node[data-tone="brand"],
1463
+ .cb-diagram__node[data-tone="brand"]::before { border-color: var(--cb-tone-brand); }
1464
+ .cb-diagram__node[data-tone="info"],
1465
+ .cb-diagram__node[data-tone="info"]::before { border-color: var(--cb-tone-info); }
1466
+ .cb-diagram__node[data-tone="success"],
1467
+ .cb-diagram__node[data-tone="success"]::before { border-color: var(--cb-tone-success); }
1468
+ .cb-diagram__node[data-tone="warning"],
1469
+ .cb-diagram__node[data-tone="warning"]::before { border-color: var(--cb-tone-warning); }
1470
+ .cb-diagram__node[data-tone="danger"],
1471
+ .cb-diagram__node[data-tone="danger"]::before { border-color: var(--cb-tone-danger); }
1472
+
1473
+ .cb-diagram__node[data-shape="diamond"][data-tone] { border-color: transparent; }
1474
+
1475
+ .cb-diagram__node[data-selected="true"],
1476
+ .cb-diagram__node[data-selected="true"]::before {
1477
+ border-color: var(--cb-tone-brand);
1478
+ box-shadow: 0 0 0 var(--cb-border-width) var(--cb-tone-brand);
1479
+ }
1480
+
1481
+ .cb-diagram__node[data-shape="diamond"][data-selected="true"] { box-shadow: none; }
1482
+
1483
+ .cb-diagram__node[data-connecting="true"],
1484
+ .cb-diagram__node[data-connecting="true"]::before {
1485
+ border-color: var(--cb-tone-info);
1486
+ border-style: dashed;
1487
+ }
1488
+
1489
+ .cb-diagram__label {
1490
+ max-inline-size: 100%;
1491
+ overflow: hidden;
1492
+ text-overflow: ellipsis;
1493
+ white-space: nowrap;
1494
+ }
1495
+
1496
+ .cb-diagram .cb-diagram__handle {
1497
+ background: var(--cb-surface);
1498
+ block-size: var(--cb-space-3);
1499
+ border: var(--cb-border-width) solid var(--cb-border-strong);
1500
+ inline-size: var(--cb-space-3);
1501
+ }
1502
+
1503
+ .cb-diagram:not(.cb-diagram--editor) .cb-diagram__handle { visibility: hidden; }
1504
+
1505
+ /* Where a pointer can hover, handles appear with the node's hover or focus; on touch there is no hover, so
1506
+ they stay visible to tap. */
1507
+ @media (hover: hover) {
1508
+ .cb-diagram--editor .react-flow__node:not(:hover, :focus-within, .selected) .cb-diagram__handle { opacity: 0; }
1509
+ }
1510
+
1511
+ .cb-diagram .react-flow__controls-button svg { fill: currentColor; }
1512
+
1513
+ .cb-diagram .react-flow__attribution { font-size: var(--cb-text-xs); }
1514
+
1515
+ .cb-diagram__outline,
1516
+ .cb-diagram__status {
1517
+ block-size: var(--cb-border-width);
1518
+ clip-path: inset(50%);
1519
+ inline-size: var(--cb-border-width);
1520
+ margin: 0;
1521
+ overflow: hidden;
1522
+ padding: 0;
1523
+ position: absolute;
1524
+ white-space: nowrap;
1525
+ }
1526
+
1527
+ @media (forced-colors: active) {
1528
+ .cb-diagram__node,
1529
+ .cb-diagram__node::before,
1530
+ .cb-diagram .cb-diagram__handle { border-color: CanvasText; }
1531
+ .cb-diagram .react-flow__edge-path { stroke: CanvasText; }
1532
+ }
1533
+
832
1534
  /* data/donut.css */
833
1535
  .cb-donut { position: relative; display: inline-grid; place-items: center; }
834
1536
  .cb-donut svg { position: absolute; inset: 0; }
@@ -1073,6 +1775,19 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1073
1775
  inset-inline: calc(var(--cb-space-8) * 3);
1074
1776
  }
1075
1777
 
1778
+ /* Fullscreen hands the element the whole screen, so the fixed canvas height stops being the right
1779
+ size: the viewport takes the space the toolbar leaves instead of leaving the rest blank. */
1780
+ .cb-pan-zoom-canvas:fullscreen {
1781
+ border-radius: var(--cb-radius-none);
1782
+ display: flex;
1783
+ flex-direction: column;
1784
+ }
1785
+
1786
+ .cb-pan-zoom-canvas:fullscreen .cb-pan-zoom-canvas__viewport {
1787
+ block-size: auto;
1788
+ flex: 1;
1789
+ }
1790
+
1076
1791
  @media (prefers-reduced-motion: reduce) {
1077
1792
  .cb-pan-zoom-canvas__content { transition: none; }
1078
1793
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceebee/ui",
3
- "version": "1.5.4",
3
+ "version": "1.7.0",
4
4
  "description": "Ceebee's design system: tokens, themed primitives, motion, and onboarding.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -55,6 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@ant-design/cssinjs": "2.1.2",
58
+ "@xyflow/react": "12.11.6",
58
59
  "antd": "6.6.1",
59
60
  "dayjs": "1.11.18"
60
61
  },