@ceebee/ui 1.6.0 → 1.8.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. */
@@ -376,4 +377,94 @@ declare const PanZoomCanvas: typeof PanZoomCanvasRoot & {
376
377
  Skeleton: typeof PanZoomCanvasSkeleton;
377
378
  };
378
379
 
379
- 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, useEffect, useContext, useMemo, 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';
@@ -13,6 +13,7 @@ import { X, Minus, Plus, RotateCcw, Minimize2, Maximize2, Search, Check, Chevron
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,
@@ -815,10 +816,14 @@ function ThemeBridge({
815
816
  if (event.target instanceof HTMLElement && event.target.id === SKIN_LINK_ID) refresh();
816
817
  };
817
818
  document.addEventListener("load", onSkinLoad, true);
819
+ const coarsePointer = window.matchMedia("(pointer: coarse)");
820
+ const onPointerChange = () => refresh();
821
+ coarsePointer.addEventListener("change", onPointerChange);
818
822
  return () => {
819
823
  rootObserver.disconnect();
820
824
  headObserver.disconnect();
821
825
  document.removeEventListener("load", onSkinLoad, true);
826
+ coarsePointer.removeEventListener("change", onPointerChange);
822
827
  };
823
828
  }, [refresh]);
824
829
  const mergedTheme = useMemo(() => ({
@@ -1668,5 +1673,311 @@ function PanZoomCanvasRoot({
1668
1673
  ] });
1669
1674
  }
1670
1675
  var PanZoomCanvas = Object.assign(PanZoomCanvasRoot, { Skeleton: PanZoomCanvasSkeleton });
1676
+ function DiagramNodeView({ data, selected }) {
1677
+ return /* @__PURE__ */ jsxs(
1678
+ "div",
1679
+ {
1680
+ className: "cb-diagram__node",
1681
+ "data-shape": data.shape,
1682
+ "data-tone": data.tone,
1683
+ "data-selected": String(Boolean(selected)),
1684
+ "data-connecting": String(data.connecting),
1685
+ children: [
1686
+ /* @__PURE__ */ jsx(Handle, { id: "left", type: "target", position: Position.Left, className: "cb-diagram__handle", isConnectable: data.connectable }),
1687
+ /* @__PURE__ */ jsx(Handle, { id: "top", type: "target", position: Position.Top, className: "cb-diagram__handle", isConnectable: data.connectable }),
1688
+ /* @__PURE__ */ jsx("span", { className: "cb-diagram__label", children: data.label }),
1689
+ data.content,
1690
+ /* @__PURE__ */ jsx(Handle, { id: "right", type: "source", position: Position.Right, className: "cb-diagram__handle", isConnectable: data.connectable }),
1691
+ /* @__PURE__ */ jsx(Handle, { id: "bottom", type: "source", position: Position.Bottom, className: "cb-diagram__handle", isConnectable: data.connectable })
1692
+ ]
1693
+ }
1694
+ );
1695
+ }
1696
+ var FLOW_NODE_TYPE = "cb-diagram";
1697
+ var FALLBACK_CELL = 20;
1698
+ function cellSize(measured) {
1699
+ return measured !== void 0 && measured > 0 ? measured : FALLBACK_CELL;
1700
+ }
1701
+ function toCells(position, cell) {
1702
+ return { x: Math.round(position.x / cell), y: Math.round(position.y / cell) };
1703
+ }
1704
+ function toFlowNodes(nodes, cell, state) {
1705
+ return nodes.map((node) => ({
1706
+ id: node.id,
1707
+ type: FLOW_NODE_TYPE,
1708
+ position: { x: node.position.x * cell, y: node.position.y * cell },
1709
+ selected: state.selectedId === node.id,
1710
+ data: {
1711
+ label: node.label,
1712
+ content: node.content,
1713
+ shape: node.shape ?? "rect",
1714
+ tone: node.tone,
1715
+ connecting: state.connectingFrom === node.id,
1716
+ connectable: state.connectable
1717
+ }
1718
+ }));
1719
+ }
1720
+ function centreOf(node) {
1721
+ const [columns, rows] = node.shape === "diamond" ? [4, 4] : [8, 3];
1722
+ return { x: node.position.x + columns / 2, y: node.position.y + rows / 2 };
1723
+ }
1724
+ function edgeHandles(from, to) {
1725
+ const a = centreOf(from);
1726
+ const b = centreOf(to);
1727
+ const dx = b.x - a.x;
1728
+ const dy = b.y - a.y;
1729
+ if (Math.abs(dx) >= Math.abs(dy)) {
1730
+ return dx >= 0 ? { sourceHandle: "right", targetHandle: "left" } : { sourceHandle: "left", targetHandle: "right" };
1731
+ }
1732
+ return dy >= 0 ? { sourceHandle: "bottom", targetHandle: "top" } : { sourceHandle: "top", targetHandle: "bottom" };
1733
+ }
1734
+ function toFlowEdges(edges, nodes) {
1735
+ const byId = new Map(nodes.map((node) => [node.id, node]));
1736
+ return edges.flatMap((edge) => {
1737
+ const from = byId.get(edge.from);
1738
+ const to = byId.get(edge.to);
1739
+ if (!from || !to) return [];
1740
+ return [
1741
+ {
1742
+ id: edge.id,
1743
+ source: edge.from,
1744
+ target: edge.to,
1745
+ ...edgeHandles(from, to),
1746
+ label: edge.label,
1747
+ className: edge.tone ? `cb-diagram__edge cb-diagram__edge--${edge.tone}` : "cb-diagram__edge",
1748
+ markerEnd: { type: MarkerType.ArrowClosed }
1749
+ }
1750
+ ];
1751
+ });
1752
+ }
1753
+ function settledMoves(changes, cell) {
1754
+ return changes.flatMap(
1755
+ (change) => change.type === "position" && change.position && change.dragging !== true ? [{ id: change.id, position: toCells(change.position, cell) }] : []
1756
+ );
1757
+ }
1758
+ function changedMoves(moves, nodes) {
1759
+ return moves.filter((move) => {
1760
+ const current = nodes.find((node) => node.id === move.id)?.position;
1761
+ return !current || current.x !== move.position.x || current.y !== move.position.y;
1762
+ });
1763
+ }
1764
+ function mergeFlowNodes(current, next) {
1765
+ const byId = new Map(current.map((node) => [node.id, node]));
1766
+ let changed = current.length !== next.length;
1767
+ const merged = next.map((node, index) => {
1768
+ const previous = byId.get(node.id);
1769
+ if (!previous) {
1770
+ changed = true;
1771
+ return node;
1772
+ }
1773
+ 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;
1774
+ if (same) return previous;
1775
+ changed = true;
1776
+ return { ...node, selected: previous.selected, measured: previous.measured };
1777
+ });
1778
+ return changed ? merged : current;
1779
+ }
1780
+ function keepIfUnchanged(current, next) {
1781
+ return next.length === current.length && next.every((item, index) => item === current[index]) ? current : next;
1782
+ }
1783
+ function applySelection(current, selectedId) {
1784
+ let changed = false;
1785
+ const next = current.map((node) => {
1786
+ const selected = node.id === selectedId;
1787
+ if (Boolean(node.selected) === selected) return node;
1788
+ changed = true;
1789
+ return { ...node, selected };
1790
+ });
1791
+ return changed ? next : current;
1792
+ }
1793
+ function connectStep(state, event) {
1794
+ if (event.type === "cancel") return { state: { mode: "idle" } };
1795
+ if (event.type === "start" || state.mode === "idle") return { state: { mode: "connecting", from: event.nodeId } };
1796
+ if (event.nodeId === state.from) return { state: { mode: "idle" } };
1797
+ return { state: { mode: "idle" }, connect: { from: state.from, to: event.nodeId } };
1798
+ }
1799
+ var NODE_TYPES = { [FLOW_NODE_TYPE]: DiagramNodeView };
1800
+ function useCellSize(ref) {
1801
+ const [cell, setCell] = useState(FALLBACK_CELL);
1802
+ useEffect(() => {
1803
+ setCell(cellSize(ref.current?.getBoundingClientRect().width));
1804
+ }, [ref]);
1805
+ return cell;
1806
+ }
1807
+ function DiagramOutline({
1808
+ id,
1809
+ nodes,
1810
+ edges,
1811
+ label
1812
+ }) {
1813
+ const byId = new Map(nodes.map((n) => [n.id, n]));
1814
+ return /* @__PURE__ */ jsx("ul", { id, className: "cb-diagram__outline", "aria-label": label, children: nodes.map((node) => {
1815
+ const leaving = edges.filter((e) => e.from === node.id && byId.has(e.to));
1816
+ return /* @__PURE__ */ jsxs("li", { children: [
1817
+ node.label,
1818
+ 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
1819
+ ] }, node.id);
1820
+ }) });
1821
+ }
1822
+ function safeId(id) {
1823
+ return id.replace(/[^a-zA-Z0-9_-]/g, "");
1824
+ }
1825
+ function DiagramSkeleton({ label = "Loading diagram" }) {
1826
+ return /* @__PURE__ */ jsx(PanZoomCanvasSkeleton, { label });
1827
+ }
1828
+ function DiagramRoot({ label, nodes, edges, hint, outlineLabel = "Diagram outline", ariaLabels }) {
1829
+ const base = safeId(useId());
1830
+ const cellRef = useRef(null);
1831
+ const cell = useCellSize(cellRef);
1832
+ const flowNodes = useMemo(() => toFlowNodes(nodes, cell, { connectable: false }), [nodes, cell]);
1833
+ const flowEdges = useMemo(() => toFlowEdges(edges, nodes), [edges, nodes]);
1834
+ return /* @__PURE__ */ jsxs("div", { className: "cb-diagram", children: [
1835
+ /* @__PURE__ */ jsx("span", { ref: cellRef, className: "cb-diagram__cell", "aria-hidden": "true" }),
1836
+ hint ? /* @__PURE__ */ jsx("p", { className: "cb-diagram__hint", children: hint }) : null,
1837
+ /* @__PURE__ */ jsx("div", { className: "cb-diagram__viewport", role: "region", "aria-label": label, "aria-describedby": `${base}-outline`, children: /* @__PURE__ */ jsxs(
1838
+ ReactFlow,
1839
+ {
1840
+ nodes: flowNodes,
1841
+ edges: flowEdges,
1842
+ nodeTypes: NODE_TYPES,
1843
+ connectionMode: ConnectionMode.Loose,
1844
+ nodesDraggable: false,
1845
+ nodesConnectable: false,
1846
+ elementsSelectable: false,
1847
+ nodesFocusable: false,
1848
+ edgesFocusable: false,
1849
+ fitView: true,
1850
+ minZoom: 0.5,
1851
+ maxZoom: 2,
1852
+ ariaLabelConfig: ariaLabels,
1853
+ children: [
1854
+ /* @__PURE__ */ jsx(Background, { gap: cell }),
1855
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false })
1856
+ ]
1857
+ }
1858
+ ) }),
1859
+ /* @__PURE__ */ jsx(DiagramOutline, { id: `${base}-outline`, nodes, edges, label: outlineLabel })
1860
+ ] });
1861
+ }
1862
+ var Diagram = Object.assign(DiagramRoot, { Skeleton: DiagramSkeleton });
1863
+ var CONNECT_KEY = "c";
1864
+ var DELETE_KEYS = ["Delete", "Backspace"];
1865
+ var defaultConnectingStatus = (node) => `Connecting from ${node}. Focus another node and press C, or press Escape to cancel.`;
1866
+ function elementAt(target) {
1867
+ if (!(target instanceof Element)) return null;
1868
+ const node = target.closest(".react-flow__node")?.getAttribute("data-id");
1869
+ if (node) return { kind: "node", id: node };
1870
+ const edge = target.closest(".react-flow__edge")?.getAttribute("data-id");
1871
+ return edge ? { kind: "edge", id: edge } : null;
1872
+ }
1873
+ function DiagramEditorRoot(props) {
1874
+ const { label, nodes, edges, hint, selectedId, connectingStatus = defaultConnectingStatus, outlineLabel = "Diagram outline", ariaLabels } = props;
1875
+ const base = safeId(useId());
1876
+ const cellRef = useRef(null);
1877
+ const cell = useCellSize(cellRef);
1878
+ const [connect, setConnect] = useState({ mode: "idle" });
1879
+ const connectingFrom = connect.mode === "connecting" ? connect.from : null;
1880
+ const latest = useRef({ props, cell, connect });
1881
+ latest.current = { props, cell, connect };
1882
+ const reportedSelection = useRef(selectedId ?? null);
1883
+ const [resync, setResync] = useState(0);
1884
+ const [flowNodes, setFlowNodes] = useState(() => toFlowNodes(nodes, cell, { selectedId, connectable: true }));
1885
+ useEffect(() => {
1886
+ setFlowNodes((current) => mergeFlowNodes(current, toFlowNodes(nodes, cell, { connectingFrom, connectable: true })));
1887
+ }, [nodes, cell, connectingFrom, resync]);
1888
+ useEffect(() => {
1889
+ if (selectedId === void 0) return;
1890
+ reportedSelection.current = selectedId;
1891
+ setFlowNodes((current) => applySelection(current, selectedId));
1892
+ }, [selectedId]);
1893
+ const flowEdges = useMemo(() => toFlowEdges(edges, nodes), [edges, nodes]);
1894
+ const snapGrid = useMemo(() => [cell, cell], [cell]);
1895
+ const step = useCallback((event) => {
1896
+ const { connect: state, props: current } = latest.current;
1897
+ const next = connectStep(state, event);
1898
+ setConnect(next.state);
1899
+ if (next.connect) current.onConnect?.(next.connect.from, next.connect.to);
1900
+ }, []);
1901
+ const onNodesChange = useCallback((changes) => {
1902
+ const { props: current, cell: size } = latest.current;
1903
+ setFlowNodes((mirror) => keepIfUnchanged(mirror, applyNodeChanges(changes.filter((change) => change.type !== "remove"), mirror)));
1904
+ const moves = changedMoves(settledMoves(changes, size), current.nodes);
1905
+ for (const move of moves) current.onMoveNode?.(move.id, move.position);
1906
+ if (moves.length > 0) setResync((n) => n + 1);
1907
+ }, []);
1908
+ const onConnectEdge = useCallback((connection) => {
1909
+ if (connection.source !== connection.target) latest.current.props.onConnect?.(connection.source, connection.target);
1910
+ }, []);
1911
+ const onBeforeDelete = useCallback(async ({ nodes: gone, edges: goneEdges }) => {
1912
+ latest.current.props.onRemove?.({ nodeIds: gone.map((n) => n.id), edgeIds: goneEdges.map((e) => e.id) });
1913
+ return false;
1914
+ }, []);
1915
+ const onSelectionChange = useCallback(({ nodes: chosen }) => {
1916
+ const id = chosen[0]?.id;
1917
+ if (!id || id === reportedSelection.current) return;
1918
+ reportedSelection.current = id;
1919
+ latest.current.props.onSelectionChange?.(id);
1920
+ }, []);
1921
+ const onNodeClick = useCallback(
1922
+ (_, node) => {
1923
+ if (latest.current.connect.mode === "connecting") step({ type: "choose", nodeId: node.id });
1924
+ },
1925
+ [step]
1926
+ );
1927
+ const onPaneClick = useCallback(() => {
1928
+ if (latest.current.connect.mode === "connecting") step({ type: "cancel" });
1929
+ }, [step]);
1930
+ const onKeyDown = useCallback(
1931
+ (event) => {
1932
+ const target = elementAt(event.target);
1933
+ if (event.key.toLowerCase() === CONNECT_KEY && target?.kind === "node" && !event.metaKey && !event.ctrlKey && !event.altKey) {
1934
+ event.preventDefault();
1935
+ step({ type: "choose", nodeId: target.id });
1936
+ } else if (event.key === "Escape" && latest.current.connect.mode === "connecting") {
1937
+ step({ type: "cancel" });
1938
+ } else if (event.key === "F2" && target) {
1939
+ event.preventDefault();
1940
+ latest.current.props.onRename?.(target);
1941
+ }
1942
+ },
1943
+ [step]
1944
+ );
1945
+ const labelOf = (id) => nodes.find((n) => n.id === id)?.label ?? "";
1946
+ return /* @__PURE__ */ jsxs("div", { className: "cb-diagram cb-diagram--editor", children: [
1947
+ /* @__PURE__ */ jsx("span", { ref: cellRef, className: "cb-diagram__cell", "aria-hidden": "true" }),
1948
+ hint ? /* @__PURE__ */ jsx("p", { className: "cb-diagram__hint", children: hint }) : null,
1949
+ /* @__PURE__ */ jsx("div", { className: "cb-diagram__viewport", role: "region", "aria-label": label, "aria-describedby": `${base}-outline`, children: /* @__PURE__ */ jsxs(
1950
+ ReactFlow,
1951
+ {
1952
+ nodes: flowNodes,
1953
+ edges: flowEdges,
1954
+ nodeTypes: NODE_TYPES,
1955
+ onNodesChange,
1956
+ onConnect: onConnectEdge,
1957
+ onBeforeDelete,
1958
+ onSelectionChange,
1959
+ onNodeClick,
1960
+ onPaneClick,
1961
+ onKeyDown,
1962
+ connectionMode: ConnectionMode.Loose,
1963
+ connectOnClick: true,
1964
+ snapToGrid: true,
1965
+ snapGrid,
1966
+ deleteKeyCode: DELETE_KEYS,
1967
+ fitView: true,
1968
+ minZoom: 0.5,
1969
+ maxZoom: 2,
1970
+ ariaLabelConfig: ariaLabels,
1971
+ children: [
1972
+ /* @__PURE__ */ jsx(Background, { gap: cell }),
1973
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false })
1974
+ ]
1975
+ }
1976
+ ) }),
1977
+ /* @__PURE__ */ jsx("p", { className: "cb-diagram__status", role: "status", children: connectingFrom ? connectingStatus(labelOf(connectingFrom)) : "" }),
1978
+ /* @__PURE__ */ jsx(DiagramOutline, { id: `${base}-outline`, nodes, edges, label: outlineLabel })
1979
+ ] });
1980
+ }
1981
+ var DiagramEditor = Object.assign(DiagramEditorRoot, { Skeleton: DiagramSkeleton });
1671
1982
 
