@umodeapporg/ui 0.27.0 → 0.29.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.
Files changed (153) hide show
  1. package/dist/assets/umode-ui.css +0 -5
  2. package/dist/components/DateTime/DatePicker.d.ts +2 -0
  3. package/dist/components/DateTime/DatePicker.js +5 -2
  4. package/dist/components/DateTime/DatePicker.js.map +1 -1
  5. package/dist/components/DateTime/DateRangePicker.d.ts +2 -0
  6. package/dist/components/DateTime/DateRangePicker.js +3 -0
  7. package/dist/components/DateTime/DateRangePicker.js.map +1 -1
  8. package/dist/components/Form/ControlledButton.d.ts +1 -1
  9. package/dist/components/Form/ControlledButton.js +2 -2
  10. package/dist/components/Form/ControlledButton.js.map +1 -1
  11. package/dist/components/Form/ControlledCheckbox.d.ts +5 -1
  12. package/dist/components/Form/ControlledCheckbox.js +53 -39
  13. package/dist/components/Form/ControlledCheckbox.js.map +1 -1
  14. package/dist/components/Form/ControlledDialogPrimitives.js +17 -5
  15. package/dist/components/Form/ControlledDialogPrimitives.js.map +1 -1
  16. package/dist/components/Form/ControlledDrawer.js +1 -0
  17. package/dist/components/Form/ControlledDrawer.js.map +1 -1
  18. package/dist/components/Form/ControlledImage/index.js +2 -0
  19. package/dist/components/Form/ControlledImage/index.js.map +1 -1
  20. package/dist/components/Form/ControlledInput.d.ts +1 -1
  21. package/dist/components/Form/ControlledInput.js +4 -4
  22. package/dist/components/Form/ControlledInput.js.map +1 -1
  23. package/dist/components/Form/ControlledSelect.js +1 -1
  24. package/dist/components/Form/ControlledSelect.js.map +1 -1
  25. package/dist/components/Form/ControlledTabs.js +17 -3
  26. package/dist/components/Form/ControlledTabs.js.map +1 -1
  27. package/dist/components/Form/ControlledTabsPrimitives.js +6 -6
  28. package/dist/components/Form/ControlledTabsPrimitives.js.map +1 -1
  29. package/dist/components/Form/ControlledTextarea.js +2 -2
  30. package/dist/components/Form/ControlledTextarea.js.map +1 -1
  31. package/dist/components/Form/ControlledTimeline.js +11 -8
  32. package/dist/components/Form/ControlledTimeline.js.map +1 -1
  33. package/dist/components/Form/ControlledVoiceTextarea.d.ts +2 -0
  34. package/dist/components/Form/ControlledVoiceTextarea.js +3 -0
  35. package/dist/components/Form/ControlledVoiceTextarea.js.map +1 -1
  36. package/dist/components/Form/FieldLabel.d.ts +16 -0
  37. package/dist/components/Form/FieldLabel.js +7 -0
  38. package/dist/components/Form/FieldLabel.js.map +1 -0
  39. package/dist/components/Form/RichTextEditor.js +15 -1
  40. package/dist/components/Form/RichTextEditor.js.map +1 -1
  41. package/dist/components/Form/attachments/AttachmentPreviewList.js +3 -0
  42. package/dist/components/Form/attachments/AttachmentPreviewList.js.map +1 -1
  43. package/dist/components/Form/attachments/FileDropzone.js +3 -3
  44. package/dist/components/Form/attachments/FileDropzone.js.map +1 -1
  45. package/dist/components/Navigation/PageTitle.d.ts +6 -0
  46. package/dist/components/Navigation/PageTitle.js +6 -4
  47. package/dist/components/Navigation/PageTitle.js.map +1 -1
  48. package/dist/components/Search/filters/FiltersPanel.js +1 -0
  49. package/dist/components/Search/filters/FiltersPanel.js.map +1 -1
  50. package/dist/components/Search/filters/FiltersSummaryTooltip.js +2 -2
  51. package/dist/components/Search/filters/FiltersSummaryTooltip.js.map +1 -1
  52. package/dist/components/Shell/LanguageToggle.js +1 -1
  53. package/dist/components/Shell/LanguageToggle.js.map +1 -1
  54. package/dist/components/Shell/SidebarMenu.js +9 -1
  55. package/dist/components/Shell/SidebarMenu.js.map +1 -1
  56. package/dist/components/Shell/ThemeToggle.js +2 -1
  57. package/dist/components/Shell/ThemeToggle.js.map +1 -1
  58. package/dist/components/UI/BaseDataCard.d.ts +2 -0
  59. package/dist/components/UI/BaseDataCard.js +6 -0
  60. package/dist/components/UI/BaseDataCard.js.map +1 -1
  61. package/dist/components/UI/BulkActionBar.js +2 -0
  62. package/dist/components/UI/BulkActionBar.js.map +1 -1
  63. package/dist/components/UI/Chips/OutlinedChip.js +13 -6
  64. package/dist/components/UI/Chips/OutlinedChip.js.map +1 -1
  65. package/dist/components/UI/ColorSwatch.js +2 -2
  66. package/dist/components/UI/ColorSwatch.js.map +1 -1
  67. package/dist/components/UI/ControlledAccordion.js +10 -4
  68. package/dist/components/UI/ControlledAccordion.js.map +1 -1
  69. package/dist/components/UI/ControlledCollapse.js +2 -2
  70. package/dist/components/UI/ControlledCollapse.js.map +1 -1
  71. package/dist/components/UI/ControlledMenu.js +3 -3
  72. package/dist/components/UI/ControlledMenu.js.map +1 -1
  73. package/dist/components/UI/ControlledPopover.d.ts +12 -0
  74. package/dist/components/UI/ControlledPopover.js +35 -5
  75. package/dist/components/UI/ControlledPopover.js.map +1 -1
  76. package/dist/components/UI/ControlledProgress.js +3 -3
  77. package/dist/components/UI/ControlledProgress.js.map +1 -1
  78. package/dist/components/UI/ControlledSlider.d.ts +2 -0
  79. package/dist/components/UI/ControlledSlider.js +8 -3
  80. package/dist/components/UI/ControlledSlider.js.map +1 -1
  81. package/dist/components/UI/ControlledTooltip.js +7 -3
  82. package/dist/components/UI/ControlledTooltip.js.map +1 -1
  83. package/dist/components/UI/DataTable/DataTable.js +1 -1
  84. package/dist/components/UI/DataTable/DataTableBody.js +4 -0
  85. package/dist/components/UI/DataTable/DataTableBody.js.map +1 -1
  86. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js +4 -2
  87. package/dist/components/UI/DataTable/DataTableBulkEditDialog.js.map +1 -1
  88. package/dist/components/UI/DataTable/DataTableBulkEditRowsPicker.js +1 -1
  89. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.d.ts +5 -1
  90. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js +19 -4
  91. package/dist/components/UI/DataTable/DataTableBulkEditValueEditor.js.map +1 -1
  92. package/dist/components/UI/DataTable/DataTableColumnToggle.js +3 -1
  93. package/dist/components/UI/DataTable/DataTableColumnToggle.js.map +1 -1
  94. package/dist/components/UI/DataTable/DataTableFilters.js +1 -1
  95. package/dist/components/UI/DataTable/DataTablePagination.js +4 -0
  96. package/dist/components/UI/DataTable/DataTablePagination.js.map +1 -1
  97. package/dist/components/UI/DataTable/DataTableStates.js +2 -2
  98. package/dist/components/UI/DataTable/DataTableStates.js.map +1 -1
  99. package/dist/components/UI/DataTable/DataTableToolbar.js +1 -1
  100. package/dist/components/UI/DataTable/applyDataTableFilters.js +1 -1
  101. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js +2 -1
  102. package/dist/components/UI/DataTable/cells/DataTableEditableCell.js.map +1 -1
  103. package/dist/components/UI/DataTable/types.d.ts +4 -0
  104. package/dist/components/UI/DataTable/types.js.map +1 -1
  105. package/dist/components/UI/Flow/FlowBlockNode.js +1 -0
  106. package/dist/components/UI/Flow/FlowBlockNode.js.map +1 -1
  107. package/dist/components/UI/Flow/FlowCanvas.js +2 -2
  108. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  109. package/dist/components/UI/Flow/FlowConnectionEdge.js +1 -0
  110. package/dist/components/UI/Flow/FlowConnectionEdge.js.map +1 -1
  111. package/dist/components/UI/Flow/FlowInspector.js +3 -2
  112. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  113. package/dist/components/UI/Flow/FlowIssueList.js +2 -2
  114. package/dist/components/UI/Flow/FlowIssueList.js.map +1 -1
  115. package/dist/components/UI/Flow/FlowLaneMap.js +2 -2
  116. package/dist/components/UI/Flow/FlowLaneMap.js.map +1 -1
  117. package/dist/components/UI/Flow/FlowResizablePanel.js +3 -2
  118. package/dist/components/UI/Flow/FlowResizablePanel.js.map +1 -1
  119. package/dist/components/UI/Flow/FlowZoomControls.js +1 -0
  120. package/dist/components/UI/Flow/FlowZoomControls.js.map +1 -1
  121. package/dist/components/UI/HorizontalScrollArea.d.ts +1 -1
  122. package/dist/components/UI/HorizontalScrollArea.js +1 -0
  123. package/dist/components/UI/HorizontalScrollArea.js.map +1 -1
  124. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js +1 -0
  125. package/dist/components/UI/ImageAnnotator/components/AnnotationColorPopover.js.map +1 -1
  126. package/dist/components/UI/ImagePlaceholder.js +5 -2
  127. package/dist/components/UI/ImagePlaceholder.js.map +1 -1
  128. package/dist/components/UI/ImageZoomOverlay.js +1 -0
  129. package/dist/components/UI/ImageZoomOverlay.js.map +1 -1
  130. package/dist/components/UI/MaterialSymbol/index.js +5 -0
  131. package/dist/components/UI/MaterialSymbol/index.js.map +1 -1
  132. package/dist/components/UI/NoDataFound/index.js +3 -3
  133. package/dist/components/UI/NoDataFound/index.js.map +1 -1
  134. package/dist/components/UI/RichTextContent.js +3 -3
  135. package/dist/components/UI/RichTextContent.js.map +1 -1
  136. package/dist/components/UI/ThreeDots.js +15 -11
  137. package/dist/components/UI/ThreeDots.js.map +1 -1
  138. package/dist/helpers/mergeClasses.d.ts +7 -3
  139. package/dist/helpers/mergeClasses.js +23 -1
  140. package/dist/helpers/mergeClasses.js.map +1 -1
  141. package/dist/hooks/useVoiceTranscription.js +5 -2
  142. package/dist/hooks/useVoiceTranscription.js.map +1 -1
  143. package/dist/index.d.ts +2 -0
  144. package/dist/index.js +58 -54
  145. package/dist/index.js.map +1 -1
  146. package/dist/locales/en/ui.json.d.ts +8 -3
  147. package/dist/locales/en/ui.json.js +3 -3
  148. package/dist/locales/index.d.ts +10 -0
  149. package/dist/locales/pt/ui.json.d.ts +8 -3
  150. package/dist/locales/pt/ui.json.js +3 -3
  151. package/package.json +1 -1
  152. package/src/assets/styles/base.css +7 -1
  153. package/tailwind-plugins/glass.js +23 -28