1672
- 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 };
1983
+ 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
@@ -38,9 +38,23 @@
38
38
  --cb-weight-medium: 500;
39
39
  --cb-weight-semibold: 600;
40
40
 
41
- --cb-control-height-sm: 2rem;
42
- --cb-control-height-md: 2.5rem;
43
- --cb-control-height-lg: 3rem;
41
+ /* The height a control must not go below for the pointer in use. A mouse points and can hit a
42
+ 32px target; a finger cannot, so a coarse pointer needs 44px — WCAG 2.5.5, Apple HIG, and
43
+ Material all land there. */
44
+ --cb-control-floor-fine: 2rem;
45
+ --cb-control-floor-coarse: 2.75rem;
46
+ --cb-control-floor: var(--cb-control-floor-fine);
47
+
48
+ /* The ladder's preferred heights. A density scale retunes these; the resolved heights below are
49
+ `max()` of the preferred step and the active floor, so no scale can shrink a control under the
50
+ target its pointer can hit. */
51
+ --cb-control-height-base-sm: 2rem;
52
+ --cb-control-height-base-md: 2.5rem;
53
+ --cb-control-height-base-lg: 3rem;
54
+
55
+ --cb-control-height-sm: max(var(--cb-control-height-base-sm), var(--cb-control-floor));
56
+ --cb-control-height-md: max(var(--cb-control-height-base-md), var(--cb-control-floor));
57
+ --cb-control-height-lg: max(var(--cb-control-height-base-lg), var(--cb-control-floor));
44
58
  --cb-canvas-block-size: 32rem;