@@ -1 +1 @@
1
- {"version":3,"file":"FlowCanvas.js","sources":["../../../../src/components/UI/Flow/FlowCanvas.tsx"],"sourcesContent":["import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport {\n Background,\n BackgroundVariant,\n type Connection,\n type EdgeChange,\n MarkerType,\n MiniMap,\n type NodeChange,\n type PanelPosition,\n type ProOptions,\n ReactFlow,\n useReactFlow,\n useStore as useReactFlowStore,\n} from '@xyflow/react';\n\nimport FlowBlockNode, { FLOW_INPUT_HANDLE_ID, type IFlowAddableBlock, type TFlowBlockNode } from './FlowBlockNode';\nimport { flowOutputsOf } from './flowBlockRegistry';\nimport FlowConnectionEdge, { type TFlowConnectionEdge } from './FlowConnectionEdge';\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport { canConnect, layoutFlowByLevels } from './flowGraph';\nimport type { IFlowIssue, IFlowPosition, IFlowRunOverlay, TFlowIssueSeverity, TFlowPortId } from './flowTypes';\nimport { boundsOfRects, type IFlowRect, type IFlowViewport, viewportToReveal } from './flowViewport';\nimport FlowZoomControls from './FlowZoomControls';\nimport { useFlowLabels } from './useFlowLabels';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nconst PRO_OPTIONS: ProOptions = { hideAttribution: true };\nconst NODE_TYPES = { flowBlock: FlowBlockNode };\nconst EDGE_TYPES = { flowConnection: FlowConnectionEdge };\nconst MINIMAP_SIZE = { width: 160, height: 104 };\n/** A small flow fits on screen whole, where a minimap only covers blocks. */\nconst MINIMAP_MIN_NODES = 5;\nconst SNAP_GRID: [number, number] = [16, 16];\nconst PORTS: Array<TFlowPortId> = ['next', 'true', 'false', 'error'];\nconst REVEAL_DURATION_MS = 200;\n\nexport interface IFlowCanvasProps {\n issues?: Array<IFlowIssue>;\n runOverlay?: IFlowRunOverlay | null;\n readOnly?: boolean;\n /** Accessible name of the canvas region, from `t()`. Defaults to the library label. */\n ariaLabel?: string;\n className?: string;\n /** Where the zoom and history controls sit. */\n controlsPosition?: PanelPosition;\n /** The dot grid behind the blocks. Blocks still snap to the grid when it is hidden. */\n background?: 'dots' | 'none';\n}\n\nconst isPortId = (value: string | null | undefined): value is TFlowPortId => PORTS.includes(value as TFlowPortId);\n\nconst worstSeverity = (issues: Array<IFlowIssue>): TFlowIssueSeverity | undefined => {\n if (issues.some((issue) => issue.severity === 'error')) return 'error';\n if (issues.some((issue) => issue.severity === 'warning')) return 'warning';\n return undefined;\n};\n\nconst isTypingTarget = (target: EventTarget | null): boolean =>\n target instanceof HTMLElement &&\n (target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName));\n\n/**\n * The editing surface. Renders the graph held by `FlowEditorProvider` and turns every\n * gesture (drag, connect, select, delete, shortcuts) into a store action, so undo\n * and the dirty flag see all of them.\n */\nconst FlowCanvas: React.FC<IFlowCanvasProps> = ({\n issues = [],\n runOverlay,\n readOnly = false,\n ariaLabel,\n className,\n controlsPosition,\n background = 'dots',\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n\n const graph = useFlowEditor((state) => state.graph);\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedEdgeIds = useFlowEditor((state) => state.selectedEdgeIds);\n const canUndo = useFlowEditor((state) => state.past.length > 0);\n const canRedo = useFlowEditor((state) => state.future.length > 0);\n\n const safeIssues = useMemo(() => (Array.isArray(issues) ? issues : []), [issues]);\n // Measured sizes are view state, not part of the flow: kept here so the minimap can draw the blocks.\n const [measuredSizes, setMeasuredSizes] = useState<Record<string, { width: number; height: number }>>({});\n\n const { getViewport, setViewport } = useReactFlow<TFlowBlockNode, TFlowConnectionEdge>();\n const canvasWidth = useReactFlowStore((state) => state.width);\n const canvasHeight = useReactFlowStore((state) => state.height);\n // Blocks just added and selected: kept in view until the user moves the view or the selection, so\n // the host panel that opens on selection (and narrows the canvas) cannot hide them.\n const [revealNodeIds, setRevealNodeIds] = useState<Array<string>>([]);\n const knownNodeIdsRef = useRef<Set<string> | null>(null);\n const pendingViewportRef = useRef<{ viewport: IFlowViewport; settlesAt: number } | null>(null);\n\n useEffect(() => {\n const currentIds = new Set(graph.nodes.map((node) => node.nodeId));\n const knownIds = knownNodeIdsRef.current;\n knownNodeIdsRef.current = currentIds;\n const addedAndSelected = knownIds ? selectedNodeIds.filter((id) => currentIds.has(id) && !knownIds.has(id)) : [];\n if (addedAndSelected.length > 0) {\n setRevealNodeIds(addedAndSelected);\n return;\n }\n setRevealNodeIds((current) =>\n current.length > 0 && !current.every((id) => selectedNodeIds.includes(id)) ? [] : current,\n );\n }, [graph.nodes, selectedNodeIds]);\n\n useEffect(() => {\n if (revealNodeIds.length === 0) return;\n const rects: Array<IFlowRect> = [];\n for (const nodeId of revealNodeIds) {\n const node = graph.nodes.find((candidate) => candidate.nodeId === nodeId);\n const size = measuredSizes[nodeId];\n if (!node || !size) return;\n rects.push({ ...node.position, ...size });\n }\n const bounds = boundsOfRects(rects);\n if (!bounds) return;\n // A pan still animating reports an in-between viewport; build on where it is heading instead.\n const pending = pendingViewportRef.current;\n const baseViewport = pending && pending.settlesAt > Date.now() ? pending.viewport : getViewport();\n const target = viewportToReveal(baseViewport, bounds, { width: canvasWidth, height: canvasHeight });\n if (!target) return;\n pendingViewportRef.current = { viewport: target, settlesAt: Date.now() + REVEAL_DURATION_MS };\n void setViewport(target, { duration: REVEAL_DURATION_MS });\n }, [revealNodeIds, graph.nodes, measuredSizes, canvasWidth, canvasHeight, getViewport, setViewport]);\n\n const stopRevealing = useCallback(() => {\n pendingViewportRef.current = null;\n setRevealNodeIds((current) => (current.length > 0 ? [] : current));\n }, []);\n\n const addableBlocks = useMemo<Array<IFlowAddableBlock>>(() => {\n if (readOnly) return [];\n return registry.list\n .filter((definition) => definition.category !== 'trigger')\n .filter(\n (definition) =>\n definition.maxInstances === undefined ||\n graph.nodes.filter((node) => node.type === definition.type).length < definition.maxInstances,\n )\n .map((definition) => ({ type: definition.type, label: definition.label, icon: definition.icon }));\n }, [registry, graph.nodes, readOnly]);\n\n // A block in the middle of an edge needs an output to carry the flow on to the old target.\n const insertableBlocks = useMemo<Array<IFlowAddableBlock>>(\n () =>\n addableBlocks.filter((block) => {\n const definition = registry.get(block.type);\n return definition ? flowOutputsOf(definition, definition.createDefaultConfig()).length > 0 : false;\n }),\n [addableBlocks, registry],\n );\n\n const nodes = useMemo<Array<TFlowBlockNode>>(\n () =>\n graph.nodes.map((node) => {\n const definition = registry.get(node.type);\n const nodeIssues = safeIssues.filter((issue) => issue.nodeId === node.nodeId);\n return {\n id: node.nodeId,\n type: 'flowBlock',\n position: node.position,\n measured: measuredSizes[node.nodeId],\n selected: selectedNodeIds.includes(node.nodeId),\n draggable: !readOnly,\n connectable: !readOnly,\n data: {\n icon: definition?.resolveIcon?.(node.config) ?? definition?.icon ?? 'help',\n title: node.label ?? definition?.resolveLabel?.(node.config) ?? definition?.label ?? node.type,\n categoryLabel: definition ? labels.categories[definition.category] : '',\n summary: definition\n ? definition.renderSummary(node.config)\n : labels.issueMessage({ code: 'unknownBlock', severity: 'error' }),\n hasInput: definition ? definition.category !== 'trigger' : true,\n outputs: flowOutputsOf(definition, node.config),\n connectedPorts: graph.edges.filter((edge) => edge.source === node.nodeId).map((edge) => edge.sourcePort),\n portLabels: labels.ports,\n addableBlocks,\n onAddFromPort: readOnly\n ? undefined\n : (port, type) => store.getState().addNodeFromPort(type, node.nodeId, port),\n disabled: node.disabled,\n issueSeverity: worstSeverity(nodeIssues),\n issueCount: nodeIssues.length,\n runStatus: runOverlay?.steps?.[node.nodeId],\n },\n };\n }),\n [\n graph.nodes,\n graph.edges,\n registry,\n safeIssues,\n selectedNodeIds,\n readOnly,\n labels,\n runOverlay,\n measuredSizes,\n addableBlocks,\n store,\n ],\n );\n\n const edges = useMemo<Array<TFlowConnectionEdge>>(() => {\n const traversed = new Set(runOverlay?.traversedEdgeIds ?? []);\n return graph.edges.map((edge) => ({\n id: edge.edgeId,\n type: 'flowConnection',\n source: edge.source,\n sourceHandle: edge.sourcePort,\n target: edge.target,\n targetHandle: FLOW_INPUT_HANDLE_ID,\n selected: selectedEdgeIds.includes(edge.edgeId),\n markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },\n data: {\n label: edge.sourcePort === 'next' ? undefined : labels.ports[edge.sourcePort],\n traversed: traversed.has(edge.edgeId),\n issueSeverity: worstSeverity(safeIssues.filter((issue) => issue.edgeId === edge.edgeId)),\n insertableBlocks,\n onInsert: readOnly ? undefined : (type: string) => store.getState().insertNodeOnEdge(type, edge.edgeId),\n },\n }));\n }, [graph.edges, selectedEdgeIds, runOverlay, labels, safeIssues, insertableBlocks, readOnly, store]);\n\n const handleNodesChange = useCallback(\n (changes: Array<NodeChange<TFlowBlockNode>>) => {\n const positions: Record<string, IFlowPosition> = {};\n const sizes: Record<string, { width: number; height: number }> = {};\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type === 'dimensions' && change.dimensions) sizes[change.id] = change.dimensions;\n if (change.type === 'position' && change.position && !readOnly) positions[change.id] = change.position;\n if (change.type === 'select') {\n const current: Array<string> = selection ?? store.getState().selectedNodeIds;\n selection = change.selected\n ? [...new Set([...current, change.id])]\n : current.filter((id) => id !== change.id);\n }\n }\n if (Object.keys(sizes).length > 0) setMeasuredSizes((current) => ({ ...current, ...sizes }));\n if (Object.keys(positions).length > 0) store.getState().moveNodes(positions);\n if (selection) store.getState().setSelection({ nodeIds: selection });\n },\n [store, readOnly],\n );\n\n const handleEdgesChange = useCallback(\n (changes: Array<EdgeChange<TFlowConnectionEdge>>) => {\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type !== 'select') continue;\n const current: Array<string> = selection ?? store.getState().selectedEdgeIds;\n selection = change.selected ? [...new Set([...current, change.id])] : current.filter((id) => id !== change.id);\n }\n if (selection) store.getState().setSelection({ edgeIds: selection });\n },\n [store],\n );\n\n const handleConnect = useCallback(\n (connection: Connection) => {\n if (readOnly || !isPortId(connection.sourceHandle)) return;\n store.getState().connect(connection.source, connection.sourceHandle, connection.target);\n },\n [store, readOnly],\n );\n\n const isValidConnection = useCallback(\n (connection: Connection | TFlowConnectionEdge) =>\n isPortId(connection.sourceHandle) &&\n canConnect(store.getState().graph, registry, {\n source: connection.source,\n sourcePort: connection.sourceHandle,\n target: connection.target,\n }),\n [store, registry],\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n if (readOnly || isTypingTarget(event.target)) return;\n const state = store.getState();\n const withModifier = event.metaKey || event.ctrlKey;\n const key = event.key.toLowerCase();\n\n if (event.key === 'Delete' || event.key === 'Backspace') state.removeSelected();\n else if (withModifier && key === 'z' && event.shiftKey) state.redo();\n else if (withModifier && key === 'z') state.undo();\n else if (withModifier && key === 'y') state.redo();\n else if (withModifier && key === 'c') state.copySelected();\n else if (withModifier && key === 'v') state.paste();\n else if (withModifier && key === 'd') state.duplicateSelected();\n else return;\n event.preventDefault();\n },\n [store, readOnly],\n );\n\n const handleLayout = useCallback(() => {\n const state = store.getState();\n state.recordHistory();\n state.moveNodes(layoutFlowByLevels(state.graph, registry));\n }, [store, registry]);\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.canvas')}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n className={twMerge(\n 'umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas focus:outline-none',\n className,\n )}\n >\n <ReactFlow<TFlowBlockNode, TFlowConnectionEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={handleConnect}\n isValidConnection={isValidConnection}\n onNodeDragStart={() => {\n stopRevealing();\n store.getState().recordHistory();\n }}\n onMoveStart={(event) => {\n if (event) stopRevealing();\n }}\n onPaneClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n deleteKeyCode={null}\n nodesDraggable={!readOnly}\n nodesConnectable={!readOnly}\n snapToGrid\n snapGrid={SNAP_GRID}\n minZoom={0.25}\n maxZoom={2}\n fitView\n fitViewOptions={{ padding: 0.2, maxZoom: 1 }}\n proOptions={PRO_OPTIONS}\n >\n {background === 'dots' && <Background variant={BackgroundVariant.Dots} gap={16} size={1} />}\n {nodes.length >= MINIMAP_MIN_NODES && (\n <MiniMap pannable zoomable position=\"bottom-right\" style={MINIMAP_SIZE} />\n )}\n <FlowZoomControls\n onUndo={readOnly ? undefined : () => store.getState().undo()}\n onRedo={readOnly ? undefined : () => store.getState().redo()}\n canUndo={canUndo}\n canRedo={canRedo}\n onLayout={readOnly ? undefined : handleLayout}\n position={controlsPosition}\n />\n </ReactFlow>\n </div>\n );\n};\n\nexport default FlowCanvas;\n"],"names":["useReactFlowStore"],"mappings":"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"}
1
+ {"version":3,"file":"FlowCanvas.js","sources":["../../../../src/components/UI/Flow/FlowCanvas.tsx"],"sourcesContent":["import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\n\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport {\n Background,\n BackgroundVariant,\n type Connection,\n type EdgeChange,\n MarkerType,\n MiniMap,\n type NodeChange,\n type PanelPosition,\n type ProOptions,\n ReactFlow,\n useReactFlow,\n useStore as useReactFlowStore,\n} from '@xyflow/react';\n\nimport FlowBlockNode, { FLOW_INPUT_HANDLE_ID, type IFlowAddableBlock, type TFlowBlockNode } from './FlowBlockNode';\nimport { flowOutputsOf } from './flowBlockRegistry';\nimport FlowConnectionEdge, { type TFlowConnectionEdge } from './FlowConnectionEdge';\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport { canConnect, layoutFlowByLevels } from './flowGraph';\nimport type { IFlowIssue, IFlowPosition, IFlowRunOverlay, TFlowIssueSeverity, TFlowPortId } from './flowTypes';\nimport { boundsOfRects, type IFlowRect, type IFlowViewport, viewportToReveal } from './flowViewport';\nimport FlowZoomControls from './FlowZoomControls';\nimport { useFlowLabels } from './useFlowLabels';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nconst PRO_OPTIONS: ProOptions = { hideAttribution: true };\nconst NODE_TYPES = { flowBlock: FlowBlockNode };\nconst EDGE_TYPES = { flowConnection: FlowConnectionEdge };\nconst MINIMAP_SIZE = { width: 160, height: 104 };\n/** A small flow fits on screen whole, where a minimap only covers blocks. */\nconst MINIMAP_MIN_NODES = 5;\nconst SNAP_GRID: [number, number] = [16, 16];\nconst PORTS: Array<TFlowPortId> = ['next', 'true', 'false', 'error'];\nconst REVEAL_DURATION_MS = 200;\n\nexport interface IFlowCanvasProps {\n issues?: Array<IFlowIssue>;\n runOverlay?: IFlowRunOverlay | null;\n readOnly?: boolean;\n /** Accessible name of the canvas region, from `t()`. Defaults to the library label. */\n ariaLabel?: string;\n className?: string;\n /** Where the zoom and history controls sit. */\n controlsPosition?: PanelPosition;\n /** The dot grid behind the blocks. Blocks still snap to the grid when it is hidden. */\n background?: 'dots' | 'none';\n}\n\nconst isPortId = (value: string | null | undefined): value is TFlowPortId => PORTS.includes(value as TFlowPortId);\n\nconst worstSeverity = (issues: Array<IFlowIssue>): TFlowIssueSeverity | undefined => {\n if (issues.some((issue) => issue.severity === 'error')) return 'error';\n if (issues.some((issue) => issue.severity === 'warning')) return 'warning';\n return undefined;\n};\n\nconst isTypingTarget = (target: EventTarget | null): boolean =>\n target instanceof HTMLElement &&\n (target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName));\n\n/**\n * The editing surface. Renders the graph held by `FlowEditorProvider` and turns every\n * gesture (drag, connect, select, delete, shortcuts) into a store action, so undo\n * and the dirty flag see all of them.\n */\nconst FlowCanvas: React.FC<IFlowCanvasProps> = ({\n issues = [],\n runOverlay,\n readOnly = false,\n ariaLabel,\n className,\n controlsPosition,\n background = 'dots',\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n\n const graph = useFlowEditor((state) => state.graph);\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedEdgeIds = useFlowEditor((state) => state.selectedEdgeIds);\n const canUndo = useFlowEditor((state) => state.past.length > 0);\n const canRedo = useFlowEditor((state) => state.future.length > 0);\n\n const safeIssues = useMemo(() => (Array.isArray(issues) ? issues : []), [issues]);\n // Measured sizes are view state, not part of the flow: kept here so the minimap can draw the blocks.\n const [measuredSizes, setMeasuredSizes] = useState<Record<string, { width: number; height: number }>>({});\n\n const { getViewport, setViewport } = useReactFlow<TFlowBlockNode, TFlowConnectionEdge>();\n const canvasWidth = useReactFlowStore((state) => state.width);\n const canvasHeight = useReactFlowStore((state) => state.height);\n // Blocks just added and selected: kept in view until the user moves the view or the selection, so\n // the host panel that opens on selection (and narrows the canvas) cannot hide them.\n const [revealNodeIds, setRevealNodeIds] = useState<Array<string>>([]);\n const knownNodeIdsRef = useRef<Set<string> | null>(null);\n const pendingViewportRef = useRef<{ viewport: IFlowViewport; settlesAt: number } | null>(null);\n\n useEffect(() => {\n const currentIds = new Set(graph.nodes.map((node) => node.nodeId));\n const knownIds = knownNodeIdsRef.current;\n knownNodeIdsRef.current = currentIds;\n const addedAndSelected = knownIds ? selectedNodeIds.filter((id) => currentIds.has(id) && !knownIds.has(id)) : [];\n if (addedAndSelected.length > 0) {\n setRevealNodeIds(addedAndSelected);\n return;\n }\n setRevealNodeIds((current) =>\n current.length > 0 && !current.every((id) => selectedNodeIds.includes(id)) ? [] : current,\n );\n }, [graph.nodes, selectedNodeIds]);\n\n useEffect(() => {\n if (revealNodeIds.length === 0) return;\n const rects: Array<IFlowRect> = [];\n for (const nodeId of revealNodeIds) {\n const node = graph.nodes.find((candidate) => candidate.nodeId === nodeId);\n const size = measuredSizes[nodeId];\n if (!node || !size) return;\n rects.push({ ...node.position, ...size });\n }\n const bounds = boundsOfRects(rects);\n if (!bounds) return;\n // A pan still animating reports an in-between viewport; build on where it is heading instead.\n const pending = pendingViewportRef.current;\n const baseViewport = pending && pending.settlesAt > Date.now() ? pending.viewport : getViewport();\n const target = viewportToReveal(baseViewport, bounds, { width: canvasWidth, height: canvasHeight });\n if (!target) return;\n pendingViewportRef.current = { viewport: target, settlesAt: Date.now() + REVEAL_DURATION_MS };\n void setViewport(target, { duration: REVEAL_DURATION_MS });\n }, [revealNodeIds, graph.nodes, measuredSizes, canvasWidth, canvasHeight, getViewport, setViewport]);\n\n const stopRevealing = useCallback(() => {\n pendingViewportRef.current = null;\n setRevealNodeIds((current) => (current.length > 0 ? [] : current));\n }, []);\n\n const addableBlocks = useMemo<Array<IFlowAddableBlock>>(() => {\n if (readOnly) return [];\n return registry.list\n .filter((definition) => definition.category !== 'trigger')\n .filter(\n (definition) =>\n definition.maxInstances === undefined ||\n graph.nodes.filter((node) => node.type === definition.type).length < definition.maxInstances,\n )\n .map((definition) => ({ type: definition.type, label: definition.label, icon: definition.icon }));\n }, [registry, graph.nodes, readOnly]);\n\n // A block in the middle of an edge needs an output to carry the flow on to the old target.\n const insertableBlocks = useMemo<Array<IFlowAddableBlock>>(\n () =>\n addableBlocks.filter((block) => {\n const definition = registry.get(block.type);\n return definition ? flowOutputsOf(definition, definition.createDefaultConfig()).length > 0 : false;\n }),\n [addableBlocks, registry],\n );\n\n const nodes = useMemo<Array<TFlowBlockNode>>(\n () =>\n graph.nodes.map((node) => {\n const definition = registry.get(node.type);\n const nodeIssues = safeIssues.filter((issue) => issue.nodeId === node.nodeId);\n return {\n id: node.nodeId,\n type: 'flowBlock',\n position: node.position,\n measured: measuredSizes[node.nodeId],\n selected: selectedNodeIds.includes(node.nodeId),\n draggable: !readOnly,\n connectable: !readOnly,\n data: {\n icon: definition?.resolveIcon?.(node.config) ?? definition?.icon ?? 'help',\n title: node.label ?? definition?.resolveLabel?.(node.config) ?? definition?.label ?? node.type,\n categoryLabel: definition ? labels.categories[definition.category] : '',\n summary: definition\n ? definition.renderSummary(node.config)\n : labels.issueMessage({ code: 'unknownBlock', severity: 'error' }),\n hasInput: definition ? definition.category !== 'trigger' : true,\n outputs: flowOutputsOf(definition, node.config),\n connectedPorts: graph.edges.filter((edge) => edge.source === node.nodeId).map((edge) => edge.sourcePort),\n portLabels: labels.ports,\n addableBlocks,\n onAddFromPort: readOnly\n ? undefined\n : (port, type) => store.getState().addNodeFromPort(type, node.nodeId, port),\n disabled: node.disabled,\n issueSeverity: worstSeverity(nodeIssues),\n issueCount: nodeIssues.length,\n runStatus: runOverlay?.steps?.[node.nodeId],\n },\n };\n }),\n [\n graph.nodes,\n graph.edges,\n registry,\n safeIssues,\n selectedNodeIds,\n readOnly,\n labels,\n runOverlay,\n measuredSizes,\n addableBlocks,\n store,\n ],\n );\n\n const edges = useMemo<Array<TFlowConnectionEdge>>(() => {\n const traversed = new Set(runOverlay?.traversedEdgeIds ?? []);\n return graph.edges.map((edge) => ({\n id: edge.edgeId,\n type: 'flowConnection',\n source: edge.source,\n sourceHandle: edge.sourcePort,\n target: edge.target,\n targetHandle: FLOW_INPUT_HANDLE_ID,\n selected: selectedEdgeIds.includes(edge.edgeId),\n markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },\n data: {\n label: edge.sourcePort === 'next' ? undefined : labels.ports[edge.sourcePort],\n traversed: traversed.has(edge.edgeId),\n issueSeverity: worstSeverity(safeIssues.filter((issue) => issue.edgeId === edge.edgeId)),\n insertableBlocks,\n onInsert: readOnly ? undefined : (type: string) => store.getState().insertNodeOnEdge(type, edge.edgeId),\n },\n }));\n }, [graph.edges, selectedEdgeIds, runOverlay, labels, safeIssues, insertableBlocks, readOnly, store]);\n\n const handleNodesChange = useCallback(\n (changes: Array<NodeChange<TFlowBlockNode>>) => {\n const positions: Record<string, IFlowPosition> = {};\n const sizes: Record<string, { width: number; height: number }> = {};\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type === 'dimensions' && change.dimensions) sizes[change.id] = change.dimensions;\n if (change.type === 'position' && change.position && !readOnly) positions[change.id] = change.position;\n if (change.type === 'select') {\n const current: Array<string> = selection ?? store.getState().selectedNodeIds;\n selection = change.selected\n ? [...new Set([...current, change.id])]\n : current.filter((id) => id !== change.id);\n }\n }\n if (Object.keys(sizes).length > 0) setMeasuredSizes((current) => ({ ...current, ...sizes }));\n if (Object.keys(positions).length > 0) store.getState().moveNodes(positions);\n if (selection) store.getState().setSelection({ nodeIds: selection });\n },\n [store, readOnly],\n );\n\n const handleEdgesChange = useCallback(\n (changes: Array<EdgeChange<TFlowConnectionEdge>>) => {\n let selection: Array<string> | null = null;\n for (const change of changes) {\n if (change.type !== 'select') continue;\n const current: Array<string> = selection ?? store.getState().selectedEdgeIds;\n selection = change.selected ? [...new Set([...current, change.id])] : current.filter((id) => id !== change.id);\n }\n if (selection) store.getState().setSelection({ edgeIds: selection });\n },\n [store],\n );\n\n const handleConnect = useCallback(\n (connection: Connection) => {\n if (readOnly || !isPortId(connection.sourceHandle)) return;\n store.getState().connect(connection.source, connection.sourceHandle, connection.target);\n },\n [store, readOnly],\n );\n\n const isValidConnection = useCallback(\n (connection: Connection | TFlowConnectionEdge) =>\n isPortId(connection.sourceHandle) &&\n canConnect(store.getState().graph, registry, {\n source: connection.source,\n sourcePort: connection.sourceHandle,\n target: connection.target,\n }),\n [store, registry],\n );\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLDivElement>) => {\n if (readOnly || isTypingTarget(event.target)) return;\n const state = store.getState();\n const withModifier = event.metaKey || event.ctrlKey;\n const key = event.key.toLowerCase();\n\n if (event.key === 'Delete' || event.key === 'Backspace') state.removeSelected();\n else if (withModifier && key === 'z' && event.shiftKey) state.redo();\n else if (withModifier && key === 'z') state.undo();\n else if (withModifier && key === 'y') state.redo();\n else if (withModifier && key === 'c') state.copySelected();\n else if (withModifier && key === 'v') state.paste();\n else if (withModifier && key === 'd') state.duplicateSelected();\n else return;\n event.preventDefault();\n },\n [store, readOnly],\n );\n\n const handleLayout = useCallback(() => {\n const state = store.getState();\n state.recordHistory();\n state.moveNodes(layoutFlowByLevels(state.graph, registry));\n }, [store, registry]);\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.canvas')}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n className={mergeClasses(\n 'umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas focus:outline-none',\n className,\n )}\n >\n <ReactFlow<TFlowBlockNode, TFlowConnectionEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n onNodesChange={handleNodesChange}\n onEdgesChange={handleEdgesChange}\n onConnect={handleConnect}\n isValidConnection={isValidConnection}\n onNodeDragStart={() => {\n stopRevealing();\n store.getState().recordHistory();\n }}\n onMoveStart={(event) => {\n if (event) stopRevealing();\n }}\n onPaneClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n deleteKeyCode={null}\n nodesDraggable={!readOnly}\n nodesConnectable={!readOnly}\n snapToGrid\n snapGrid={SNAP_GRID}\n minZoom={0.25}\n maxZoom={2}\n fitView\n fitViewOptions={{ padding: 0.2, maxZoom: 1 }}\n proOptions={PRO_OPTIONS}\n >\n {background === 'dots' && <Background variant={BackgroundVariant.Dots} gap={16} size={1} />}\n {nodes.length >= MINIMAP_MIN_NODES && (\n <MiniMap pannable zoomable position=\"bottom-right\" style={MINIMAP_SIZE} />\n )}\n <FlowZoomControls\n onUndo={readOnly ? undefined : () => store.getState().undo()}\n onRedo={readOnly ? undefined : () => store.getState().redo()}\n canUndo={canUndo}\n canRedo={canRedo}\n onLayout={readOnly ? undefined : handleLayout}\n position={controlsPosition}\n />\n </ReactFlow>\n </div>\n );\n};\n\nexport default FlowCanvas;\n"],"names":["useReactFlowStore"],"mappings":"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"}
@@ -95,6 +95,7 @@ const FlowConnectionEdge = ({
95
95
  trigger: /* @__PURE__ */ jsx(
96
96
  ControlledButton,
97
97
  {
98
+ "aria-label": t("flow.edge.insertBlock"),
98
99
  defaultHtmlButton: true,
99
100
  className: "interactive flex size-6 items-center justify-center rounded-full border border-dashed border-brand-border bg-surface text-brand-emphasis shadow-sm hover:bg-brand-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
100
101
  children: /* @__PURE__ */ jsx(MaterialSymbol, { icon: "add", size: 16, "aria-hidden": true })
@@ -1 +1 @@
1
- {"version":3,"file":"FlowConnectionEdge.js","sources":["../../../../src/components/UI/Flow/FlowConnectionEdge.tsx"],"sourcesContent":["import { useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledMenu from '@/components/UI/ControlledMenu';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { BaseEdge, type Edge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport type { IFlowAddableBlock } from './FlowBlockNode';\nimport type { TFlowIssueSeverity } from './flowTypes';\n\nexport interface IFlowConnectionEdgeData extends Record<string, unknown> {\n label?: string;\n traversed?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n /** What can go in the middle of this edge; empty in a read-only canvas. */\n insertableBlocks?: Array<IFlowAddableBlock>;\n onInsert?: (type: string) => void;\n}\n\nexport type TFlowConnectionEdge = Edge<IFlowConnectionEdgeData, 'flowConnection'>;\n\n/** Wide enough to hover a 1px line without hunting for it. */\nconst HOVER_STROKE_WIDTH = 24;\n\n/**\n * A smooth-step connection. Hovering it (or selecting it) shows a \"+\" at its middle that puts a\n * block between the two it connects, so a step can be added without rewiring by hand.\n */\nconst FlowConnectionEdge: React.FC<EdgeProps<TFlowConnectionEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n markerEnd,\n selected,\n}) => {\n const { t } = useTranslation('ui');\n const [hovered, setHovered] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const [path, labelX, labelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n borderRadius: 12,\n });\n\n const insertableBlocks = Array.isArray(data?.insertableBlocks) ? data.insertableBlocks : [];\n const canInsert = Boolean(data?.onInsert) && insertableBlocks.length > 0;\n const showInsert = canInsert && (hovered || Boolean(selected) || menuOpen);\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n className={classNames('umode-flow-edge-path', {\n 'umode-flow-edge-path--selected': selected,\n 'umode-flow-edge-path--traversed': data?.traversed,\n 'umode-flow-edge-path--error': data?.issueSeverity === 'error',\n 'umode-flow-edge-path--warning': data?.issueSeverity === 'warning',\n })}\n />\n {canInsert && (\n <path\n d={path}\n fill=\"none\"\n stroke=\"transparent\"\n strokeWidth={HOVER_STROKE_WIDTH}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n />\n )}\n {(data?.label || canInsert) && (\n <EdgeLabelRenderer>\n <div\n className={classNames(\n 'nodrag nopan absolute flex items-center gap-x-1',\n canInsert ? 'pointer-events-auto' : 'pointer-events-none',\n )}\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {data?.label && (\n <OutlinedChip label={data.label} tone=\"neutral\" variant=\"outlined\" size=\"xs\" preserveLabelCase />\n )}\n {canInsert && (\n <div\n className={classNames(\n 'transition-opacity duration-fast focus-within:opacity-100',\n showInsert ? 'opacity-100' : 'opacity-0',\n )}\n >\n <ControlledMenu\n placement=\"bottom\"\n triggerTooltip={t('flow.edge.insertBlock')}\n onOpenChange={setMenuOpen}\n items={insertableBlocks.map((block) => ({\n key: block.type,\n label: block.label,\n icon: block.icon,\n onClick: () => data?.onInsert?.(block.type),\n }))}\n trigger={\n <ControlledButton\n defaultHtmlButton\n className=\"interactive flex size-6 items-center justify-center rounded-full border border-dashed border-brand-border bg-surface text-brand-emphasis shadow-sm hover:bg-brand-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"add\" size={16} aria-hidden />\n </ControlledButton>\n }\n />\n </div>\n )}\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nexport default FlowConnectionEdge;\n"],"names":[],"mappings":";;;;;;;;;AA0BA,MAAM,qBAAqB;AAM3B,MAAM,qBAA+D,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,MAAM,QAAQ,MAAM,IAAI,kBAAkB;AAAA,IAC/C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,EAAA,CACf;AAED,QAAM,mBAAmB,MAAM,QAAQ,MAAM,gBAAgB,IAAI,KAAK,mBAAmB,CAAA;AACzF,QAAM,YAAY,QAAQ,MAAM,QAAQ,KAAK,iBAAiB,SAAS;AACvE,QAAM,aAAa,cAAc,WAAW,QAAQ,QAAQ,KAAK;AAEjE,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,WAAW,wBAAwB;AAAA,UAC5C,kCAAkC;AAAA,UAClC,mCAAmC,MAAM;AAAA,UACzC,+BAA+B,MAAM,kBAAkB;AAAA,UACvD,iCAAiC,MAAM,kBAAkB;AAAA,QAAA,CAC1D;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,aACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,GAAG;AAAA,QACH,MAAK;AAAA,QACL,QAAO;AAAA,QACP,aAAa;AAAA,QACb,cAAc,MAAM,WAAW,IAAI;AAAA,QACnC,cAAc,MAAM,WAAW,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,KAGtC,MAAM,SAAS,cACf,oBAAC,mBAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,YAAY,wBAAwB;AAAA,QAAA;AAAA,QAEtC,OAAO,EAAE,WAAW,mCAAmC,MAAM,OAAO,MAAM,MAAA;AAAA,QAC1E,cAAc,MAAM,WAAW,IAAI;AAAA,QACnC,cAAc,MAAM,WAAW,KAAK;AAAA,QAEnC,UAAA;AAAA,UAAA,MAAM,SACL,oBAAC,cAAA,EAAa,OAAO,KAAK,OAAO,MAAK,WAAU,SAAQ,YAAW,MAAK,MAAK,mBAAiB,MAAC;AAAA,UAEhG,aACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,aAAa,gBAAgB;AAAA,cAAA;AAAA,cAG/B,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,WAAU;AAAA,kBACV,gBAAgB,EAAE,uBAAuB;AAAA,kBACzC,cAAc;AAAA,kBACd,OAAO,iBAAiB,IAAI,CAAC,WAAW;AAAA,oBACtC,KAAK,MAAM;AAAA,oBACX,OAAO,MAAM;AAAA,oBACb,MAAM,MAAM;AAAA,oBACZ,SAAS,MAAM,MAAM,WAAW,MAAM,IAAI;AAAA,kBAAA,EAC1C;AAAA,kBACF,SACE;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,mBAAiB;AAAA,sBACjB,WAAU;AAAA,sBAEV,8BAAC,gBAAA,EAAe,MAAK,OAAM,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACnD;AAAA,cAAA;AAAA,YAEJ;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA,EAEJ,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
1
+ {"version":3,"file":"FlowConnectionEdge.js","sources":["../../../../src/components/UI/Flow/FlowConnectionEdge.tsx"],"sourcesContent":["import { useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledMenu from '@/components/UI/ControlledMenu';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport { BaseEdge, type Edge, EdgeLabelRenderer, type EdgeProps, getSmoothStepPath } from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport type { IFlowAddableBlock } from './FlowBlockNode';\nimport type { TFlowIssueSeverity } from './flowTypes';\n\nexport interface IFlowConnectionEdgeData extends Record<string, unknown> {\n label?: string;\n traversed?: boolean;\n issueSeverity?: TFlowIssueSeverity;\n /** What can go in the middle of this edge; empty in a read-only canvas. */\n insertableBlocks?: Array<IFlowAddableBlock>;\n onInsert?: (type: string) => void;\n}\n\nexport type TFlowConnectionEdge = Edge<IFlowConnectionEdgeData, 'flowConnection'>;\n\n/** Wide enough to hover a 1px line without hunting for it. */\nconst HOVER_STROKE_WIDTH = 24;\n\n/**\n * A smooth-step connection. Hovering it (or selecting it) shows a \"+\" at its middle that puts a\n * block between the two it connects, so a step can be added without rewiring by hand.\n */\nconst FlowConnectionEdge: React.FC<EdgeProps<TFlowConnectionEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n data,\n markerEnd,\n selected,\n}) => {\n const { t } = useTranslation('ui');\n const [hovered, setHovered] = useState(false);\n const [menuOpen, setMenuOpen] = useState(false);\n const [path, labelX, labelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n borderRadius: 12,\n });\n\n const insertableBlocks = Array.isArray(data?.insertableBlocks) ? data.insertableBlocks : [];\n const canInsert = Boolean(data?.onInsert) && insertableBlocks.length > 0;\n const showInsert = canInsert && (hovered || Boolean(selected) || menuOpen);\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n className={classNames('umode-flow-edge-path', {\n 'umode-flow-edge-path--selected': selected,\n 'umode-flow-edge-path--traversed': data?.traversed,\n 'umode-flow-edge-path--error': data?.issueSeverity === 'error',\n 'umode-flow-edge-path--warning': data?.issueSeverity === 'warning',\n })}\n />\n {canInsert && (\n <path\n d={path}\n fill=\"none\"\n stroke=\"transparent\"\n strokeWidth={HOVER_STROKE_WIDTH}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n />\n )}\n {(data?.label || canInsert) && (\n <EdgeLabelRenderer>\n <div\n className={classNames(\n 'nodrag nopan absolute flex items-center gap-x-1',\n canInsert ? 'pointer-events-auto' : 'pointer-events-none',\n )}\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {data?.label && (\n <OutlinedChip label={data.label} tone=\"neutral\" variant=\"outlined\" size=\"xs\" preserveLabelCase />\n )}\n {canInsert && (\n <div\n className={classNames(\n 'transition-opacity duration-fast focus-within:opacity-100',\n showInsert ? 'opacity-100' : 'opacity-0',\n )}\n >\n <ControlledMenu\n placement=\"bottom\"\n triggerTooltip={t('flow.edge.insertBlock')}\n onOpenChange={setMenuOpen}\n items={insertableBlocks.map((block) => ({\n key: block.type,\n label: block.label,\n icon: block.icon,\n onClick: () => data?.onInsert?.(block.type),\n }))}\n trigger={\n <ControlledButton\n aria-label={t('flow.edge.insertBlock')}\n defaultHtmlButton\n className=\"interactive flex size-6 items-center justify-center rounded-full border border-dashed border-brand-border bg-surface text-brand-emphasis shadow-sm hover:bg-brand-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"add\" size={16} aria-hidden />\n </ControlledButton>\n }\n />\n </div>\n )}\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nexport default FlowConnectionEdge;\n"],"names":[],"mappings":";;;;;;;;;AA0BA,MAAM,qBAAqB;AAM3B,MAAM,qBAA+D,CAAC;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,CAAC,MAAM,QAAQ,MAAM,IAAI,kBAAkB;AAAA,IAC/C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,EAAA,CACf;AAED,QAAM,mBAAmB,MAAM,QAAQ,MAAM,gBAAgB,IAAI,KAAK,mBAAmB,CAAA;AACzF,QAAM,YAAY,QAAQ,MAAM,QAAQ,KAAK,iBAAiB,SAAS;AACvE,QAAM,aAAa,cAAc,WAAW,QAAQ,QAAQ,KAAK;AAEjE,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW,WAAW,wBAAwB;AAAA,UAC5C,kCAAkC;AAAA,UAClC,mCAAmC,MAAM;AAAA,UACzC,+BAA+B,MAAM,kBAAkB;AAAA,UACvD,iCAAiC,MAAM,kBAAkB;AAAA,QAAA,CAC1D;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,aACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,GAAG;AAAA,QACH,MAAK;AAAA,QACL,QAAO;AAAA,QACP,aAAa;AAAA,QACb,cAAc,MAAM,WAAW,IAAI;AAAA,QACnC,cAAc,MAAM,WAAW,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,KAGtC,MAAM,SAAS,cACf,oBAAC,mBAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,YAAY,wBAAwB;AAAA,QAAA;AAAA,QAEtC,OAAO,EAAE,WAAW,mCAAmC,MAAM,OAAO,MAAM,MAAA;AAAA,QAC1E,cAAc,MAAM,WAAW,IAAI;AAAA,QACnC,cAAc,MAAM,WAAW,KAAK;AAAA,QAEnC,UAAA;AAAA,UAAA,MAAM,SACL,oBAAC,cAAA,EAAa,OAAO,KAAK,OAAO,MAAK,WAAU,SAAQ,YAAW,MAAK,MAAK,mBAAiB,MAAC;AAAA,UAEhG,aACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,WAAW;AAAA,gBACT;AAAA,gBACA,aAAa,gBAAgB;AAAA,cAAA;AAAA,cAG/B,UAAA;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBACC,WAAU;AAAA,kBACV,gBAAgB,EAAE,uBAAuB;AAAA,kBACzC,cAAc;AAAA,kBACd,OAAO,iBAAiB,IAAI,CAAC,WAAW;AAAA,oBACtC,KAAK,MAAM;AAAA,oBACX,OAAO,MAAM;AAAA,oBACb,MAAM,MAAM;AAAA,oBACZ,SAAS,MAAM,MAAM,WAAW,MAAM,IAAI;AAAA,kBAAA,EAC1C;AAAA,kBACF,SACE;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,cAAY,EAAE,uBAAuB;AAAA,sBACrC,mBAAiB;AAAA,sBACjB,WAAU;AAAA,sBAEV,8BAAC,gBAAA,EAAe,MAAK,OAAM,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,oBAAA;AAAA,kBAAA;AAAA,gBACnD;AAAA,cAAA;AAAA,YAEJ;AAAA,UAAA;AAAA,QACF;AAAA,MAAA;AAAA,IAAA,EAEJ,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -1,11 +1,11 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
- import { twMerge } from "tailwind-merge";
4
3
  import ControlledButton from "../../Form/ControlledButton.js";
5
4
  import ControlledInput from "../../Form/ControlledInput.js";
6
5
  import ControlledTooltip from "../ControlledTooltip.js";
7
6
  import { MaterialSymbol } from "../MaterialSymbol/index.js";
8
7
  import NoDataFound from "../NoDataFound/index.js";
8
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
9
9
  import { useFlowBlockRegistry, useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
10
10
  import { isFlowNodeRemovable } from "./flowGraph.js";
11
11
  import { useFlowLabels } from "./useFlowLabels.js";
@@ -23,7 +23,7 @@ const FlowInspector = ({
23
23
  const selectedNode = useFlowEditor(
24
24
  (state) => state.selectedNodeIds.length === 1 ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0]) : void 0
25
25
  );
26
- const shellClass = twMerge(
26
+ const shellClass = mergeClasses(
27
27
  "flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4",
28
28
  className
29
29
  );
@@ -53,6 +53,7 @@ const FlowInspector = ({
53
53
  /* @__PURE__ */ jsx(ControlledTooltip, { content: t("flow.inspector.close"), placement: "left", children: /* @__PURE__ */ jsx(
54
54
  ControlledButton,
55
55
  {
56
+ "aria-label": t("flow.inspector.close"),
56
57
  defaultHtmlButton: true,
57
58
  onClick: () => store.getState().setSelection({ nodeIds: [], edgeIds: [] }),
58
59
  className: "interactive ml-auto flex size-8 shrink-0 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
@@ -1 +1 @@
1
- {"version":3,"file":"FlowInspector.js","sources":["../../../../src/components/UI/Flow/FlowInspector.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\n\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport { isFlowNodeRemovable } from './flowGraph';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowInspectorProps {\n issues?: Array<IFlowIssue>;\n readOnly?: boolean;\n /** Renders nothing while no block is selected, instead of the empty state. */\n hideWhenEmpty?: boolean;\n className?: string;\n}\n\n/** The side panel of the selected block: its name, its own settings panel and its issues. */\nconst FlowInspector: React.FC<IFlowInspectorProps> = ({\n issues = [],\n readOnly = false,\n hideWhenEmpty = false,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedNode = useFlowEditor((state) =>\n state.selectedNodeIds.length === 1\n ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0])\n : undefined,\n );\n\n const shellClass = twMerge(\n 'flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4',\n className,\n );\n\n if (!selectedNode || selectedNodeIds.length !== 1) {\n if (hideWhenEmpty) return null;\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <NoDataFound size=\"sm\" variant=\"waiting\" message={t('flow.inspector.empty')} />\n </aside>\n );\n }\n\n const definition = registry.get(selectedNode.type);\n const nodeIssues = (Array.isArray(issues) ? issues : []).filter((issue) => issue.nodeId === selectedNode.nodeId);\n const ConfigPanel = definition?.ConfigPanel;\n const canDuplicate = definition?.category !== 'trigger';\n const canRemove = isFlowNodeRemovable(selectedNode, registry);\n\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <div className=\"flex items-center gap-x-2\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground-muted\">\n <MaterialSymbol\n icon={definition?.resolveIcon?.(selectedNode.config) ?? definition?.icon ?? 'help'}\n size={18}\n aria-hidden\n />\n </span>\n <div className=\"flex min-w-0 flex-col\">\n {definition && (\n <span className=\"text-xxs font-semibold uppercase tracking-wide text-foreground-subtle\">\n {labels.categories[definition.category]}\n </span>\n )}\n <span className=\"truncate text-sm font-semibold text-foreground\">\n {definition?.resolveLabel?.(selectedNode.config) ?? definition?.label ?? selectedNode.type}\n </span>\n </div>\n <ControlledTooltip content={t('flow.inspector.close')} placement=\"left\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n className=\"interactive ml-auto flex size-8 shrink-0 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"close\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n <ControlledInput\n label={t('flow.inspector.label')}\n value={selectedNode.label ?? ''}\n disabled={readOnly}\n onChange={(event) => store.getState().updateNodeLabel(selectedNode.nodeId, event.target.value)}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col gap-y-3 overflow-y-auto\">\n {ConfigPanel ? (\n <ConfigPanel\n config={selectedNode.config}\n readOnly={readOnly}\n onChange={(next) => store.getState().updateNodeConfig(selectedNode.nodeId, next)}\n />\n ) : (\n <NoDataFound compact variant=\"error\" message={t('flow.inspector.unknownBlock')} />\n )}\n\n {nodeIssues.length > 0 && (\n <ul className=\"flex flex-col gap-y-1\">\n {nodeIssues.map((issue) => (\n <li key={`${issue.code}|${issue.edgeId ?? ''}`} className=\"flex items-start gap-x-2\">\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {!readOnly && (canDuplicate || canRemove) && (\n <div className=\"flex gap-x-2\">\n {canDuplicate && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"neutral\"\n className=\"normal-case\"\n onClick={() => store.getState().duplicateSelected()}\n >\n <MaterialSymbol icon=\"content_copy\" size={16} aria-hidden />\n {t('flow.inspector.duplicate')}\n </ControlledButton>\n )}\n {canRemove && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"danger\"\n className=\"normal-case\"\n onClick={() => store.getState().removeSelected()}\n >\n <MaterialSymbol icon=\"delete\" size={16} aria-hidden />\n {t('flow.inspector.delete')}\n </ControlledButton>\n )}\n </div>\n )}\n </aside>\n );\n};\n\nexport default FlowInspector;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FlowInspector.js","sources":["../../../../src/components/UI/Flow/FlowInspector.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledInput from '@/components/Form/ControlledInput';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport { mergeClasses } from '@/helpers/mergeClasses';\n\nimport { useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport { isFlowNodeRemovable } from './flowGraph';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowInspectorProps {\n issues?: Array<IFlowIssue>;\n readOnly?: boolean;\n /** Renders nothing while no block is selected, instead of the empty state. */\n hideWhenEmpty?: boolean;\n className?: string;\n}\n\n/** The side panel of the selected block: its name, its own settings panel and its issues. */\nconst FlowInspector: React.FC<IFlowInspectorProps> = ({\n issues = [],\n readOnly = false,\n hideWhenEmpty = false,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const registry = useFlowBlockRegistry();\n const store = useFlowEditorStore();\n const selectedNodeIds = useFlowEditor((state) => state.selectedNodeIds);\n const selectedNode = useFlowEditor((state) =>\n state.selectedNodeIds.length === 1\n ? state.graph.nodes.find((node) => node.nodeId === state.selectedNodeIds[0])\n : undefined,\n );\n\n const shellClass = mergeClasses(\n 'flex h-full min-h-0 flex-col gap-y-3 rounded-xl border border-border bg-surface p-4',\n className,\n );\n\n if (!selectedNode || selectedNodeIds.length !== 1) {\n if (hideWhenEmpty) return null;\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <NoDataFound size=\"sm\" variant=\"waiting\" message={t('flow.inspector.empty')} />\n </aside>\n );\n }\n\n const definition = registry.get(selectedNode.type);\n const nodeIssues = (Array.isArray(issues) ? issues : []).filter((issue) => issue.nodeId === selectedNode.nodeId);\n const ConfigPanel = definition?.ConfigPanel;\n const canDuplicate = definition?.category !== 'trigger';\n const canRemove = isFlowNodeRemovable(selectedNode, registry);\n\n return (\n <aside className={shellClass} aria-label={t('flow.inspector.title')}>\n <div className=\"flex items-center gap-x-2\">\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground-muted\">\n <MaterialSymbol\n icon={definition?.resolveIcon?.(selectedNode.config) ?? definition?.icon ?? 'help'}\n size={18}\n aria-hidden\n />\n </span>\n <div className=\"flex min-w-0 flex-col\">\n {definition && (\n <span className=\"text-xxs font-semibold uppercase tracking-wide text-foreground-subtle\">\n {labels.categories[definition.category]}\n </span>\n )}\n <span className=\"truncate text-sm font-semibold text-foreground\">\n {definition?.resolveLabel?.(selectedNode.config) ?? definition?.label ?? selectedNode.type}\n </span>\n </div>\n <ControlledTooltip content={t('flow.inspector.close')} placement=\"left\">\n <ControlledButton\n aria-label={t('flow.inspector.close')}\n defaultHtmlButton\n onClick={() => store.getState().setSelection({ nodeIds: [], edgeIds: [] })}\n className=\"interactive ml-auto flex size-8 shrink-0 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <MaterialSymbol icon=\"close\" size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n\n <ControlledInput\n label={t('flow.inspector.label')}\n value={selectedNode.label ?? ''}\n disabled={readOnly}\n onChange={(event) => store.getState().updateNodeLabel(selectedNode.nodeId, event.target.value)}\n />\n\n <div className=\"flex min-h-0 flex-1 flex-col gap-y-3 overflow-y-auto\">\n {ConfigPanel ? (\n <ConfigPanel\n config={selectedNode.config}\n readOnly={readOnly}\n onChange={(next) => store.getState().updateNodeConfig(selectedNode.nodeId, next)}\n />\n ) : (\n <NoDataFound compact variant=\"error\" message={t('flow.inspector.unknownBlock')} />\n )}\n\n {nodeIssues.length > 0 && (\n <ul className=\"flex flex-col gap-y-1\">\n {nodeIssues.map((issue) => (\n <li key={`${issue.code}|${issue.edgeId ?? ''}`} className=\"flex items-start gap-x-2\">\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {!readOnly && (canDuplicate || canRemove) && (\n <div className=\"flex gap-x-2\">\n {canDuplicate && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"neutral\"\n className=\"normal-case\"\n onClick={() => store.getState().duplicateSelected()}\n >\n <MaterialSymbol icon=\"content_copy\" size={16} aria-hidden />\n {t('flow.inspector.duplicate')}\n </ControlledButton>\n )}\n {canRemove && (\n <ControlledButton\n size=\"sm\"\n variant=\"outlined\"\n tone=\"danger\"\n className=\"normal-case\"\n onClick={() => store.getState().removeSelected()}\n >\n <MaterialSymbol icon=\"delete\" size={16} aria-hidden />\n {t('flow.inspector.delete')}\n </ControlledButton>\n )}\n </div>\n )}\n </aside>\n );\n};\n\nexport default FlowInspector;\n"],"names":[],"mappings":";;;;;;;;;;;AAuBA,MAAM,gBAA+C,CAAC;AAAA,EACpD,SAAS,CAAA;AAAA,EACT,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,SAAS,cAAA;AACf,QAAM,WAAW,qBAAA;AACjB,QAAM,QAAQ,mBAAA;AACd,QAAM,kBAAkB,cAAc,CAAC,UAAU,MAAM,eAAe;AACtE,QAAM,eAAe;AAAA,IAAc,CAAC,UAClC,MAAM,gBAAgB,WAAW,IAC7B,MAAM,MAAM,MAAM,KAAK,CAAC,SAAS,KAAK,WAAW,MAAM,gBAAgB,CAAC,CAAC,IACzE;AAAA,EAAA;AAGN,QAAM,aAAa;AAAA,IACjB;AAAA,IACA;AAAA,EAAA;AAGF,MAAI,CAAC,gBAAgB,gBAAgB,WAAW,GAAG;AACjD,QAAI,cAAe,QAAO;AAC1B,+BACG,SAAA,EAAM,WAAW,YAAY,cAAY,EAAE,sBAAsB,GAChE,UAAA,oBAAC,aAAA,EAAY,MAAK,MAAK,SAAQ,WAAU,SAAS,EAAE,sBAAsB,GAAG,GAC/E;AAAA,EAEJ;AAEA,QAAM,aAAa,SAAS,IAAI,aAAa,IAAI;AACjD,QAAM,cAAc,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAA,GAAI,OAAO,CAAC,UAAU,MAAM,WAAW,aAAa,MAAM;AAC/G,QAAM,cAAc,YAAY;AAChC,QAAM,eAAe,YAAY,aAAa;AAC9C,QAAM,YAAY,oBAAoB,cAAc,QAAQ;AAE5D,8BACG,SAAA,EAAM,WAAW,YAAY,cAAY,EAAE,sBAAsB,GAChE,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,6BACb,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAK,WAAU,8FACd,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAM,YAAY,cAAc,aAAa,MAAM,KAAK,YAAY,QAAQ;AAAA,UAC5E,MAAM;AAAA,UACN,eAAW;AAAA,QAAA;AAAA,MAAA,GAEf;AAAA,MACA,qBAAC,OAAA,EAAI,WAAU,yBACZ,UAAA;AAAA,QAAA,cACC,oBAAC,UAAK,WAAU,yEACb,iBAAO,WAAW,WAAW,QAAQ,EAAA,CACxC;AAAA,QAEF,oBAAC,QAAA,EAAK,WAAU,kDACb,UAAA,YAAY,eAAe,aAAa,MAAM,KAAK,YAAY,SAAS,aAAa,KAAA,CACxF;AAAA,MAAA,GACF;AAAA,0BACC,mBAAA,EAAkB,SAAS,EAAE,sBAAsB,GAAG,WAAU,QAC/D,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,cAAY,EAAE,sBAAsB;AAAA,UACpC,mBAAiB;AAAA,UACjB,SAAS,MAAM,MAAM,WAAW,aAAa,EAAE,SAAS,CAAA,GAAI,SAAS,CAAA,GAAI;AAAA,UACzE,WAAU;AAAA,UAEV,8BAAC,gBAAA,EAAe,MAAK,SAAQ,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,QAAA;AAAA,MAAA,EACrD,CACF;AAAA,IAAA,GACF;AAAA,IAEA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO,EAAE,sBAAsB;AAAA,QAC/B,OAAO,aAAa,SAAS;AAAA,QAC7B,UAAU;AAAA,QACV,UAAU,CAAC,UAAU,MAAM,SAAA,EAAW,gBAAgB,aAAa,QAAQ,MAAM,OAAO,KAAK;AAAA,MAAA;AAAA,IAAA;AAAA,IAG/F,qBAAC,OAAA,EAAI,WAAU,wDACZ,UAAA;AAAA,MAAA,cACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,QAAQ,aAAa;AAAA,UACrB;AAAA,UACA,UAAU,CAAC,SAAS,MAAM,WAAW,iBAAiB,aAAa,QAAQ,IAAI;AAAA,QAAA;AAAA,MAAA,IAGjF,oBAAC,aAAA,EAAY,SAAO,MAAC,SAAQ,SAAQ,SAAS,EAAE,6BAA6B,EAAA,CAAG;AAAA,MAGjF,WAAW,SAAS,KACnB,oBAAC,QAAG,WAAU,yBACX,UAAA,WAAW,IAAI,CAAC,UACf,qBAAC,MAAA,EAA+C,WAAU,4BACxD,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAM,MAAM,aAAa,UAAU,UAAU;AAAA,YAC7C,MAAM;AAAA,YACN,WAAW,MAAM,aAAa,UAAU,yBAAyB;AAAA,YACjE,eAAW;AAAA,UAAA;AAAA,QAAA;AAAA,4BAEZ,QAAA,EAAK,WAAU,iCAAiC,UAAA,OAAO,aAAa,KAAK,EAAA,CAAE;AAAA,MAAA,KAPrE,GAAG,MAAM,IAAI,IAAI,MAAM,UAAU,EAAE,EAQ5C,CACD,EAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAEC,CAAC,aAAa,gBAAgB,cAC7B,qBAAC,OAAA,EAAI,WAAU,gBACZ,UAAA;AAAA,MAAA,gBACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM,MAAM,SAAA,EAAW,kBAAA;AAAA,UAEhC,UAAA;AAAA,YAAA,oBAAC,kBAAe,MAAK,gBAAe,MAAM,IAAI,eAAW,MAAC;AAAA,YACzD,EAAE,0BAA0B;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGhC,aACC;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,WAAU;AAAA,UACV,SAAS,MAAM,MAAM,SAAA,EAAW,eAAA;AAAA,UAEhC,UAAA;AAAA,YAAA,oBAAC,kBAAe,MAAK,UAAS,MAAM,IAAI,eAAW,MAAC;AAAA,YACnD,EAAE,uBAAuB;AAAA,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAC5B,EAAA,CAEJ;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -1,9 +1,9 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useTranslation } from "react-i18next";
3
- import { twMerge } from "tailwind-merge";
4
3
  import ControlledButton from "../../Form/ControlledButton.js";
5
4
  import { MaterialSymbol } from "../MaterialSymbol/index.js";
6
5
  import NoDataFound from "../NoDataFound/index.js";
6
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
7
7
  import { useReactFlow } from "@xyflow/react";
8
8
  import { useFlowEditorStore, useFlowEditor } from "./FlowEditorProvider.js";
9
9
  import { useFlowLabels } from "./useFlowLabels.js";
@@ -24,7 +24,7 @@ const FlowIssueList = ({ issues, className }) => {
24
24
  store.getState().setSelection({ nodeIds: [node.nodeId], edgeIds: [] });
25
25
  void setCenter(node.position.x + 128, node.position.y + 48, { zoom: 1, duration: 200 });
26
26
  };
27
- return /* @__PURE__ */ jsxs("section", { className: twMerge("flex flex-col gap-y-2", className), children: [
27
+ return /* @__PURE__ */ jsxs("section", { className: mergeClasses("flex flex-col gap-y-2", className), children: [
28
28
  /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: t("flow.issues.title") }),
29
29
  uniqueIssues.length === 0 ? /* @__PURE__ */ jsx(NoDataFound, { compact: true, variant: "success", message: t("flow.issues.none") }) : /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-y-1", children: uniqueIssues.map((issue) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
30
30
  ControlledButton,
@@ -1 +1 @@
1
- {"version":3,"file":"FlowIssueList.js","sources":["../../../../src/components/UI/Flow/FlowIssueList.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport { useReactFlow } from '@xyflow/react';\n\nimport { useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowIssueListProps {\n issues: Array<IFlowIssue>;\n className?: string;\n}\n\nconst issueKey = (issue: IFlowIssue): string => `${issue.code}|${issue.nodeId ?? ''}|${issue.edgeId ?? ''}`;\n\n/** Every issue of the flow; clicking one selects and centers the block it points to. */\nconst FlowIssueList: React.FC<IFlowIssueListProps> = ({ issues, className }) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const store = useFlowEditorStore();\n const nodes = useFlowEditor((state) => state.graph.nodes);\n const { setCenter } = useReactFlow();\n const safeIssues = Array.isArray(issues) ? issues : [];\n const uniqueIssues = safeIssues.filter(\n (issue, index) => safeIssues.findIndex((other) => issueKey(other) === issueKey(issue)) === index,\n );\n\n const focusNode = (nodeId: string | undefined) => {\n const node = nodes.find((candidate) => candidate.nodeId === nodeId);\n if (!node) return;\n store.getState().setSelection({ nodeIds: [node.nodeId], edgeIds: [] });\n void setCenter(node.position.x + 128, node.position.y + 48, { zoom: 1, duration: 200 });\n };\n\n return (\n <section className={twMerge('flex flex-col gap-y-2', className)}>\n <span className=\"text-sm font-semibold text-foreground\">{t('flow.issues.title')}</span>\n {uniqueIssues.length === 0 ? (\n <NoDataFound compact variant=\"success\" message={t('flow.issues.none')} />\n ) : (\n <ul className=\"flex flex-col gap-y-1\">\n {uniqueIssues.map((issue) => (\n <li key={issueKey(issue)}>\n <ControlledButton\n defaultHtmlButton\n disabled={!issue.nodeId}\n onClick={() => focusNode(issue.nodeId)}\n className=\"interactive flex w-full items-start gap-x-2 rounded-lg px-2 py-1.5 text-left hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:hover:bg-transparent\"\n >\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </ControlledButton>\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n};\n\nexport default FlowIssueList;\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,WAAW,CAAC,UAA8B,GAAG,MAAM,IAAI,IAAI,MAAM,UAAU,EAAE,IAAI,MAAM,UAAU,EAAE;AAGzG,MAAM,gBAA+C,CAAC,EAAE,QAAQ,gBAAgB;AAC9E,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,SAAS,cAAA;AACf,QAAM,QAAQ,mBAAA;AACd,QAAM,QAAQ,cAAc,CAAC,UAAU,MAAM,MAAM,KAAK;AACxD,QAAM,EAAE,UAAA,IAAc,aAAA;AACtB,QAAM,aAAa,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAA;AACpD,QAAM,eAAe,WAAW;AAAA,IAC9B,CAAC,OAAO,UAAU,WAAW,UAAU,CAAC,UAAU,SAAS,KAAK,MAAM,SAAS,KAAK,CAAC,MAAM;AAAA,EAAA;AAG7F,QAAM,YAAY,CAAC,WAA+B;AAChD,UAAM,OAAO,MAAM,KAAK,CAAC,cAAc,UAAU,WAAW,MAAM;AAClE,QAAI,CAAC,KAAM;AACX,UAAM,SAAA,EAAW,aAAa,EAAE,SAAS,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA,GAAI;AACrE,SAAK,UAAU,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK;AAAA,EACxF;AAEA,8BACG,WAAA,EAAQ,WAAW,QAAQ,yBAAyB,SAAS,GAC5D,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,EAAE,mBAAmB,GAAE;AAAA,IAC/E,aAAa,WAAW,IACvB,oBAAC,eAAY,SAAO,MAAC,SAAQ,WAAU,SAAS,EAAE,kBAAkB,EAAA,CAAG,IAEvE,oBAAC,MAAA,EAAG,WAAU,yBACX,uBAAa,IAAI,CAAC,UACjB,oBAAC,MAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,mBAAiB;AAAA,QACjB,UAAU,CAAC,MAAM;AAAA,QACjB,SAAS,MAAM,UAAU,MAAM,MAAM;AAAA,QACrC,WAAU;AAAA,QAEV,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,MAAM,aAAa,UAAU,UAAU;AAAA,cAC7C,MAAM;AAAA,cACN,WAAW,MAAM,aAAa,UAAU,yBAAyB;AAAA,cACjE,eAAW;AAAA,YAAA;AAAA,UAAA;AAAA,8BAEZ,QAAA,EAAK,WAAU,iCAAiC,UAAA,OAAO,aAAa,KAAK,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,EAC9E,GAdO,SAAS,KAAK,CAevB,CACD,EAAA,CACH;AAAA,EAAA,GAEJ;AAEJ;"}
1
+ {"version":3,"file":"FlowIssueList.js","sources":["../../../../src/components/UI/Flow/FlowIssueList.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { MaterialSymbol } from '@/components/UI/MaterialSymbol';\nimport NoDataFound from '@/components/UI/NoDataFound';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { useReactFlow } from '@xyflow/react';\n\nimport { useFlowEditor, useFlowEditorStore } from './FlowEditorProvider';\nimport type { IFlowIssue } from './flowTypes';\nimport { useFlowLabels } from './useFlowLabels';\n\ninterface IFlowIssueListProps {\n issues: Array<IFlowIssue>;\n className?: string;\n}\n\nconst issueKey = (issue: IFlowIssue): string => `${issue.code}|${issue.nodeId ?? ''}|${issue.edgeId ?? ''}`;\n\n/** Every issue of the flow; clicking one selects and centers the block it points to. */\nconst FlowIssueList: React.FC<IFlowIssueListProps> = ({ issues, className }) => {\n const { t } = useTranslation('ui');\n const labels = useFlowLabels();\n const store = useFlowEditorStore();\n const nodes = useFlowEditor((state) => state.graph.nodes);\n const { setCenter } = useReactFlow();\n const safeIssues = Array.isArray(issues) ? issues : [];\n const uniqueIssues = safeIssues.filter(\n (issue, index) => safeIssues.findIndex((other) => issueKey(other) === issueKey(issue)) === index,\n );\n\n const focusNode = (nodeId: string | undefined) => {\n const node = nodes.find((candidate) => candidate.nodeId === nodeId);\n if (!node) return;\n store.getState().setSelection({ nodeIds: [node.nodeId], edgeIds: [] });\n void setCenter(node.position.x + 128, node.position.y + 48, { zoom: 1, duration: 200 });\n };\n\n return (\n <section className={mergeClasses('flex flex-col gap-y-2', className)}>\n <span className=\"text-sm font-semibold text-foreground\">{t('flow.issues.title')}</span>\n {uniqueIssues.length === 0 ? (\n <NoDataFound compact variant=\"success\" message={t('flow.issues.none')} />\n ) : (\n <ul className=\"flex flex-col gap-y-1\">\n {uniqueIssues.map((issue) => (\n <li key={issueKey(issue)}>\n <ControlledButton\n defaultHtmlButton\n disabled={!issue.nodeId}\n onClick={() => focusNode(issue.nodeId)}\n className=\"interactive flex w-full items-start gap-x-2 rounded-lg px-2 py-1.5 text-left hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:hover:bg-transparent\"\n >\n <MaterialSymbol\n icon={issue.severity === 'error' ? 'error' : 'warning'}\n size={16}\n className={issue.severity === 'error' ? 'text-danger-emphasis' : 'text-warning-emphasis'}\n aria-hidden\n />\n <span className=\"text-xs text-foreground-muted\">{labels.issueMessage(issue)}</span>\n </ControlledButton>\n </li>\n ))}\n </ul>\n )}\n </section>\n );\n};\n\nexport default FlowIssueList;\n"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,WAAW,CAAC,UAA8B,GAAG,MAAM,IAAI,IAAI,MAAM,UAAU,EAAE,IAAI,MAAM,UAAU,EAAE;AAGzG,MAAM,gBAA+C,CAAC,EAAE,QAAQ,gBAAgB;AAC9E,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,SAAS,cAAA;AACf,QAAM,QAAQ,mBAAA;AACd,QAAM,QAAQ,cAAc,CAAC,UAAU,MAAM,MAAM,KAAK;AACxD,QAAM,EAAE,UAAA,IAAc,aAAA;AACtB,QAAM,aAAa,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAA;AACpD,QAAM,eAAe,WAAW;AAAA,IAC9B,CAAC,OAAO,UAAU,WAAW,UAAU,CAAC,UAAU,SAAS,KAAK,MAAM,SAAS,KAAK,CAAC,MAAM;AAAA,EAAA;AAG7F,QAAM,YAAY,CAAC,WAA+B;AAChD,UAAM,OAAO,MAAM,KAAK,CAAC,cAAc,UAAU,WAAW,MAAM;AAClE,QAAI,CAAC,KAAM;AACX,UAAM,SAAA,EAAW,aAAa,EAAE,SAAS,CAAC,KAAK,MAAM,GAAG,SAAS,CAAA,GAAI;AACrE,SAAK,UAAU,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,EAAE,MAAM,GAAG,UAAU,KAAK;AAAA,EACxF;AAEA,8BACG,WAAA,EAAQ,WAAW,aAAa,yBAAyB,SAAS,GACjE,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,yCAAyC,UAAA,EAAE,mBAAmB,GAAE;AAAA,IAC/E,aAAa,WAAW,IACvB,oBAAC,eAAY,SAAO,MAAC,SAAQ,WAAU,SAAS,EAAE,kBAAkB,EAAA,CAAG,IAEvE,oBAAC,MAAA,EAAG,WAAU,yBACX,uBAAa,IAAI,CAAC,UACjB,oBAAC,MAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,mBAAiB;AAAA,QACjB,UAAU,CAAC,MAAM;AAAA,QACjB,SAAS,MAAM,UAAU,MAAM,MAAM;AAAA,QACrC,WAAU;AAAA,QAEV,UAAA;AAAA,UAAA;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAM,MAAM,aAAa,UAAU,UAAU;AAAA,cAC7C,MAAM;AAAA,cACN,WAAW,MAAM,aAAa,UAAU,yBAAyB;AAAA,cACjE,eAAW;AAAA,YAAA;AAAA,UAAA;AAAA,8BAEZ,QAAA,EAAK,WAAU,iCAAiC,UAAA,OAAO,aAAa,KAAK,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,EAC9E,GAdO,SAAS,KAAK,CAevB,CACD,EAAA,CACH;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -2,8 +2,8 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
3
  import classNames from "classnames";
4
4
  import { useTranslation } from "react-i18next";
5
- import { twMerge } from "tailwind-merge";
6
5
  import ControlledButton from "../../Form/ControlledButton.js";
6
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
7
7
  import { ReactFlowProvider, MarkerType, ReactFlow, Background, BackgroundVariant, BaseEdge, EdgeLabelRenderer, Handle, Position } from "@xyflow/react";
8
8
  import OutlinedChip from "../Chips/OutlinedChip.js";
9
9
  import FlowZoomControls from "./FlowZoomControls.js";
@@ -173,7 +173,7 @@ const FlowLaneMapContent = ({
173
173
  {
174
174
  role: "region",
175
175
  "aria-label": ariaLabel ?? t("flow.laneMap.label"),
176
- className: twMerge("umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas", className),
176
+ className: mergeClasses("umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas", className),
177
177
  children: /* @__PURE__ */ jsxs(
178
178
  ReactFlow,
179
179
  {
@@ -1 +1 @@
1
- {"version":3,"file":"FlowLaneMap.js","sources":["../../../../src/components/UI/Flow/FlowLaneMap.tsx"],"sourcesContent":["import { ReactNode, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport {\n Background,\n BackgroundVariant,\n BaseEdge,\n type Edge,\n EdgeLabelRenderer,\n type EdgeProps,\n Handle,\n MarkerType,\n type Node,\n type NodeProps,\n Position,\n type ProOptions,\n ReactFlow,\n ReactFlowProvider,\n} from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport FlowZoomControls from './FlowZoomControls';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nexport interface IFlowLane {\n laneId: string;\n title: string;\n subtitle?: string;\n /** Counters or chips under the title (rules acting on entry, stage requirements). */\n badges?: ReactNode;\n}\n\nexport type TFlowArcTone = 'default' | 'warning' | 'danger';\n\nexport interface IFlowArc {\n arcId: string;\n sourceLaneId: string;\n targetLaneId: string;\n label: string;\n /** `over` arcs above the lanes, `under` below them; the host decides what each side means. */\n side: 'over' | 'under';\n tone?: TFlowArcTone;\n dashed?: boolean;\n}\n\ninterface IFlowLaneMapProps {\n lanes: Array<IFlowLane>;\n arcs: Array<IFlowArc>;\n onArcClick?: (arcId: string) => void;\n onLaneClick?: (laneId: string) => void;\n selectedArcId?: string | null;\n ariaLabel?: string;\n className?: string;\n}\n\nconst LANE_WIDTH = 192;\nconst LANE_HEIGHT = 168;\nconst LANE_GAP = 80;\n\ninterface ILaneNodeData extends Record<string, unknown> {\n lane: IFlowLane;\n onLaneClick?: (laneId: string) => void;\n}\n\ninterface IArcEdgeData extends Record<string, unknown> {\n arc: IFlowArc;\n}\n\ntype TLaneNode = Node<ILaneNodeData, 'flowLane'>;\ntype TArcEdge = Edge<IArcEdgeData, 'flowArc'>;\n\nconst HIDDEN_HANDLE_CLASS = '!size-px !min-h-0 !min-w-0 !border-0 !bg-transparent';\n\nconst LaneNode: React.FC<NodeProps<TLaneNode>> = ({ data }) => {\n const { lane, onLaneClick } = data;\n const content = (\n <>\n <span className=\"truncate text-sm font-semibold text-foreground\">{lane.title}</span>\n {lane.subtitle && <span className=\"truncate text-xs text-foreground-subtle\">{lane.subtitle}</span>}\n {lane.badges && <div className=\"mt-auto flex flex-wrap gap-1\">{lane.badges}</div>}\n </>\n );\n\n return (\n <div style={{ width: LANE_WIDTH, height: LANE_HEIGHT }} className=\"relative\">\n <Handle\n id=\"over-out\"\n type=\"source\"\n position={Position.Top}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n <Handle\n id=\"over-in\"\n type=\"target\"\n position={Position.Top}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n {onLaneClick ? (\n <ControlledButton\n defaultHtmlButton\n onClick={() => onLaneClick(lane.laneId)}\n className=\"interactive flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 text-left shadow-sm hover:border-border-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {content}\n </ControlledButton>\n ) : (\n <div className=\"flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 shadow-sm\">\n {content}\n </div>\n )}\n <Handle\n id=\"under-out\"\n type=\"source\"\n position={Position.Bottom}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n <Handle\n id=\"under-in\"\n type=\"target\"\n position={Position.Bottom}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n </div>\n );\n};\n\n/** A curve between two lane edges whose height grows with the distance, so long arcs clear the short ones. */\nconst ArcEdge: React.FC<EdgeProps<TArcEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n data,\n markerEnd,\n selected,\n}) => {\n const arc = data?.arc;\n const isOver = arc?.side !== 'under';\n const isBackward = targetX < sourceX;\n const lift = 40 + Math.abs(targetX - sourceX) * 0.2 + (isBackward ? 32 : 0);\n const controlY = isOver ? Math.min(sourceY, targetY) - lift * 1.3 : Math.max(sourceY, targetY) + lift * 1.3;\n const path = `M ${sourceX},${sourceY} C ${sourceX},${controlY} ${targetX},${controlY} ${targetX},${targetY}`;\n const labelX = (sourceX + targetX) / 2;\n const labelY = 0.125 * (sourceY + targetY) + 0.75 * controlY;\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n interactionWidth={16}\n className={classNames('umode-flow-arc', {\n 'umode-flow-arc--warning': arc?.tone === 'warning',\n 'umode-flow-arc--danger': arc?.tone === 'danger',\n 'umode-flow-arc--dashed': arc?.dashed,\n 'umode-flow-arc--selected': selected,\n })}\n />\n {arc?.label && (\n <EdgeLabelRenderer>\n <div\n className=\"nodrag nopan pointer-events-none absolute whitespace-nowrap\"\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n >\n <OutlinedChip\n label={arc.label}\n tone={arc.tone === 'warning' ? 'warning' : arc.tone === 'danger' ? 'danger' : 'neutral'}\n variant=\"outlined\"\n size=\"xs\"\n preserveLabelCase\n />\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nconst PRO_OPTIONS: ProOptions = { hideAttribution: true };\nconst NODE_TYPES = { flowLane: LaneNode };\nconst EDGE_TYPES = { flowArc: ArcEdge };\n\n/**\n * A read-only map of fixed columns joined by arcs: the shape of a pipeline seen from\n * above. Lanes keep the order they are given, so no graph layout is needed.\n */\nconst FlowLaneMapContent: React.FC<IFlowLaneMapProps> = ({\n lanes,\n arcs,\n onArcClick,\n onLaneClick,\n selectedArcId,\n ariaLabel,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const safeLanes = useMemo(() => (Array.isArray(lanes) ? lanes : []), [lanes]);\n const safeArcs = useMemo(() => (Array.isArray(arcs) ? arcs : []), [arcs]);\n\n const nodes = useMemo<Array<TLaneNode>>(\n () =>\n safeLanes.map((lane, index) => ({\n id: lane.laneId,\n type: 'flowLane',\n position: { x: index * (LANE_WIDTH + LANE_GAP), y: 0 },\n draggable: false,\n selectable: false,\n data: { lane, onLaneClick },\n })),\n [safeLanes, onLaneClick],\n );\n\n const laneIds = useMemo(() => new Set(safeLanes.map((lane) => lane.laneId)), [safeLanes]);\n\n const edges = useMemo<Array<TArcEdge>>(\n () =>\n safeArcs\n .filter(\n (arc) =>\n laneIds.has(arc.sourceLaneId) && laneIds.has(arc.targetLaneId) && arc.sourceLaneId !== arc.targetLaneId,\n )\n .map((arc) => ({\n id: arc.arcId,\n type: 'flowArc',\n source: arc.sourceLaneId,\n sourceHandle: `${arc.side}-out`,\n target: arc.targetLaneId,\n targetHandle: `${arc.side}-in`,\n selected: arc.arcId === selectedArcId,\n markerEnd: { type: MarkerType.ArrowClosed, width: 14, height: 14 },\n data: { arc },\n })),\n [safeArcs, laneIds, selectedArcId],\n );\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.laneMap.label')}\n className={twMerge('umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas', className)}\n >\n <ReactFlow<TLaneNode, TArcEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n nodesDraggable={false}\n nodesConnectable={false}\n deleteKeyCode={null}\n onEdgeClick={(_event, edge) => onArcClick?.(edge.id)}\n minZoom={0.25}\n maxZoom={1.5}\n fitView\n fitViewOptions={{ padding: 0.15, minZoom: 0.75 }}\n proOptions={PRO_OPTIONS}\n >\n <Background variant={BackgroundVariant.Dots} gap={16} size={1} />\n <FlowZoomControls />\n </ReactFlow>\n </div>\n );\n};\n\nconst FlowLaneMap: React.FC<IFlowLaneMapProps> = (props) => (\n <ReactFlowProvider>\n <FlowLaneMapContent {...props} />\n </ReactFlowProvider>\n);\n\nexport default FlowLaneMap;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"FlowLaneMap.js","sources":["../../../../src/components/UI/Flow/FlowLaneMap.tsx"],"sourcesContent":["import { ReactNode, useMemo } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport {\n Background,\n BackgroundVariant,\n BaseEdge,\n type Edge,\n EdgeLabelRenderer,\n type EdgeProps,\n Handle,\n MarkerType,\n type Node,\n type NodeProps,\n Position,\n type ProOptions,\n ReactFlow,\n ReactFlowProvider,\n} from '@xyflow/react';\n\nimport OutlinedChip from '../Chips/OutlinedChip';\n\nimport FlowZoomControls from './FlowZoomControls';\n\nimport '@xyflow/react/dist/base.css';\nimport '../../../styles/flow.css';\n\nexport interface IFlowLane {\n laneId: string;\n title: string;\n subtitle?: string;\n /** Counters or chips under the title (rules acting on entry, stage requirements). */\n badges?: ReactNode;\n}\n\nexport type TFlowArcTone = 'default' | 'warning' | 'danger';\n\nexport interface IFlowArc {\n arcId: string;\n sourceLaneId: string;\n targetLaneId: string;\n label: string;\n /** `over` arcs above the lanes, `under` below them; the host decides what each side means. */\n side: 'over' | 'under';\n tone?: TFlowArcTone;\n dashed?: boolean;\n}\n\ninterface IFlowLaneMapProps {\n lanes: Array<IFlowLane>;\n arcs: Array<IFlowArc>;\n onArcClick?: (arcId: string) => void;\n onLaneClick?: (laneId: string) => void;\n selectedArcId?: string | null;\n ariaLabel?: string;\n className?: string;\n}\n\nconst LANE_WIDTH = 192;\nconst LANE_HEIGHT = 168;\nconst LANE_GAP = 80;\n\ninterface ILaneNodeData extends Record<string, unknown> {\n lane: IFlowLane;\n onLaneClick?: (laneId: string) => void;\n}\n\ninterface IArcEdgeData extends Record<string, unknown> {\n arc: IFlowArc;\n}\n\ntype TLaneNode = Node<ILaneNodeData, 'flowLane'>;\ntype TArcEdge = Edge<IArcEdgeData, 'flowArc'>;\n\nconst HIDDEN_HANDLE_CLASS = '!size-px !min-h-0 !min-w-0 !border-0 !bg-transparent';\n\nconst LaneNode: React.FC<NodeProps<TLaneNode>> = ({ data }) => {\n const { lane, onLaneClick } = data;\n const content = (\n <>\n <span className=\"truncate text-sm font-semibold text-foreground\">{lane.title}</span>\n {lane.subtitle && <span className=\"truncate text-xs text-foreground-subtle\">{lane.subtitle}</span>}\n {lane.badges && <div className=\"mt-auto flex flex-wrap gap-1\">{lane.badges}</div>}\n </>\n );\n\n return (\n <div style={{ width: LANE_WIDTH, height: LANE_HEIGHT }} className=\"relative\">\n <Handle\n id=\"over-out\"\n type=\"source\"\n position={Position.Top}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n <Handle\n id=\"over-in\"\n type=\"target\"\n position={Position.Top}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n {onLaneClick ? (\n <ControlledButton\n defaultHtmlButton\n onClick={() => onLaneClick(lane.laneId)}\n className=\"interactive flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 text-left shadow-sm hover:border-border-emphasis focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {content}\n </ControlledButton>\n ) : (\n <div className=\"flex size-full flex-col gap-y-1 rounded-xl border border-border bg-surface p-3 shadow-sm\">\n {content}\n </div>\n )}\n <Handle\n id=\"under-out\"\n type=\"source\"\n position={Position.Bottom}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n <Handle\n id=\"under-in\"\n type=\"target\"\n position={Position.Bottom}\n isConnectable={false}\n className={HIDDEN_HANDLE_CLASS}\n />\n </div>\n );\n};\n\n/** A curve between two lane edges whose height grows with the distance, so long arcs clear the short ones. */\nconst ArcEdge: React.FC<EdgeProps<TArcEdge>> = ({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n data,\n markerEnd,\n selected,\n}) => {\n const arc = data?.arc;\n const isOver = arc?.side !== 'under';\n const isBackward = targetX < sourceX;\n const lift = 40 + Math.abs(targetX - sourceX) * 0.2 + (isBackward ? 32 : 0);\n const controlY = isOver ? Math.min(sourceY, targetY) - lift * 1.3 : Math.max(sourceY, targetY) + lift * 1.3;\n const path = `M ${sourceX},${sourceY} C ${sourceX},${controlY} ${targetX},${controlY} ${targetX},${targetY}`;\n const labelX = (sourceX + targetX) / 2;\n const labelY = 0.125 * (sourceY + targetY) + 0.75 * controlY;\n\n return (\n <>\n <BaseEdge\n id={id}\n path={path}\n markerEnd={markerEnd}\n interactionWidth={16}\n className={classNames('umode-flow-arc', {\n 'umode-flow-arc--warning': arc?.tone === 'warning',\n 'umode-flow-arc--danger': arc?.tone === 'danger',\n 'umode-flow-arc--dashed': arc?.dashed,\n 'umode-flow-arc--selected': selected,\n })}\n />\n {arc?.label && (\n <EdgeLabelRenderer>\n <div\n className=\"nodrag nopan pointer-events-none absolute whitespace-nowrap\"\n style={{ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)` }}\n >\n <OutlinedChip\n label={arc.label}\n tone={arc.tone === 'warning' ? 'warning' : arc.tone === 'danger' ? 'danger' : 'neutral'}\n variant=\"outlined\"\n size=\"xs\"\n preserveLabelCase\n />\n </div>\n </EdgeLabelRenderer>\n )}\n </>\n );\n};\n\nconst PRO_OPTIONS: ProOptions = { hideAttribution: true };\nconst NODE_TYPES = { flowLane: LaneNode };\nconst EDGE_TYPES = { flowArc: ArcEdge };\n\n/**\n * A read-only map of fixed columns joined by arcs: the shape of a pipeline seen from\n * above. Lanes keep the order they are given, so no graph layout is needed.\n */\nconst FlowLaneMapContent: React.FC<IFlowLaneMapProps> = ({\n lanes,\n arcs,\n onArcClick,\n onLaneClick,\n selectedArcId,\n ariaLabel,\n className,\n}) => {\n const { t } = useTranslation('ui');\n const safeLanes = useMemo(() => (Array.isArray(lanes) ? lanes : []), [lanes]);\n const safeArcs = useMemo(() => (Array.isArray(arcs) ? arcs : []), [arcs]);\n\n const nodes = useMemo<Array<TLaneNode>>(\n () =>\n safeLanes.map((lane, index) => ({\n id: lane.laneId,\n type: 'flowLane',\n position: { x: index * (LANE_WIDTH + LANE_GAP), y: 0 },\n draggable: false,\n selectable: false,\n data: { lane, onLaneClick },\n })),\n [safeLanes, onLaneClick],\n );\n\n const laneIds = useMemo(() => new Set(safeLanes.map((lane) => lane.laneId)), [safeLanes]);\n\n const edges = useMemo<Array<TArcEdge>>(\n () =>\n safeArcs\n .filter(\n (arc) =>\n laneIds.has(arc.sourceLaneId) && laneIds.has(arc.targetLaneId) && arc.sourceLaneId !== arc.targetLaneId,\n )\n .map((arc) => ({\n id: arc.arcId,\n type: 'flowArc',\n source: arc.sourceLaneId,\n sourceHandle: `${arc.side}-out`,\n target: arc.targetLaneId,\n targetHandle: `${arc.side}-in`,\n selected: arc.arcId === selectedArcId,\n markerEnd: { type: MarkerType.ArrowClosed, width: 14, height: 14 },\n data: { arc },\n })),\n [safeArcs, laneIds, selectedArcId],\n );\n\n return (\n <div\n role=\"region\"\n aria-label={ariaLabel ?? t('flow.laneMap.label')}\n className={mergeClasses('umode-flow relative h-full min-h-[24rem] w-full rounded-xl bg-canvas', className)}\n >\n <ReactFlow<TLaneNode, TArcEdge>\n nodes={nodes}\n edges={edges}\n nodeTypes={NODE_TYPES}\n edgeTypes={EDGE_TYPES}\n nodesDraggable={false}\n nodesConnectable={false}\n deleteKeyCode={null}\n onEdgeClick={(_event, edge) => onArcClick?.(edge.id)}\n minZoom={0.25}\n maxZoom={1.5}\n fitView\n fitViewOptions={{ padding: 0.15, minZoom: 0.75 }}\n proOptions={PRO_OPTIONS}\n >\n <Background variant={BackgroundVariant.Dots} gap={16} size={1} />\n <FlowZoomControls />\n </ReactFlow>\n </div>\n );\n};\n\nconst FlowLaneMap: React.FC<IFlowLaneMapProps> = (props) => (\n <ReactFlowProvider>\n <FlowLaneMapContent {...props} />\n </ReactFlowProvider>\n);\n\nexport default FlowLaneMap;\n"],"names":[],"mappings":"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"}
@@ -2,9 +2,9 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useState, useRef, useEffect } from "react";
3
3
  import classNames from "classnames";
4
4
  import { useTranslation } from "react-i18next";
5
- import { twMerge } from "tailwind-merge";
6
5
  import ControlledButton from "../../Form/ControlledButton.js";
7
6
  import ControlledTooltip from "../ControlledTooltip.js";
7
+ import { mergeClasses } from "../../../helpers/mergeClasses.js";
8
8
  import { writeUiPreference, readUiPreference } from "../../../helpers/uiPreferences.js";
9
9
  const PANEL_WIDTHS_KEY = "umode-flow-panel-widths";
10
10
  const KEYBOARD_RESIZE_STEP = 24;
@@ -78,7 +78,7 @@ const FlowResizablePanel = ({
78
78
  return /* @__PURE__ */ jsxs(
79
79
  "div",
80
80
  {
81
- className: twMerge("relative w-full shrink-0 lg:w-[var(--flow-panel-width)]", className),
81
+ className: mergeClasses("relative w-full shrink-0 lg:w-[var(--flow-panel-width)]", className),
82
82
  style: { "--flow-panel-width": `${width}px` },
83
83
  children: [
84
84
  children,
@@ -86,6 +86,7 @@ const FlowResizablePanel = ({
86
86
  ControlledButton,
87
87
  {
88
88
  defaultHtmlButton: true,
89
+ "aria-label": t("flow.resizePanel"),
89
90
  onPointerDown: handlePointerDown,
90
91
  onPointerMove: handlePointerMove,
91
92
  onPointerUp: handlePointerEnd,
@@ -1 +1 @@
1
- {"version":3,"file":"FlowResizablePanel.js","sources":["../../../../src/components/UI/Flow/FlowResizablePanel.tsx"],"sourcesContent":["import { CSSProperties, ReactNode, useCallback, useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\nimport { twMerge } from 'tailwind-merge';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { readUiPreference, writeUiPreference } from '@/helpers/uiPreferences';\n\nconst PANEL_WIDTHS_KEY = 'umode-flow-panel-widths';\nconst KEYBOARD_RESIZE_STEP = 24;\n\nexport interface IFlowResizablePanelProps {\n /** Which edge carries the drag handle: the one that faces the canvas. */\n handleSide: 'left' | 'right';\n /** Widths in px, like any pointer-driven geometry. */\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n /** Remembers the width the user chose, per panel, across visits. */\n storageKey?: string;\n className?: string;\n children: ReactNode;\n}\n\nconst readStoredWidths = (): Record<string, number> => readUiPreference<Record<string, number>>(PANEL_WIDTHS_KEY) ?? {};\n\n/**\n * A side panel of the flow editor that the user can widen or narrow by dragging the edge facing\n * the canvas (arrow keys on the focused handle, double click resets). Below `lg` it takes the full\n * width of its container and the handle hides; where it sits then is up to the parent layout.\n */\nconst FlowResizablePanel: React.FC<IFlowResizablePanelProps> = ({\n handleSide,\n defaultWidth,\n minWidth,\n maxWidth,\n storageKey,\n className,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const clampWidth = useCallback(\n (width: number) => Math.min(maxWidth, Math.max(minWidth, Math.round(width))),\n [minWidth, maxWidth],\n );\n const [width, setWidth] = useState(() => {\n const stored = storageKey ? readStoredWidths()[storageKey] : undefined;\n return clampWidth(typeof stored === 'number' ? stored : defaultWidth);\n });\n const [isResizing, setIsResizing] = useState(false);\n const resizeStartRef = useRef<{ pointerX: number; width: number } | null>(null);\n const widthRef = useRef(width);\n widthRef.current = width;\n\n const persistWidth = useCallback(\n (nextWidth: number) => {\n if (!storageKey) return;\n writeUiPreference(PANEL_WIDTHS_KEY, { ...readStoredWidths(), [storageKey]: nextWidth });\n },\n [storageKey],\n );\n\n useEffect(() => setWidth((current) => clampWidth(current)), [clampWidth]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLButtonElement>) => {\n event.preventDefault();\n resizeStartRef.current = { pointerX: event.clientX, width: widthRef.current };\n setIsResizing(true);\n event.currentTarget.setPointerCapture(event.pointerId);\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLButtonElement>) => {\n const resizeStart = resizeStartRef.current;\n if (!resizeStart) return;\n const pointerDelta = event.clientX - resizeStart.pointerX;\n setWidth(clampWidth(resizeStart.width + (handleSide === 'right' ? pointerDelta : -pointerDelta)));\n };\n\n const handlePointerEnd = (event: React.PointerEvent<HTMLButtonElement>) => {\n if (!resizeStartRef.current) return;\n resizeStartRef.current = null;\n setIsResizing(false);\n persistWidth(widthRef.current);\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;\n event.preventDefault();\n const pointerDirection = event.key === 'ArrowRight' ? 1 : -1;\n const nextWidth = clampWidth(\n widthRef.current + (handleSide === 'right' ? pointerDirection : -pointerDirection) * KEYBOARD_RESIZE_STEP,\n );\n setWidth(nextWidth);\n persistWidth(nextWidth);\n };\n\n const handleReset = () => {\n const nextWidth = clampWidth(defaultWidth);\n setWidth(nextWidth);\n persistWidth(nextWidth);\n };\n\n return (\n <div\n className={twMerge('relative w-full shrink-0 lg:w-[var(--flow-panel-width)]', className)}\n style={{ '--flow-panel-width': `${width}px` } as CSSProperties}\n >\n {children}\n <ControlledTooltip content={t('flow.resizePanel')} placement={handleSide === 'right' ? 'right' : 'left'}>\n <ControlledButton\n defaultHtmlButton\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerEnd}\n onPointerCancel={handlePointerEnd}\n onKeyDown={handleKeyDown}\n onDoubleClick={handleReset}\n className={classNames(\n 'group/resize absolute inset-y-0 z-10 hidden w-3 cursor-col-resize touch-none select-none items-center justify-center focus:outline-none lg:flex',\n handleSide === 'right' ? '-right-3' : '-left-3',\n )}\n >\n <span\n className={classNames(\n 'h-full w-0.5 rounded-full transition-colors duration-base',\n isResizing ? 'bg-brand-hover' : 'bg-transparent group-hover/resize:bg-brand-hover',\n )}\n />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n );\n};\n\nexport default FlowResizablePanel;\n"],"names":["width"],"mappings":"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"}
1
+ {"version":3,"file":"FlowResizablePanel.js","sources":["../../../../src/components/UI/Flow/FlowResizablePanel.tsx"],"sourcesContent":["import { CSSProperties, ReactNode, useCallback, useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { mergeClasses } from '@/helpers/mergeClasses';\nimport { readUiPreference, writeUiPreference } from '@/helpers/uiPreferences';\n\nconst PANEL_WIDTHS_KEY = 'umode-flow-panel-widths';\nconst KEYBOARD_RESIZE_STEP = 24;\n\nexport interface IFlowResizablePanelProps {\n /** Which edge carries the drag handle: the one that faces the canvas. */\n handleSide: 'left' | 'right';\n /** Widths in px, like any pointer-driven geometry. */\n defaultWidth: number;\n minWidth: number;\n maxWidth: number;\n /** Remembers the width the user chose, per panel, across visits. */\n storageKey?: string;\n className?: string;\n children: ReactNode;\n}\n\nconst readStoredWidths = (): Record<string, number> => readUiPreference<Record<string, number>>(PANEL_WIDTHS_KEY) ?? {};\n\n/**\n * A side panel of the flow editor that the user can widen or narrow by dragging the edge facing\n * the canvas (arrow keys on the focused handle, double click resets). Below `lg` it takes the full\n * width of its container and the handle hides; where it sits then is up to the parent layout.\n */\nconst FlowResizablePanel: React.FC<IFlowResizablePanelProps> = ({\n handleSide,\n defaultWidth,\n minWidth,\n maxWidth,\n storageKey,\n className,\n children,\n}) => {\n const { t } = useTranslation('ui');\n const clampWidth = useCallback(\n (width: number) => Math.min(maxWidth, Math.max(minWidth, Math.round(width))),\n [minWidth, maxWidth],\n );\n const [width, setWidth] = useState(() => {\n const stored = storageKey ? readStoredWidths()[storageKey] : undefined;\n return clampWidth(typeof stored === 'number' ? stored : defaultWidth);\n });\n const [isResizing, setIsResizing] = useState(false);\n const resizeStartRef = useRef<{ pointerX: number; width: number } | null>(null);\n const widthRef = useRef(width);\n widthRef.current = width;\n\n const persistWidth = useCallback(\n (nextWidth: number) => {\n if (!storageKey) return;\n writeUiPreference(PANEL_WIDTHS_KEY, { ...readStoredWidths(), [storageKey]: nextWidth });\n },\n [storageKey],\n );\n\n useEffect(() => setWidth((current) => clampWidth(current)), [clampWidth]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLButtonElement>) => {\n event.preventDefault();\n resizeStartRef.current = { pointerX: event.clientX, width: widthRef.current };\n setIsResizing(true);\n event.currentTarget.setPointerCapture(event.pointerId);\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLButtonElement>) => {\n const resizeStart = resizeStartRef.current;\n if (!resizeStart) return;\n const pointerDelta = event.clientX - resizeStart.pointerX;\n setWidth(clampWidth(resizeStart.width + (handleSide === 'right' ? pointerDelta : -pointerDelta)));\n };\n\n const handlePointerEnd = (event: React.PointerEvent<HTMLButtonElement>) => {\n if (!resizeStartRef.current) return;\n resizeStartRef.current = null;\n setIsResizing(false);\n persistWidth(widthRef.current);\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId);\n }\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return;\n event.preventDefault();\n const pointerDirection = event.key === 'ArrowRight' ? 1 : -1;\n const nextWidth = clampWidth(\n widthRef.current + (handleSide === 'right' ? pointerDirection : -pointerDirection) * KEYBOARD_RESIZE_STEP,\n );\n setWidth(nextWidth);\n persistWidth(nextWidth);\n };\n\n const handleReset = () => {\n const nextWidth = clampWidth(defaultWidth);\n setWidth(nextWidth);\n persistWidth(nextWidth);\n };\n\n return (\n <div\n className={mergeClasses('relative w-full shrink-0 lg:w-[var(--flow-panel-width)]', className)}\n style={{ '--flow-panel-width': `${width}px` } as CSSProperties}\n >\n {children}\n <ControlledTooltip content={t('flow.resizePanel')} placement={handleSide === 'right' ? 'right' : 'left'}>\n <ControlledButton\n defaultHtmlButton\n aria-label={t('flow.resizePanel')}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerEnd}\n onPointerCancel={handlePointerEnd}\n onKeyDown={handleKeyDown}\n onDoubleClick={handleReset}\n className={classNames(\n 'group/resize absolute inset-y-0 z-10 hidden w-3 cursor-col-resize touch-none select-none items-center justify-center focus:outline-none lg:flex',\n handleSide === 'right' ? '-right-3' : '-left-3',\n )}\n >\n <span\n className={classNames(\n 'h-full w-0.5 rounded-full transition-colors duration-base',\n isResizing ? 'bg-brand-hover' : 'bg-transparent group-hover/resize:bg-brand-hover',\n )}\n />\n </ControlledButton>\n </ControlledTooltip>\n </div>\n );\n};\n\nexport default FlowResizablePanel;\n"],"names":["width"],"mappings":"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"}
@@ -7,6 +7,7 @@ import { useReactFlow, Panel } from "@xyflow/react";
7
7
  const ControlButton = ({ icon, label, onClick, disabled }) => /* @__PURE__ */ jsx(ControlledTooltip, { content: label, placement: "right", children: /* @__PURE__ */ jsx(
8
8
  ControlledButton,
9
9
  {
10
+ "aria-label": label,
10
11
  defaultHtmlButton: true,
11
12
  onClick,
12
13
  disabled,
@@ -1 +1 @@
1
- {"version":3,"file":"FlowZoomControls.js","sources":["../../../../src/components/UI/Flow/FlowZoomControls.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { Panel, type PanelPosition, useReactFlow } from '@xyflow/react';\n\ninterface IFlowZoomControlsProps {\n onUndo?: () => void;\n onRedo?: () => void;\n canUndo?: boolean;\n canRedo?: boolean;\n onLayout?: () => void;\n position?: PanelPosition;\n}\n\ninterface IControlButtonProps {\n icon: SymbolCodepoints;\n label: string;\n onClick: () => void;\n disabled?: boolean;\n}\n\nconst ControlButton: React.FC<IControlButtonProps> = ({ icon, label, onClick, disabled }) => (\n <ControlledTooltip content={label} placement=\"right\">\n <ControlledButton\n defaultHtmlButton\n onClick={onClick}\n disabled={disabled}\n className=\"interactive flex size-8 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40\"\n >\n <MaterialSymbol icon={icon} size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n);\n\n/** Zoom, fit and history controls drawn with the library's own buttons. */\nconst FlowZoomControls: React.FC<IFlowZoomControlsProps> = ({\n onUndo,\n onRedo,\n canUndo,\n canRedo,\n onLayout,\n position = 'top-left',\n}) => {\n const { t } = useTranslation('ui');\n const { zoomIn, zoomOut, fitView } = useReactFlow();\n\n return (\n <Panel\n position={position}\n className=\"flex flex-col gap-y-1 rounded-xl border border-border bg-surface p-1 shadow-sm\"\n >\n <ControlButton icon=\"add\" label={t('flow.controls.zoomIn')} onClick={() => void zoomIn()} />\n <ControlButton icon=\"remove\" label={t('flow.controls.zoomOut')} onClick={() => void zoomOut()} />\n <ControlButton\n icon=\"fit_screen\"\n label={t('flow.controls.fitView')}\n onClick={() => void fitView({ padding: 0.2 })}\n />\n {onLayout && <ControlButton icon=\"account_tree\" label={t('flow.controls.layout')} onClick={onLayout} />}\n {onUndo && <ControlButton icon=\"undo\" label={t('flow.controls.undo')} onClick={onUndo} disabled={!canUndo} />}\n {onRedo && <ControlButton icon=\"redo\" label={t('flow.controls.redo')} onClick={onRedo} disabled={!canRedo} />}\n </Panel>\n );\n};\n\nexport default FlowZoomControls;\n"],"names":[],"mappings":";;;;;;AAuBA,MAAM,gBAA+C,CAAC,EAAE,MAAM,OAAO,SAAS,SAAA,MAC5E,oBAAC,mBAAA,EAAkB,SAAS,OAAO,WAAU,SAC3C,UAAA;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,mBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,WAAU;AAAA,IAEV,8BAAC,gBAAA,EAAe,MAAY,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,EAAA;AACpD,GACF;AAIF,MAAM,mBAAqD,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,QAAQ,SAAS,QAAA,IAAY,aAAA;AAErC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA,oBAAC,eAAA,EAAc,MAAK,OAAM,OAAO,EAAE,sBAAsB,GAAG,SAAS,MAAM,KAAK,OAAA,EAAO,CAAG;AAAA,QAC1F,oBAAC,eAAA,EAAc,MAAK,UAAS,OAAO,EAAE,uBAAuB,GAAG,SAAS,MAAM,KAAK,QAAA,EAAQ,CAAG;AAAA,QAC/F;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO,EAAE,uBAAuB;AAAA,YAChC,SAAS,MAAM,KAAK,QAAQ,EAAE,SAAS,KAAK;AAAA,UAAA;AAAA,QAAA;AAAA,QAE7C,YAAY,oBAAC,eAAA,EAAc,MAAK,gBAAe,OAAO,EAAE,sBAAsB,GAAG,SAAS,SAAA,CAAU;AAAA,QACpG,UAAU,oBAAC,eAAA,EAAc,MAAK,QAAO,OAAO,EAAE,oBAAoB,GAAG,SAAS,QAAQ,UAAU,CAAC,QAAA,CAAS;AAAA,QAC1G,UAAU,oBAAC,eAAA,EAAc,MAAK,QAAO,OAAO,EAAE,oBAAoB,GAAG,SAAS,QAAQ,UAAU,CAAC,QAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGjH;"}
1
+ {"version":3,"file":"FlowZoomControls.js","sources":["../../../../src/components/UI/Flow/FlowZoomControls.tsx"],"sourcesContent":["import { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\nimport { Panel, type PanelPosition, useReactFlow } from '@xyflow/react';\n\ninterface IFlowZoomControlsProps {\n onUndo?: () => void;\n onRedo?: () => void;\n canUndo?: boolean;\n canRedo?: boolean;\n onLayout?: () => void;\n position?: PanelPosition;\n}\n\ninterface IControlButtonProps {\n icon: SymbolCodepoints;\n label: string;\n onClick: () => void;\n disabled?: boolean;\n}\n\nconst ControlButton: React.FC<IControlButtonProps> = ({ icon, label, onClick, disabled }) => (\n <ControlledTooltip content={label} placement=\"right\">\n <ControlledButton\n aria-label={label}\n defaultHtmlButton\n onClick={onClick}\n disabled={disabled}\n className=\"interactive flex size-8 items-center justify-center rounded-lg text-foreground-muted hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-40\"\n >\n <MaterialSymbol icon={icon} size={18} aria-hidden />\n </ControlledButton>\n </ControlledTooltip>\n);\n\n/** Zoom, fit and history controls drawn with the library's own buttons. */\nconst FlowZoomControls: React.FC<IFlowZoomControlsProps> = ({\n onUndo,\n onRedo,\n canUndo,\n canRedo,\n onLayout,\n position = 'top-left',\n}) => {\n const { t } = useTranslation('ui');\n const { zoomIn, zoomOut, fitView } = useReactFlow();\n\n return (\n <Panel\n position={position}\n className=\"flex flex-col gap-y-1 rounded-xl border border-border bg-surface p-1 shadow-sm\"\n >\n <ControlButton icon=\"add\" label={t('flow.controls.zoomIn')} onClick={() => void zoomIn()} />\n <ControlButton icon=\"remove\" label={t('flow.controls.zoomOut')} onClick={() => void zoomOut()} />\n <ControlButton\n icon=\"fit_screen\"\n label={t('flow.controls.fitView')}\n onClick={() => void fitView({ padding: 0.2 })}\n />\n {onLayout && <ControlButton icon=\"account_tree\" label={t('flow.controls.layout')} onClick={onLayout} />}\n {onUndo && <ControlButton icon=\"undo\" label={t('flow.controls.undo')} onClick={onUndo} disabled={!canUndo} />}\n {onRedo && <ControlButton icon=\"redo\" label={t('flow.controls.redo')} onClick={onRedo} disabled={!canRedo} />}\n </Panel>\n );\n};\n\nexport default FlowZoomControls;\n"],"names":[],"mappings":";;;;;;AAuBA,MAAM,gBAA+C,CAAC,EAAE,MAAM,OAAO,SAAS,SAAA,MAC5E,oBAAC,mBAAA,EAAkB,SAAS,OAAO,WAAU,SAC3C,UAAA;AAAA,EAAC;AAAA,EAAA;AAAA,IACC,cAAY;AAAA,IACZ,mBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,WAAU;AAAA,IAEV,8BAAC,gBAAA,EAAe,MAAY,MAAM,IAAI,eAAW,KAAA,CAAC;AAAA,EAAA;AACpD,GACF;AAIF,MAAM,mBAAqD,CAAC;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AACb,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AACjC,QAAM,EAAE,QAAQ,SAAS,QAAA,IAAY,aAAA;AAErC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MAEV,UAAA;AAAA,QAAA,oBAAC,eAAA,EAAc,MAAK,OAAM,OAAO,EAAE,sBAAsB,GAAG,SAAS,MAAM,KAAK,OAAA,EAAO,CAAG;AAAA,QAC1F,oBAAC,eAAA,EAAc,MAAK,UAAS,OAAO,EAAE,uBAAuB,GAAG,SAAS,MAAM,KAAK,QAAA,EAAQ,CAAG;AAAA,QAC/F;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO,EAAE,uBAAuB;AAAA,YAChC,SAAS,MAAM,KAAK,QAAQ,EAAE,SAAS,KAAK;AAAA,UAAA;AAAA,QAAA;AAAA,QAE7C,YAAY,oBAAC,eAAA,EAAc,MAAK,gBAAe,OAAO,EAAE,sBAAsB,GAAG,SAAS,SAAA,CAAU;AAAA,QACpG,UAAU,oBAAC,eAAA,EAAc,MAAK,QAAO,OAAO,EAAE,oBAAoB,GAAG,SAAS,QAAQ,UAAU,CAAC,QAAA,CAAS;AAAA,QAC1G,UAAU,oBAAC,eAAA,EAAc,MAAK,QAAO,OAAO,EAAE,oBAAoB,GAAG,SAAS,QAAQ,UAAU,CAAC,QAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGjH;"}
@@ -4,7 +4,7 @@ interface IHorizontalScrollAreaProps {
4
4
  className?: string;
5
5
  /** Classes for the outer positioning wrapper that carries the edge fades. */
6
6
  containerClassName?: string;
7
- /** Names the scrollable region for assistive tech, since it is keyboard focusable. */
7
+ /** Names the strip for assistive tech, since it is keyboard focusable. Makes it a `group`: a name on a div with no role is ignored. */
8
8
  ariaLabel?: string;
9
9
  }
10
10
  /**
@@ -32,6 +32,7 @@ const HorizontalScrollArea = ({
32
32
  /* @__PURE__ */ jsx(
33
33
  "div",
34
34
  {
35
+ role: ariaLabel ? "group" : void 0,
35
36
  "aria-label": ariaLabel,
36
37
  ref: scrollRef,
37
38
  onScroll: updateEdges,
@@ -1 +1 @@
1
- {"version":3,"file":"HorizontalScrollArea.js","sources":["../../../src/components/UI/HorizontalScrollArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\n\ninterface IHorizontalScrollAreaProps {\n children: React.ReactNode;\n /** Classes for the scrolling strip itself (usually the flex row that holds the items). */\n className?: string;\n /** Classes for the outer positioning wrapper that carries the edge fades. */\n containerClassName?: string;\n /** Names the scrollable region for assistive tech, since it is keyboard focusable. */\n ariaLabel?: string;\n}\n\n/**\n * A strip that keeps its children on one line and scrolls horizontally when they\n * do not fit, with a fade on whichever edge still has content behind it.\n *\n * This is the house answer to \"a row of controls that overflows on a narrow\n * screen\" (segmented tabs, bulk actions, filter chips). It exists so those rows\n * do not each grow their own clipping, wrapping or hide-on-mobile behaviour:\n * clipped controls are unreachable, and wrapping a control bar reflows the whole\n * page height. The fades are the only affordance a touch user gets that there is\n * more to the side, so they are part of the primitive rather than a caller's job.\n */\nconst HorizontalScrollArea: React.FC<IHorizontalScrollAreaProps> = ({\n children,\n className,\n containerClassName,\n ariaLabel,\n}) => {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n\n const updateEdges = useCallback(() => {\n const scroller = scrollRef.current;\n if (!scroller) return;\n\n const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;\n setCanScrollLeft(scroller.scrollLeft > 1);\n setCanScrollRight(scroller.scrollLeft < maxScrollLeft - 1);\n }, []);\n\n useEffect(() => {\n const scroller = scrollRef.current;\n if (!scroller) return undefined;\n\n updateEdges();\n\n const observer = new ResizeObserver(updateEdges);\n observer.observe(scroller);\n const firstChild = scroller.firstElementChild;\n if (firstChild) observer.observe(firstChild);\n\n return () => observer.disconnect();\n }, [updateEdges, children]);\n\n const fadeClass = 'pointer-events-none absolute inset-y-0 z-10 w-6 transition-opacity duration-fast';\n\n return (\n <div className={classNames('relative flex min-w-0 max-w-full', containerClassName)}>\n <div\n aria-label={ariaLabel}\n ref={scrollRef}\n onScroll={updateEdges}\n // `tabIndex` keeps an overflowing strip reachable by keyboard: without it a\n // scroll container that holds only non-focusable content cannot be panned.\n tabIndex={canScrollLeft || canScrollRight ? 0 : -1}\n className={classNames(\n 'scrollbar-hide min-w-0 max-w-full overflow-x-auto overscroll-x-contain focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border',\n className,\n )}\n >\n {children}\n </div>\n\n <span\n aria-hidden\n className={classNames(\n fadeClass,\n 'left-0 bg-gradient-to-r from-surface to-transparent',\n canScrollLeft ? 'opacity-100' : 'opacity-0',\n )}\n />\n <span\n aria-hidden\n className={classNames(\n fadeClass,\n 'right-0 bg-gradient-to-l from-surface to-transparent',\n canScrollRight ? 'opacity-100' : 'opacity-0',\n )}\n />\n </div>\n );\n};\n\nexport default HorizontalScrollArea;\n"],"names":[],"mappings":";;;AAwBA,MAAM,uBAA6D,CAAC;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AACxD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,KAAK;AAE1D,QAAM,cAAc,YAAY,MAAM;AACpC,UAAM,WAAW,UAAU;AAC3B,QAAI,CAAC,SAAU;AAEf,UAAM,gBAAgB,SAAS,cAAc,SAAS;AACtD,qBAAiB,SAAS,aAAa,CAAC;AACxC,sBAAkB,SAAS,aAAa,gBAAgB,CAAC;AAAA,EAC3D,GAAG,CAAA,CAAE;AAEL,YAAU,MAAM;AACd,UAAM,WAAW,UAAU;AAC3B,QAAI,CAAC,SAAU,QAAO;AAEtB,gBAAA;AAEA,UAAM,WAAW,IAAI,eAAe,WAAW;AAC/C,aAAS,QAAQ,QAAQ;AACzB,UAAM,aAAa,SAAS;AAC5B,QAAI,WAAY,UAAS,QAAQ,UAAU;AAE3C,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,aAAa,QAAQ,CAAC;AAE1B,QAAM,YAAY;AAElB,8BACG,OAAA,EAAI,WAAW,WAAW,oCAAoC,kBAAkB,GAC/E,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,cAAY;AAAA,QACZ,KAAK;AAAA,QACL,UAAU;AAAA,QAGV,UAAU,iBAAiB,iBAAiB,IAAI;AAAA,QAChD,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAGD;AAAA,MAAA;AAAA,IAAA;AAAA,IAGH;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,gBAAgB,gBAAgB;AAAA,QAAA;AAAA,MAClC;AAAA,IAAA;AAAA,IAEF;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,iBAAiB,gBAAgB;AAAA,QAAA;AAAA,MACnC;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
1
+ {"version":3,"file":"HorizontalScrollArea.js","sources":["../../../src/components/UI/HorizontalScrollArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport classNames from 'classnames';\n\ninterface IHorizontalScrollAreaProps {\n children: React.ReactNode;\n /** Classes for the scrolling strip itself (usually the flex row that holds the items). */\n className?: string;\n /** Classes for the outer positioning wrapper that carries the edge fades. */\n containerClassName?: string;\n /** Names the strip for assistive tech, since it is keyboard focusable. Makes it a `group`: a name on a div with no role is ignored. */\n ariaLabel?: string;\n}\n\n/**\n * A strip that keeps its children on one line and scrolls horizontally when they\n * do not fit, with a fade on whichever edge still has content behind it.\n *\n * This is the house answer to \"a row of controls that overflows on a narrow\n * screen\" (segmented tabs, bulk actions, filter chips). It exists so those rows\n * do not each grow their own clipping, wrapping or hide-on-mobile behaviour:\n * clipped controls are unreachable, and wrapping a control bar reflows the whole\n * page height. The fades are the only affordance a touch user gets that there is\n * more to the side, so they are part of the primitive rather than a caller's job.\n */\nconst HorizontalScrollArea: React.FC<IHorizontalScrollAreaProps> = ({\n children,\n className,\n containerClassName,\n ariaLabel,\n}) => {\n const scrollRef = useRef<HTMLDivElement>(null);\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n\n const updateEdges = useCallback(() => {\n const scroller = scrollRef.current;\n if (!scroller) return;\n\n const maxScrollLeft = scroller.scrollWidth - scroller.clientWidth;\n setCanScrollLeft(scroller.scrollLeft > 1);\n setCanScrollRight(scroller.scrollLeft < maxScrollLeft - 1);\n }, []);\n\n useEffect(() => {\n const scroller = scrollRef.current;\n if (!scroller) return undefined;\n\n updateEdges();\n\n const observer = new ResizeObserver(updateEdges);\n observer.observe(scroller);\n const firstChild = scroller.firstElementChild;\n if (firstChild) observer.observe(firstChild);\n\n return () => observer.disconnect();\n }, [updateEdges, children]);\n\n const fadeClass = 'pointer-events-none absolute inset-y-0 z-10 w-6 transition-opacity duration-fast';\n\n return (\n <div className={classNames('relative flex min-w-0 max-w-full', containerClassName)}>\n <div\n role={ariaLabel ? 'group' : undefined}\n aria-label={ariaLabel}\n ref={scrollRef}\n onScroll={updateEdges}\n // `tabIndex` keeps an overflowing strip reachable by keyboard: without it a\n // scroll container that holds only non-focusable content cannot be panned.\n tabIndex={canScrollLeft || canScrollRight ? 0 : -1}\n className={classNames(\n 'scrollbar-hide min-w-0 max-w-full overflow-x-auto overscroll-x-contain focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-border',\n className,\n )}\n >\n {children}\n </div>\n\n <span\n aria-hidden\n className={classNames(\n fadeClass,\n 'left-0 bg-gradient-to-r from-surface to-transparent',\n canScrollLeft ? 'opacity-100' : 'opacity-0',\n )}\n />\n <span\n aria-hidden\n className={classNames(\n fadeClass,\n 'right-0 bg-gradient-to-l from-surface to-transparent',\n canScrollRight ? 'opacity-100' : 'opacity-0',\n )}\n />\n </div>\n );\n};\n\nexport default HorizontalScrollArea;\n"],"names":[],"mappings":";;;AAwBA,MAAM,uBAA6D,CAAC;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,YAAY,OAAuB,IAAI;AAC7C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,KAAK;AACxD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,KAAK;AAE1D,QAAM,cAAc,YAAY,MAAM;AACpC,UAAM,WAAW,UAAU;AAC3B,QAAI,CAAC,SAAU;AAEf,UAAM,gBAAgB,SAAS,cAAc,SAAS;AACtD,qBAAiB,SAAS,aAAa,CAAC;AACxC,sBAAkB,SAAS,aAAa,gBAAgB,CAAC;AAAA,EAC3D,GAAG,CAAA,CAAE;AAEL,YAAU,MAAM;AACd,UAAM,WAAW,UAAU;AAC3B,QAAI,CAAC,SAAU,QAAO;AAEtB,gBAAA;AAEA,UAAM,WAAW,IAAI,eAAe,WAAW;AAC/C,aAAS,QAAQ,QAAQ;AACzB,UAAM,aAAa,SAAS;AAC5B,QAAI,WAAY,UAAS,QAAQ,UAAU;AAE3C,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,aAAa,QAAQ,CAAC;AAE1B,QAAM,YAAY;AAElB,8BACG,OAAA,EAAI,WAAW,WAAW,oCAAoC,kBAAkB,GAC/E,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM,YAAY,UAAU;AAAA,QAC5B,cAAY;AAAA,QACZ,KAAK;AAAA,QACL,UAAU;AAAA,QAGV,UAAU,iBAAiB,iBAAiB,IAAI;AAAA,QAChD,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAGD;AAAA,MAAA;AAAA,IAAA;AAAA,IAGH;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,gBAAgB,gBAAgB;AAAA,QAAA;AAAA,MAClC;AAAA,IAAA;AAAA,IAEF;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,iBAAiB,gBAAgB;AAAA,QAAA;AAAA,MACnC;AAAA,IAAA;AAAA,EACF,GACF;AAEJ;"}
@@ -34,6 +34,7 @@ const AnnotationColorPopover = ({
34
34
  /* @__PURE__ */ jsx(ControlledTooltip, { content: t("imageAnnotator.style.none"), className: "z-[9999]", children: /* @__PURE__ */ jsx(
35
35
  ControlledButton,
36
36
  {
37
+ "aria-label": t("imageAnnotator.style.none"),
37
38
  defaultHtmlButton: true,
38
39
  onClick: () => onChange(null),
39
40
  "aria-pressed": !value,
@@ -1 +1 @@
1
- {"version":3,"file":"AnnotationColorPopover.js","sources":["../../../../../src/components/UI/ImageAnnotator/components/AnnotationColorPopover.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nimport { ANNOTATION_COLORS } from '../annotationStyle';\n\ninterface IAnnotationColorPopoverProps {\n icon: SymbolCodepoints;\n label: string;\n value: string | null;\n touch?: boolean;\n onChange: (color: string | null) => void;\n}\n\n/** Fill and text background: both need the palette plus an explicit \"no colour\" choice. */\nconst AnnotationColorPopover: React.FC<IAnnotationColorPopoverProps> = ({\n icon,\n label,\n value,\n touch = false,\n onChange,\n}) => {\n const { t } = useTranslation('ui');\n\n return (\n <ControlledPopoverRoot placement=\"bottom-start\">\n <ControlledTooltip content={label} className=\"z-[9999]\">\n <ControlledPopoverHandler>\n <ControlledButton\n defaultHtmlButton\n aria-label={label}\n className={classNames(\n 'interactive flex shrink-0 items-center gap-x-1.5 rounded-lg px-2 text-foreground-subtle hover:bg-muted-active hover:text-foreground',\n touch ? 'h-11' : 'h-9',\n )}\n >\n <MaterialSymbol icon={icon} size={20} />\n {value ? (\n <span className=\"size-4 rounded-full border border-border-strong/30\" style={{ backgroundColor: value }} />\n ) : (\n <span className=\"flex size-4 items-center justify-center rounded-full border border-border-strong/30 bg-muted text-foreground-faint\">\n <MaterialSymbol icon=\"block\" size={10} />\n </span>\n )}\n </ControlledButton>\n </ControlledPopoverHandler>\n </ControlledTooltip>\n\n <ControlledPopoverContent className=\"z-[9999] w-auto rounded-xl border border-border bg-surface-overlay p-2 glass\">\n <div className=\"flex max-w-52 flex-wrap items-center gap-1.5\">\n <ControlledTooltip content={t('imageAnnotator.style.none')} className=\"z-[9999]\">\n <ControlledButton\n defaultHtmlButton\n onClick={() => onChange(null)}\n aria-pressed={!value}\n className={classNames(\n 'flex size-7 items-center justify-center rounded-full border border-border-strong/30 text-foreground-faint transition-transform hover:scale-110',\n !value && 'scale-110 ring-2 ring-ring ring-offset-1',\n )}\n >\n <MaterialSymbol icon=\"block\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n\n {ANNOTATION_COLORS.map((color) => (\n <ControlledButton\n key={color}\n defaultHtmlButton\n onClick={() => onChange(color)}\n aria-label={color}\n aria-pressed={value === color}\n className={classNames(\n 'size-7 rounded-full border border-border-strong/30 transition-transform hover:scale-110',\n value === color && 'scale-110 ring-2 ring-ring ring-offset-1',\n )}\n style={{ backgroundColor: color }}\n >\n {null}\n </ControlledButton>\n ))}\n </div>\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default AnnotationColorPopover;\n"],"names":[],"mappings":";;;;;;;;AAuBA,MAAM,yBAAiE,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,SACE,qBAAC,uBAAA,EAAsB,WAAU,gBAC/B,UAAA;AAAA,IAAA,oBAAC,qBAAkB,SAAS,OAAO,WAAU,YAC3C,8BAAC,0BAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,mBAAiB;AAAA,QACjB,cAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA,QAAQ,SAAS;AAAA,QAAA;AAAA,QAGnB,UAAA;AAAA,UAAA,oBAAC,gBAAA,EAAe,MAAY,MAAM,GAAA,CAAI;AAAA,UACrC,4BACE,QAAA,EAAK,WAAU,sDAAqD,OAAO,EAAE,iBAAiB,MAAA,EAAM,CAAG,IAExG,oBAAC,QAAA,EAAK,WAAU,sHACd,UAAA,oBAAC,kBAAe,MAAK,SAAQ,MAAM,GAAA,CAAI,EAAA,CACzC;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAGN,EAAA,CACF;AAAA,wBAEC,0BAAA,EAAyB,WAAU,gFAClC,UAAA,qBAAC,OAAA,EAAI,WAAU,gDACb,UAAA;AAAA,MAAA,oBAAC,qBAAkB,SAAS,EAAE,2BAA2B,GAAG,WAAU,YACpE,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,mBAAiB;AAAA,UACjB,SAAS,MAAM,SAAS,IAAI;AAAA,UAC5B,gBAAc,CAAC;AAAA,UACf,WAAW;AAAA,YACT;AAAA,YACA,CAAC,SAAS;AAAA,UAAA;AAAA,UAGZ,UAAA,oBAAC,gBAAA,EAAe,MAAK,SAAQ,MAAM,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA,GAE3C;AAAA,MAEC,kBAAkB,IAAI,CAAC,UACtB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,mBAAiB;AAAA,UACjB,SAAS,MAAM,SAAS,KAAK;AAAA,UAC7B,cAAY;AAAA,UACZ,gBAAc,UAAU;AAAA,UACxB,WAAW;AAAA,YACT;AAAA,YACA,UAAU,SAAS;AAAA,UAAA;AAAA,UAErB,OAAO,EAAE,iBAAiB,MAAA;AAAA,UAEzB,UAAA;AAAA,QAAA;AAAA,QAXI;AAAA,MAAA,CAaR;AAAA,IAAA,EAAA,CACH,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"AnnotationColorPopover.js","sources":["../../../../../src/components/UI/ImageAnnotator/components/AnnotationColorPopover.tsx"],"sourcesContent":["import classNames from 'classnames';\nimport { useTranslation } from 'react-i18next';\n\nimport ControlledButton from '@/components/Form/ControlledButton';\nimport {\n ControlledPopoverContent,\n ControlledPopoverHandler,\n ControlledPopoverRoot,\n} from '@/components/UI/ControlledPopover';\nimport ControlledTooltip from '@/components/UI/ControlledTooltip';\nimport { MaterialSymbol, SymbolCodepoints } from '@/components/UI/MaterialSymbol';\n\nimport { ANNOTATION_COLORS } from '../annotationStyle';\n\ninterface IAnnotationColorPopoverProps {\n icon: SymbolCodepoints;\n label: string;\n value: string | null;\n touch?: boolean;\n onChange: (color: string | null) => void;\n}\n\n/** Fill and text background: both need the palette plus an explicit \"no colour\" choice. */\nconst AnnotationColorPopover: React.FC<IAnnotationColorPopoverProps> = ({\n icon,\n label,\n value,\n touch = false,\n onChange,\n}) => {\n const { t } = useTranslation('ui');\n\n return (\n <ControlledPopoverRoot placement=\"bottom-start\">\n <ControlledTooltip content={label} className=\"z-[9999]\">\n <ControlledPopoverHandler>\n <ControlledButton\n defaultHtmlButton\n aria-label={label}\n className={classNames(\n 'interactive flex shrink-0 items-center gap-x-1.5 rounded-lg px-2 text-foreground-subtle hover:bg-muted-active hover:text-foreground',\n touch ? 'h-11' : 'h-9',\n )}\n >\n <MaterialSymbol icon={icon} size={20} />\n {value ? (\n <span className=\"size-4 rounded-full border border-border-strong/30\" style={{ backgroundColor: value }} />\n ) : (\n <span className=\"flex size-4 items-center justify-center rounded-full border border-border-strong/30 bg-muted text-foreground-faint\">\n <MaterialSymbol icon=\"block\" size={10} />\n </span>\n )}\n </ControlledButton>\n </ControlledPopoverHandler>\n </ControlledTooltip>\n\n <ControlledPopoverContent className=\"z-[9999] w-auto rounded-xl border border-border bg-surface-overlay p-2 glass\">\n <div className=\"flex max-w-52 flex-wrap items-center gap-1.5\">\n <ControlledTooltip content={t('imageAnnotator.style.none')} className=\"z-[9999]\">\n <ControlledButton\n aria-label={t('imageAnnotator.style.none')}\n defaultHtmlButton\n onClick={() => onChange(null)}\n aria-pressed={!value}\n className={classNames(\n 'flex size-7 items-center justify-center rounded-full border border-border-strong/30 text-foreground-faint transition-transform hover:scale-110',\n !value && 'scale-110 ring-2 ring-ring ring-offset-1',\n )}\n >\n <MaterialSymbol icon=\"block\" size={14} />\n </ControlledButton>\n </ControlledTooltip>\n\n {ANNOTATION_COLORS.map((color) => (\n <ControlledButton\n key={color}\n defaultHtmlButton\n onClick={() => onChange(color)}\n aria-label={color}\n aria-pressed={value === color}\n className={classNames(\n 'size-7 rounded-full border border-border-strong/30 transition-transform hover:scale-110',\n value === color && 'scale-110 ring-2 ring-ring ring-offset-1',\n )}\n style={{ backgroundColor: color }}\n >\n {null}\n </ControlledButton>\n ))}\n </div>\n </ControlledPopoverContent>\n </ControlledPopoverRoot>\n );\n};\n\nexport default AnnotationColorPopover;\n"],"names":[],"mappings":";;;;;;;;AAuBA,MAAM,yBAAiE,CAAC;AAAA,EACtE;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AACF,MAAM;AACJ,QAAM,EAAE,EAAA,IAAM,eAAe,IAAI;AAEjC,SACE,qBAAC,uBAAA,EAAsB,WAAU,gBAC/B,UAAA;AAAA,IAAA,oBAAC,qBAAkB,SAAS,OAAO,WAAU,YAC3C,8BAAC,0BAAA,EACC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,mBAAiB;AAAA,QACjB,cAAY;AAAA,QACZ,WAAW;AAAA,UACT;AAAA,UACA,QAAQ,SAAS;AAAA,QAAA;AAAA,QAGnB,UAAA;AAAA,UAAA,oBAAC,gBAAA,EAAe,MAAY,MAAM,GAAA,CAAI;AAAA,UACrC,4BACE,QAAA,EAAK,WAAU,sDAAqD,OAAO,EAAE,iBAAiB,MAAA,EAAM,CAAG,IAExG,oBAAC,QAAA,EAAK,WAAU,sHACd,UAAA,oBAAC,kBAAe,MAAK,SAAQ,MAAM,GAAA,CAAI,EAAA,CACzC;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA,GAGN,EAAA,CACF;AAAA,wBAEC,0BAAA,EAAyB,WAAU,gFAClC,UAAA,qBAAC,OAAA,EAAI,WAAU,gDACb,UAAA;AAAA,MAAA,oBAAC,qBAAkB,SAAS,EAAE,2BAA2B,GAAG,WAAU,YACpE,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,cAAY,EAAE,2BAA2B;AAAA,UACzC,mBAAiB;AAAA,UACjB,SAAS,MAAM,SAAS,IAAI;AAAA,UAC5B,gBAAc,CAAC;AAAA,UACf,WAAW;AAAA,YACT;AAAA,YACA,CAAC,SAAS;AAAA,UAAA;AAAA,UAGZ,UAAA,oBAAC,gBAAA,EAAe,MAAK,SAAQ,MAAM,GAAA,CAAI;AAAA,QAAA;AAAA,MAAA,GAE3C;AAAA,MAEC,kBAAkB,IAAI,CAAC,UACtB;AAAA,QAAC;AAAA,QAAA;AAAA,UAEC,mBAAiB;AAAA,UACjB,SAAS,MAAM,SAAS,KAAK;AAAA,UAC7B,cAAY;AAAA,UACZ,gBAAc,UAAU;AAAA,UACxB,WAAW;AAAA,YACT;AAAA,YACA,UAAU,SAAS;AAAA,UAAA;AAAA,UAErB,OAAO,EAAE,iBAAiB,MAAA;AAAA,UAEzB,UAAA;AAAA,QAAA;AAAA,QAXI;AAAA,MAAA,CAaR;AAAA,IAAA,EAAA,CACH,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}