45
59
 
46
60
  --cb-sticker-enter-y: var(--cb-space-3);
@@ -73,6 +87,16 @@
73
87
  --cb-z-overlay: var(--cb-z-modal-backdrop);
74
88
  }
75
89
 
90
+ /* A coarse pointer switches the active floor to the touch target. The resolved heights above are
91
+ `max()` of the preferred step and that floor, so `sm` and `md` both reach 44px and `lg` keeps its
92
+ 48px — nothing here restates a step, so retuning `--cb-control-height-base-*` on either pointer
93
+ still cannot take a control under its floor. */
94
+ @media (pointer: coarse) {
95
+ :root {
96
+ --cb-control-floor: var(--cb-control-floor-coarse);
97
+ }
98
+ }
99
+
76
100
  /* tokens/skin.css */
77
101
  /* Skin tokens: the default Ceebee skin. A Skin file rewrites only these.
78
102
  Light is defined on bare :root so no colour has its only definition inside a media query. */
@@ -363,6 +387,513 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
363
387
  --cb-skeleton-opacity-max: 1;
364
388
  }
365
389
 
390
+ /* @xyflow/react/dist/base.css (MIT, see THIRD_PARTY_NOTICES.md) */
391
+ /* this will be exported as base.css and can be used for a basic styling */
392
+ /* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */
393
+ .react-flow {
394
+ direction: ltr;
395
+
396
+ --xy-edge-stroke-default: #b1b1b7;
397
+ --xy-edge-stroke-width-default: 1;
398
+ --xy-edge-stroke-selected-default: #555;
399
+
400
+ --xy-connectionline-stroke-default: #b1b1b7;
401
+ --xy-connectionline-stroke-width-default: 1;
402
+
403
+ --xy-attribution-background-color-default: rgba(255, 255, 255, 0.5);
404
+
405
+ --xy-minimap-background-color-default: #fff;
406
+ --xy-minimap-mask-background-color-default: rgba(240, 240, 240, 0.6);
407
+ --xy-minimap-mask-stroke-color-default: transparent;
408
+ --xy-minimap-mask-stroke-width-default: 1;
409
+ --xy-minimap-node-background-color-default: #e2e2e2;
410
+ --xy-minimap-node-stroke-color-default: transparent;
411
+ --xy-minimap-node-stroke-width-default: 2;
412
+
413
+ --xy-background-color-default: transparent;
414
+ --xy-background-pattern-dots-color-default: #91919a;
415
+ --xy-background-pattern-lines-color-default: #eee;
416
+ --xy-background-pattern-cross-color-default: #e2e2e2;
417
+ background-color: var(--xy-background-color, var(--xy-background-color-default));
418
+ --xy-node-border-default: 1px solid #bbb;
419
+ --xy-node-border-selected-default: 1px solid #555;
420
+
421
+ --xy-handle-background-color-default: #333;
422
+
423
+ --xy-selection-background-color-default: rgba(150, 150, 180, 0.1);
424
+ --xy-selection-border-default: 1px dotted rgba(155, 155, 155, 0.8);
425
+ --xy-resize-background-color-default: #3367d9;
426
+ }
427
+ .react-flow.dark {
428
+ --xy-edge-stroke-default: #3e3e3e;
429
+ --xy-edge-stroke-width-default: 1;
430
+ --xy-edge-stroke-selected-default: #727272;
431
+
432
+ --xy-connectionline-stroke-default: #b1b1b7;
433
+ --xy-connectionline-stroke-width-default: 1;
434
+
435
+ --xy-attribution-background-color-default: rgba(150, 150, 150, 0.25);
436
+
437
+ --xy-minimap-background-color-default: #141414;
438
+ --xy-minimap-mask-background-color-default: rgba(60, 60, 60, 0.6);
439
+ --xy-minimap-mask-stroke-color-default: transparent;
440
+ --xy-minimap-mask-stroke-width-default: 1;
441
+ --xy-minimap-node-background-color-default: #2b2b2b;
442
+ --xy-minimap-node-stroke-color-default: transparent;
443
+ --xy-minimap-node-stroke-width-default: 2;
444
+
445
+ --xy-background-color-default: #141414;
446
+ --xy-background-pattern-dots-color-default: #555;
447
+ --xy-background-pattern-lines-color-default: #333;
448
+ --xy-background-pattern-cross-color-default: #333;
449
+ --xy-node-color-default: #f8f8f8;
450
+ }
451
+ .react-flow__background {
452
+ background-color: var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));
453
+ pointer-events: none;
454
+ z-index: -1;
455
+ }
456
+ .react-flow__container {
457
+ position: absolute;
458
+ width: 100%;
459
+ height: 100%;
460
+ top: 0;
461
+ left: 0;
462
+ }
463
+ .react-flow__pane {
464
+ z-index: 1;
465
+ touch-action: none;
466
+ }
467
+ .react-flow__pane.draggable {
468
+ cursor: grab;
469
+ }
470
+ .react-flow__pane.dragging {
471
+ cursor: grabbing;
472
+ }
473
+ .react-flow__pane.selection {
474
+ cursor: pointer;
475
+ }
476
+ .react-flow__viewport {
477
+ transform-origin: 0 0;
478
+ z-index: 2;
479
+ pointer-events: none;
480
+ }
481
+ .react-flow__renderer {
482
+ z-index: 4;
483
+ }
484
+ .react-flow__selection {
485
+ z-index: 6;
486
+ }
487
+ .react-flow__nodesselection-rect:focus,
488
+ .react-flow__nodesselection-rect:focus-visible {
489
+ outline: none;
490
+ }
491
+ .react-flow__edge-path {
492
+ stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
493
+ stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));
494
+ fill: none;
495
+ }
496
+ .react-flow__connection-path {
497
+ stroke: var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));
498
+ stroke-width: var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));
499
+ fill: none;
500
+ }
501
+ .react-flow .react-flow__edges {
502
+ position: absolute;
503
+ }
504
+ .react-flow .react-flow__edges svg {
505
+ overflow: visible;
506
+ position: absolute;
507
+ pointer-events: none;
508
+ }
509
+ .react-flow__edge {
510
+ pointer-events: visibleStroke;
511
+ }
512
+ .react-flow__edge.selectable {
513
+ cursor: pointer;
514
+ }
515
+ .react-flow__edge.animated path {
516
+ stroke-dasharray: 5;
517
+ animation: dashdraw 0.5s linear infinite;
518
+ }
519
+ .react-flow__edge.animated path.react-flow__edge-interaction {
520
+ stroke-dasharray: none;
521
+ animation: none;
522
+ }
523
+ .react-flow__edge.inactive {
524
+ pointer-events: none;
525
+ }
526
+ .react-flow__edge.selected,
527
+ .react-flow__edge:focus,
528
+ .react-flow__edge:focus-visible {
529
+ outline: none;
530
+ }
531
+ .react-flow__edge.selected .react-flow__edge-path,
532
+ .react-flow__edge.selectable:focus .react-flow__edge-path,
533
+ .react-flow__edge.selectable:focus-visible .react-flow__edge-path {
534
+ stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
535
+ }
536
+ .react-flow__edge-textwrapper {
537
+ pointer-events: all;
538
+ }
539
+ .react-flow__edge .react-flow__edge-text {
540
+ pointer-events: none;
541
+ -webkit-user-select: none;
542
+ -moz-user-select: none;
543
+ user-select: none;
544
+ }
545
+ /* Arrowhead marker styles - use CSS custom properties as default */
546
+ .react-flow__arrowhead polyline {
547
+ stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
548
+ }
549
+ .react-flow__arrowhead polyline.arrowclosed {
550
+ fill: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
551
+ }
552
+ .react-flow__connection {
553
+ pointer-events: none;
554
+ }
555
+ .react-flow__connection .animated {
556
+ stroke-dasharray: 5;
557
+ animation: dashdraw 0.5s linear infinite;
558
+ }
559
+ svg.react-flow__connectionline {
560
+ z-index: 1001;
561
+ overflow: visible;
562
+ position: absolute;
563
+ }
564
+ .react-flow__nodes {
565
+ pointer-events: none;
566
+ transform-origin: 0 0;
567
+ }
568
+ .react-flow__node {
569
+ position: absolute;
570
+ -webkit-user-select: none;
571
+ -moz-user-select: none;
572
+ user-select: none;
573
+ pointer-events: all;
574
+ transform-origin: 0 0;
575
+ box-sizing: border-box;
576
+ cursor: default;
577
+ }
578
+ .react-flow__node.selectable {
579
+ cursor: pointer;
580
+ }
581
+ .react-flow__node.draggable {
582
+ cursor: grab;
583
+ pointer-events: all;
584
+ }
585
+ .react-flow__node.draggable.dragging {
586
+ cursor: grabbing;
587
+ }
588
+ .react-flow__nodesselection {
589
+ z-index: 3;
590
+ transform-origin: left top;
591
+ pointer-events: none;
592
+ }
593
+ .react-flow__nodesselection-rect {
594
+ position: absolute;
595
+ pointer-events: all;
596
+ cursor: grab;
597
+ }
598
+ .react-flow__handle {
599
+ position: absolute;
600
+ pointer-events: none;
601
+ min-width: 5px;
602
+ min-height: 5px;
603
+ background-color: var(--xy-handle-background-color, var(--xy-handle-background-color-default));
604
+ }
605
+ .react-flow__handle.connectingfrom {
606
+ pointer-events: all;
607
+ }
608
+ .react-flow__handle.connectionindicator {
609
+ pointer-events: all;
610
+ cursor: crosshair;
611
+ }
612
+ .react-flow__handle-bottom {
613
+ top: auto;
614
+ left: 50%;
615
+ bottom: 0;
616
+ transform: translate(-50%, 50%);
617
+ }
618
+ .react-flow__handle-top {
619
+ top: 0;
620
+ left: 50%;
621
+ transform: translate(-50%, -50%);
622
+ }
623
+ .react-flow__handle-left {
624
+ top: 50%;
625
+ left: 0;
626
+ transform: translate(-50%, -50%);
627
+ }
628
+ .react-flow__handle-right {
629
+ top: 50%;
630
+ right: 0;
631
+ transform: translate(50%, -50%);
632
+ }
633
+ .react-flow__edgeupdater {
634
+ cursor: move;
635
+ pointer-events: all;
636
+ }
637
+ .react-flow__pane.selection .react-flow__panel {
638
+ pointer-events: none;
639
+ }
640
+ .react-flow__panel {
641
+ position: absolute;
642
+ z-index: 5;
643
+ margin: 15px;
644
+ }
645
+ .react-flow__panel.top {
646
+ top: 0;
647
+ }
648
+ .react-flow__panel.bottom {
649
+ bottom: 0;
650
+ }
651
+ .react-flow__panel.top.center, .react-flow__panel.bottom.center {
652
+ left: 50%;
653
+ transform: translateX(-15px) translateX(-50%);
654
+ }
655
+ .react-flow__panel.left {
656
+ left: 0;
657
+ }
658
+ .react-flow__panel.right {
659
+ right: 0;
660
+ }
661
+ .react-flow__panel.left.center, .react-flow__panel.right.center {
662
+ top: 50%;
663
+ transform: translateY(-15px) translateY(-50%);
664
+ }
665
+ .react-flow__attribution {
666
+ font-size: 10px;
667
+ background: var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));
668
+ padding: 2px 3px;
669
+ margin: 0;
670
+ }
671
+ .react-flow__attribution a {
672
+ text-decoration: none;
673
+ color: #999;
674
+ }
675
+ @keyframes dashdraw {
676
+ from {
677
+ stroke-dashoffset: 10;
678
+ }
679
+ }
680
+ .react-flow__edgelabel-renderer {
681
+ position: absolute;
682
+ width: 100%;
683
+ height: 100%;
684
+ pointer-events: none;
685
+ -webkit-user-select: none;
686
+ -moz-user-select: none;
687
+ user-select: none;
688
+ left: 0;
689
+ top: 0;
690
+ }
691
+ .react-flow__viewport-portal {
692
+ position: absolute;
693
+ width: 100%;
694
+ height: 100%;
695
+ left: 0;
696
+ top: 0;
697
+ -webkit-user-select: none;
698
+ -moz-user-select: none;
699
+ user-select: none;
700
+ }
701
+ .react-flow__minimap {
702
+ background: var(
703
+ --xy-minimap-background-color-props,
704
+ var(--xy-minimap-background-color, var(--xy-minimap-background-color-default))
705
+ );
706
+ }
707
+ .react-flow__minimap-svg {
708
+ display: block;
709
+ }
710
+ .react-flow__minimap-mask {
711
+ fill: var(
712
+ --xy-minimap-mask-background-color-props,
713
+ var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default))
714
+ );
715
+ stroke: var(
716
+ --xy-minimap-mask-stroke-color-props,
717
+ var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default))
718
+ );
719
+ stroke-width: var(
720
+ --xy-minimap-mask-stroke-width-props,
721
+ var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default))
722
+ );
723
+ }
724
+ .react-flow__minimap-node {
725
+ fill: var(
726
+ --xy-minimap-node-background-color-props,
727
+ var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default))
728
+ );
729
+ stroke: var(
730
+ --xy-minimap-node-stroke-color-props,
731
+ var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default))
732
+ );
733
+ stroke-width: var(
734
+ --xy-minimap-node-stroke-width-props,
735
+ var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default))
736
+ );
737
+ }
738
+ .react-flow__background-pattern.dots {
739
+ fill: var(
740
+ --xy-background-pattern-color-props,
741
+ var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default))
742
+ );
743
+ }
744
+ .react-flow__background-pattern.lines {
745
+ stroke: var(
746
+ --xy-background-pattern-color-props,
747
+ var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default))
748
+ );
749
+ }
750
+ .react-flow__background-pattern.cross {
751
+ stroke: var(
752
+ --xy-background-pattern-color-props,
753
+ var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default))
754
+ );
755
+ }
756
+ .react-flow__controls {
757
+ display: flex;
758
+ flex-direction: column;
759
+ }
760
+ .react-flow__controls.horizontal {
761
+ flex-direction: row;
762
+ }
763
+ .react-flow__controls-button {
764
+ display: flex;
765
+ justify-content: center;
766
+ align-items: center;
767
+ height: 26px;
768
+ width: 26px;
769
+ padding: 4px;
770
+ }
771
+ .react-flow__controls-button svg {
772
+ width: 100%;
773
+ max-width: 12px;
774
+ max-height: 12px;
775
+ fill: currentColor;
776
+ }
777
+ .react-flow__node-input,
778
+ .react-flow__node-default,
779
+ .react-flow__node-output,
780
+ .react-flow__node-group {
781
+ border: var(--xy-node-border, var(--xy-node-border-default));
782
+ color: var(--xy-node-color, var(--xy-node-color-default));
783
+ }
784
+ .react-flow__node-input.selected,
785
+ .react-flow__node-input:focus,
786
+ .react-flow__node-input:focus-visible,
787
+ .react-flow__node-default.selected,
788
+ .react-flow__node-default:focus,
789
+ .react-flow__node-default:focus-visible,
790
+ .react-flow__node-output.selected,
791
+ .react-flow__node-output:focus,
792
+ .react-flow__node-output:focus-visible,
793
+ .react-flow__node-group.selected,
794
+ .react-flow__node-group:focus,
795
+ .react-flow__node-group:focus-visible {
796
+ outline: none;
797
+ border: var(--xy-node-border-selected, var(--xy-node-border-selected-default));
798
+ }
799
+ .react-flow__nodesselection-rect,
800
+ .react-flow__selection {
801
+ background: var(--xy-selection-background-color, var(--xy-selection-background-color-default));
802
+ border: var(--xy-selection-border, var(--xy-selection-border-default));
803
+ }
804
+ .react-flow__resize-control {
805
+ position: absolute;
806
+ }
807
+ .react-flow__resize-control.left,
808
+ .react-flow__resize-control.right {
809
+ cursor: ew-resize;
810
+ }
811
+ .react-flow__resize-control.top,
812
+ .react-flow__resize-control.bottom {
813
+ cursor: ns-resize;
814
+ }
815
+ .react-flow__resize-control.top.left,
816
+ .react-flow__resize-control.bottom.right {
817
+ cursor: nwse-resize;
818
+ }
819
+ .react-flow__resize-control.bottom.left,
820
+ .react-flow__resize-control.top.right {
821
+ cursor: nesw-resize;
822
+ }
823
+ /* handle styles */
824
+ .react-flow__resize-control.handle {
825
+ width: 5px;
826
+ height: 5px;
827
+ border: 1px solid #fff;
828
+ border-radius: 1px;
829
+ background-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
830
+ translate: -50% -50%;
831
+ }
832
+ .react-flow__resize-control.handle.left {
833
+ left: 0;
834
+ top: 50%;
835
+ }
836
+ .react-flow__resize-control.handle.right {
837
+ left: 100%;
838
+ top: 50%;
839
+ }
840
+ .react-flow__resize-control.handle.top {
841
+ left: 50%;
842
+ top: 0;
843
+ }
844
+ .react-flow__resize-control.handle.bottom {
845
+ left: 50%;
846
+ top: 100%;
847
+ }
848
+ .react-flow__resize-control.handle.top.left {
849
+ left: 0;
850
+ }
851
+ .react-flow__resize-control.handle.bottom.left {
852
+ left: 0;
853
+ }
854
+ .react-flow__resize-control.handle.top.right {
855
+ left: 100%;
856
+ }
857
+ .react-flow__resize-control.handle.bottom.right {
858
+ left: 100%;
859
+ }
860
+ /* line styles */
861
+ .react-flow__resize-control.line {
862
+ border-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
863
+ border-width: 0;
864
+ border-style: solid;
865
+ }
866
+ .react-flow__resize-control.line.left,
867
+ .react-flow__resize-control.line.right {
868
+ width: 1px;
869
+ transform: translate(-50%, 0);
870
+ top: 0;
871
+ height: 100%;
872
+ }
873
+ .react-flow__resize-control.line.left {
874
+ left: 0;
875
+ border-left-width: 1px;
876
+ }
877
+ .react-flow__resize-control.line.right {
878
+ left: 100%;
879
+ border-right-width: 1px;
880
+ }
881
+ .react-flow__resize-control.line.top,
882
+ .react-flow__resize-control.line.bottom {
883
+ height: 1px;
884
+ transform: translate(0, -50%);
885
+ left: 0;
886
+ width: 100%;
887
+ }
888
+ .react-flow__resize-control.line.top {
889
+ top: 0;
890
+ border-top-width: 1px;
891
+ }
892
+ .react-flow__resize-control.line.bottom {
893
+ border-bottom-width: 1px;
894
+ top: 100%;
895
+ }
896
+
366
897
  /* foundation/layout.css */
367
898
  .cb-flex { display: flex; gap: var(--cb-flex-gap); }
368
899
  .cb-flex--column { flex-direction: column; }
@@ -829,6 +1360,201 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
829
1360
  line-height: var(--cb-leading-normal);
830
1361
  }
831
1362
 
1363
+ /* data/diagram/diagram.css */
1364
+ /* Diagram and DiagramEditor run on React Flow. Its base stylesheet (bundled before this file) owns layout and
1365
+ reads its colours from `--xy-*` custom properties; here those are pointed at Tokens, and the node React
1366
+ Flow wraps is drawn by this library. */
1367
+
1368
+ .cb-diagram {
1369
+ --cb-diagram-cell: var(--cb-space-5);
1370
+ min-inline-size: 0;
1371
+ position: relative;
1372
+ }
1373
+
1374
+ .cb-diagram__cell {
1375
+ inline-size: var(--cb-diagram-cell);
1376
+ pointer-events: none;
1377
+ position: absolute;
1378
+ visibility: hidden;
1379
+ }
1380
+
1381
+ .cb-diagram__hint {
1382
+ color: var(--cb-fg-muted);
1383
+ font-size: var(--cb-text-xs);
1384
+ margin: 0 0 var(--cb-space-2);
1385
+ }
1386
+
1387
+ .cb-diagram__viewport {
1388
+ background: var(--cb-surface);
1389
+ block-size: var(--cb-canvas-block-size);
1390
+ border: var(--cb-border-width) solid var(--cb-border);
1391
+ border-radius: var(--cb-radius-lg);
1392
+ overflow: hidden;
1393
+ }
1394
+
1395
+ .cb-diagram .react-flow {
1396
+ --xy-background-color: var(--cb-surface);
1397
+ --xy-background-pattern-color: var(--cb-border);
1398
+ --xy-edge-stroke: var(--cb-border-strong);
1399
+ --xy-edge-stroke-selected: var(--cb-tone-brand);
1400
+ --xy-edge-stroke-width: var(--cb-border-width);
1401
+ --xy-connectionline-stroke: var(--cb-tone-info);
1402
+ --xy-connectionline-stroke-width: var(--cb-border-width);
1403
+ --xy-handle-background-color: var(--cb-surface);
1404
+ --xy-handle-border-color: var(--cb-border-strong);
1405
+ --xy-attribution-background-color: transparent;
1406
+ --xy-controls-button-background-color: var(--cb-surface);
1407
+ --xy-controls-button-background-color-hover: var(--cb-bg-subtle);
1408
+ --xy-controls-button-color: var(--cb-fg);
1409
+ --xy-controls-button-color-hover: var(--cb-fg);
1410
+ --xy-controls-button-border-color: var(--cb-border);
1411
+ --xy-controls-box-shadow: none;
1412
+ --xy-edge-label-background-color: var(--cb-surface);
1413
+ --xy-edge-label-color: var(--cb-fg-muted);
1414
+ color: var(--cb-fg);
1415
+ font-family: inherit;
1416
+ }
1417
+
1418
+ .cb-diagram .react-flow__edge-text {
1419
+ fill: var(--cb-fg-muted);
1420
+ font-size: var(--cb-text-xs);
1421
+ }
1422
+
1423
+ .cb-diagram .react-flow__edge-textbg { fill: var(--cb-surface); }
1424
+
1425
+ .cb-diagram .cb-diagram__edge--neutral .react-flow__edge-path { stroke: var(--cb-tone-neutral); }
1426
+ .cb-diagram .cb-diagram__edge--brand .react-flow__edge-path { stroke: var(--cb-tone-brand); }
1427
+ .cb-diagram .cb-diagram__edge--info .react-flow__edge-path { stroke: var(--cb-tone-info); }
1428
+ .cb-diagram .cb-diagram__edge--success .react-flow__edge-path { stroke: var(--cb-tone-success); }
1429
+ .cb-diagram .cb-diagram__edge--warning .react-flow__edge-path { stroke: var(--cb-tone-warning); }
1430
+ .cb-diagram .cb-diagram__edge--danger .react-flow__edge-path { stroke: var(--cb-tone-danger); }
1431
+
1432
+ .cb-diagram .react-flow__node:focus-visible,
1433
+ .cb-diagram .react-flow__edge:focus-visible {
1434
+ outline: var(--cb-focus-width) solid var(--cb-focus);
1435
+ outline-offset: var(--cb-focus-offset);
1436
+ }
1437
+
1438
+ .cb-diagram__node {
1439
+ align-items: center;
1440
+ background: var(--cb-surface);
1441
+ block-size: calc(var(--cb-diagram-cell) * 3);
1442
+ border: var(--cb-border-width) solid var(--cb-border-strong);
1443
+ border-radius: var(--cb-radius-md);
1444
+ box-sizing: border-box;
1445
+ color: var(--cb-fg);
1446
+ display: flex;
1447
+ flex-direction: column;
1448
+ font-size: var(--cb-text-sm);
1449
+ gap: var(--cb-space-1);
1450
+ inline-size: calc(var(--cb-diagram-cell) * 8);
1451
+ isolation: isolate;
1452
+ justify-content: center;
1453
+ padding: var(--cb-space-1) var(--cb-space-2);
1454
+ position: relative;
1455
+ text-align: center;
1456
+ }
1457
+
1458
+ .cb-diagram--editor .cb-diagram__node { cursor: grab; }
1459
+
1460
+ .cb-diagram__node[data-shape="pill"] { border-radius: var(--cb-radius-full); }
1461
+
1462
+ /* A diamond's box is square, so the handles React Flow places at the middle of each side sit exactly on the
1463
+ diamond's tips and arrows meet the drawn outline. The rotated square's diagonal equals the box's side. */
1464
+ .cb-diagram__node[data-shape="diamond"] {
1465
+ background: transparent;
1466
+ block-size: calc(var(--cb-diagram-cell) * 4);
1467
+ border-color: transparent;
1468
+ inline-size: calc(var(--cb-diagram-cell) * 4);
1469
+ }
1470
+
1471
+ .cb-diagram__node[data-shape="diamond"]::before {
1472
+ aspect-ratio: 1;
1473
+ background: var(--cb-surface);
1474
+ block-size: 70.7%;
1475
+ border: var(--cb-border-width) solid var(--cb-border-strong);
1476
+ content: "";
1477
+ inset: 0;
1478
+ margin: auto;
1479
+ position: absolute;
1480
+ rotate: 45deg;
1481
+ z-index: -1;
1482
+ }
1483
+
1484
+ .cb-diagram__node[data-tone="neutral"],
1485
+ .cb-diagram__node[data-tone="neutral"]::before { border-color: var(--cb-tone-neutral); }
1486
+ .cb-diagram__node[data-tone="brand"],
1487
+ .cb-diagram__node[data-tone="brand"]::before { border-color: var(--cb-tone-brand); }
1488
+ .cb-diagram__node[data-tone="info"],
1489
+ .cb-diagram__node[data-tone="info"]::before { border-color: var(--cb-tone-info); }
1490
+ .cb-diagram__node[data-tone="success"],
1491
+ .cb-diagram__node[data-tone="success"]::before { border-color: var(--cb-tone-success); }
1492
+ .cb-diagram__node[data-tone="warning"],
1493
+ .cb-diagram__node[data-tone="warning"]::before { border-color: var(--cb-tone-warning); }
1494
+ .cb-diagram__node[data-tone="danger"],
1495
+ .cb-diagram__node[data-tone="danger"]::before { border-color: var(--cb-tone-danger); }
1496
+
1497
+ .cb-diagram__node[data-shape="diamond"][data-tone] { border-color: transparent; }
1498
+
1499
+ .cb-diagram__node[data-selected="true"],
1500
+ .cb-diagram__node[data-selected="true"]::before {
1501
+ border-color: var(--cb-tone-brand);
1502
+ box-shadow: 0 0 0 var(--cb-border-width) var(--cb-tone-brand);
1503
+ }
1504
+
1505
+ .cb-diagram__node[data-shape="diamond"][data-selected="true"] { box-shadow: none; }
1506
+
1507
+ .cb-diagram__node[data-connecting="true"],
1508
+ .cb-diagram__node[data-connecting="true"]::before {
1509
+ border-color: var(--cb-tone-info);
1510
+ border-style: dashed;
1511
+ }
1512
+
1513
+ .cb-diagram__label {
1514
+ max-inline-size: 100%;
1515
+ overflow: hidden;
1516
+ text-overflow: ellipsis;
1517
+ white-space: nowrap;
1518
+ }
1519
+
1520
+ .cb-diagram .cb-diagram__handle {
1521
+ background: var(--cb-surface);
1522
+ block-size: var(--cb-space-3);
1523
+ border: var(--cb-border-width) solid var(--cb-border-strong);
1524
+ inline-size: var(--cb-space-3);
1525
+ }
1526
+
1527
+ .cb-diagram:not(.cb-diagram--editor) .cb-diagram__handle { visibility: hidden; }
1528
+
1529
+ /* Where a pointer can hover, handles appear with the node's hover or focus; on touch there is no hover, so
1530
+ they stay visible to tap. */
1531
+ @media (hover: hover) {
1532
+ .cb-diagram--editor .react-flow__node:not(:hover, :focus-within, .selected) .cb-diagram__handle { opacity: 0; }
1533
+ }
1534
+
1535
+ .cb-diagram .react-flow__controls-button svg { fill: currentColor; }
1536
+
1537
+ .cb-diagram .react-flow__attribution { font-size: var(--cb-text-xs); }
1538
+
1539
+ .cb-diagram__outline,
1540
+ .cb-diagram__status {
1541
+ block-size: var(--cb-border-width);
1542
+ clip-path: inset(50%);
1543
+ inline-size: var(--cb-border-width);
1544
+ margin: 0;
1545
+ overflow: hidden;
1546
+ padding: 0;
1547
+ position: absolute;
1548
+ white-space: nowrap;
1549
+ }
1550
+
1551
+ @media (forced-colors: active) {
1552
+ .cb-diagram__node,
1553
+ .cb-diagram__node::before,
1554
+ .cb-diagram .cb-diagram__handle { border-color: CanvasText; }
1555
+ .cb-diagram .react-flow__edge-path { stroke: CanvasText; }
1556
+ }
1557
+
832
1558
  /* data/donut.css */
833
1559
  .cb-donut { position: relative; display: inline-grid; place-items: center; }
834
1560
  .cb-donut svg { position: absolute; inset: 0; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceebee/ui",
3
- "version": "1.6.0",
3
+ "version": "1.8.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
  },