{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ipycanvas",
  "title": "ipycanvas Widget",
  "description": "Canvas widget for ipycanvas. Renders an HTML canvas and processes drawing commands sent from Python via the ipycanvas binary protocol. Supports shapes, paths, text, transforms, batch operations, and mouse/keyboard events.",
  "registryDependencies": [
    "@nteract/widget-view"
  ],
  "files": [
    {
      "path": "registry/widgets/ipycanvas/index.ts",
      "content": "import { registerWidget } from \"../widget-registry\";\nimport { CanvasManagerWidget, CanvasWidget } from \"./canvas-widget\";\n\nregisterWidget(\"CanvasModel\", CanvasWidget);\nregisterWidget(\"CanvasManagerModel\", CanvasManagerWidget);\n\nexport { CanvasManagerWidget, CanvasWidget } from \"./canvas-widget\";\n",
      "type": "registry:lib",
      "target": "components/widgets/ipycanvas/index.ts"
    },
    {
      "path": "registry/widgets/ipycanvas/canvas-widget.tsx",
      "content": "\"use client\";\n\n/**\n * ipycanvas Canvas widget.\n *\n * Renders an HTML <canvas> element and processes drawing commands sent from\n * Python via the ipycanvas binary protocol. Drawing commands arrive at each\n * canvas's comm_id, routed by createCanvasManagerRouter at the store level.\n *\n * @see https://ipycanvas.readthedocs.io/\n */\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport type { WidgetComponentProps } from \"../widget-registry\";\nimport {\n  useWidgetModelValue,\n  useWidgetStoreRequired,\n} from \"../widget-store-context\";\nimport { getTypedArray, processCommands } from \"./ipycanvas-commands\";\n\n// === CanvasWidget ===\n\nexport function CanvasWidget({ modelId, className }: WidgetComponentProps) {\n  const { store, sendCustom } = useWidgetStoreRequired();\n\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const ctxRef = useRef<CanvasRenderingContext2D | null>(null);\n  // Track an async command processing chain so commands execute in order\n  const processingRef = useRef<Promise<void>>(Promise.resolve());\n\n  const width = useWidgetModelValue<number>(modelId, \"width\") ?? 200;\n  const height = useWidgetModelValue<number>(modelId, \"height\") ?? 200;\n  const sendClientReady =\n    useWidgetModelValue<boolean>(modelId, \"_send_client_ready_event\") ?? true;\n  const imageData = useWidgetModelValue<Uint8ClampedArray | null>(\n    modelId,\n    \"image_data\",\n  );\n\n  // Initialize 2D context\n  useEffect(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    ctxRef.current = canvas.getContext(\"2d\");\n  }, []);\n\n  // Draw image_data when it changes (for restoring canvas state)\n  useEffect(() => {\n    if (!imageData || !ctxRef.current || !canvasRef.current) return;\n\n    const blob = new Blob([imageData.buffer as ArrayBuffer]);\n    const url = URL.createObjectURL(blob);\n    const img = new Image();\n    img.onload = () => {\n      ctxRef.current?.drawImage(img, 0, 0);\n      URL.revokeObjectURL(url);\n    };\n    img.src = url;\n  }, [imageData]);\n\n  // Subscribe to messages routed to this canvas's comm_id, then send\n  // client_ready. Routing is handled by createCanvasManagerRouter at the\n  // store level — this widget just processes what it receives.\n  useEffect(() => {\n    const unsubscribe = store.subscribeToCustomMessage(\n      modelId,\n      (content, buffers) => {\n        const canvas = canvasRef.current;\n        const ctx = ctxRef.current;\n        if (!canvas || !ctx || !buffers || buffers.length === 0) return;\n\n        // Chain command processing to maintain order\n        processingRef.current = processingRef.current.then(async () => {\n          try {\n            // First buffer is the JSON-encoded commands\n            const metadata = content as { dtype: string };\n            const typedArray = getTypedArray(buffers[0], metadata);\n            const jsonStr = new TextDecoder(\"utf-8\").decode(typedArray);\n            const commands = JSON.parse(jsonStr);\n\n            // Remaining buffers are binary data for batch operations\n            const dataBuffers = buffers.slice(1);\n\n            await processCommands(\n              ctx,\n              commands,\n              dataBuffers,\n              canvas,\n              modelId,\n              true,\n            );\n          } catch (err) {\n            console.warn(\"[ipycanvas] Error processing commands:\", err);\n          }\n        });\n      },\n    );\n\n    if (sendClientReady) {\n      sendCustom(modelId, { event: \"client_ready\" });\n    }\n\n    return unsubscribe;\n  }, [store, modelId, sendClientReady, sendCustom]);\n\n  // Mouse event helpers\n  const getCoordinates = useCallback(\n    (event: React.MouseEvent<HTMLCanvasElement>) => {\n      const canvas = canvasRef.current;\n      if (!canvas) return { x: 0, y: 0 };\n      const rect = canvas.getBoundingClientRect();\n      return {\n        x: (canvas.width * (event.clientX - rect.left)) / rect.width,\n        y: (canvas.height * (event.clientY - rect.top)) / rect.height,\n      };\n    },\n    [],\n  );\n\n  const onMouseMove = useCallback(\n    (event: React.MouseEvent<HTMLCanvasElement>) => {\n      sendCustom(modelId, { event: \"mouse_move\", ...getCoordinates(event) });\n    },\n    [modelId, sendCustom, getCoordinates],\n  );\n\n  const onMouseDown = useCallback(\n    (event: React.MouseEvent<HTMLCanvasElement>) => {\n      canvasRef.current?.focus();\n      sendCustom(modelId, { event: \"mouse_down\", ...getCoordinates(event) });\n    },\n    [modelId, sendCustom, getCoordinates],\n  );\n\n  const onMouseUp = useCallback(\n    (event: React.MouseEvent<HTMLCanvasElement>) => {\n      sendCustom(modelId, { event: \"mouse_up\", ...getCoordinates(event) });\n    },\n    [modelId, sendCustom, getCoordinates],\n  );\n\n  const onMouseOut = useCallback(\n    (event: React.MouseEvent<HTMLCanvasElement>) => {\n      sendCustom(modelId, { event: \"mouse_out\", ...getCoordinates(event) });\n    },\n    [modelId, sendCustom, getCoordinates],\n  );\n\n  const onWheel = useCallback(\n    (event: React.WheelEvent<HTMLCanvasElement>) => {\n      sendCustom(modelId, {\n        event: \"mouse_wheel\",\n        x: event.deltaX,\n        y: event.deltaY,\n      });\n      event.preventDefault();\n    },\n    [modelId, sendCustom],\n  );\n\n  const onKeyDown = useCallback(\n    (event: React.KeyboardEvent<HTMLCanvasElement>) => {\n      event.preventDefault();\n      event.stopPropagation();\n      sendCustom(modelId, {\n        event: \"key_down\",\n        key: event.key,\n        shift_key: event.shiftKey,\n        ctrl_key: event.ctrlKey,\n        meta_key: event.metaKey,\n      });\n    },\n    [modelId, sendCustom],\n  );\n\n  return (\n    <canvas\n      ref={canvasRef}\n      width={width}\n      height={height}\n      tabIndex={0}\n      className={cn(\"block\", className)}\n      data-widget-id={modelId}\n      data-widget-type=\"Canvas\"\n      onMouseMove={onMouseMove}\n      onMouseDown={onMouseDown}\n      onMouseUp={onMouseUp}\n      onMouseOut={onMouseOut}\n      onBlur={() => sendCustom(modelId, { event: \"mouse_out\", x: 0, y: 0 })}\n      onWheel={onWheel}\n      onKeyDown={onKeyDown}\n    />\n  );\n}\n\n// === CanvasManagerWidget ===\n\n/**\n * Stub for CanvasManagerModel. Routing is handled at the store level\n * by createCanvasManagerRouter. CanvasManagerModel has _view_name: null\n * and is never rendered.\n */\nexport function CanvasManagerWidget(_props: WidgetComponentProps) {\n  return null;\n}\n",
      "type": "registry:component",
      "target": "components/widgets/ipycanvas/canvas-widget.tsx"
    },
    {
      "path": "registry/widgets/ipycanvas/ipycanvas-commands.ts",
      "content": "export const COMMANDS = [\n  \"fillRect\",\n  \"strokeRect\",\n  \"fillRects\",\n  \"strokeRects\",\n  \"clearRect\",\n  \"fillArc\",\n  \"fillCircle\",\n  \"strokeArc\",\n  \"strokeCircle\",\n  \"fillArcs\",\n  \"strokeArcs\",\n  \"fillCircles\",\n  \"strokeCircles\",\n  \"strokeLine\",\n  \"beginPath\",\n  \"closePath\",\n  \"stroke\",\n  \"strokePath\",\n  \"fillPath\",\n  \"fill\",\n  \"moveTo\",\n  \"lineTo\",\n  \"rect\",\n  \"arc\",\n  \"ellipse\",\n  \"arcTo\",\n  \"quadraticCurveTo\",\n  \"bezierCurveTo\",\n  \"fillText\",\n  \"strokeText\",\n  \"setLineDash\",\n  \"drawImage\",\n  \"putImageData\",\n  \"clip\",\n  \"save\",\n  \"restore\",\n  \"translate\",\n  \"rotate\",\n  \"scale\",\n  \"transform\",\n  \"setTransform\",\n  \"resetTransform\",\n  \"set\",\n  \"clear\",\n  \"sleep\",\n  \"fillPolygon\",\n  \"strokePolygon\",\n  \"strokeLines\",\n  \"fillPolygons\",\n  \"strokePolygons\",\n  \"strokeLineSegments\",\n  \"fillStyledRects\",\n  \"strokeStyledRects\",\n  \"fillStyledCircles\",\n  \"strokeStyledCircles\",\n  \"fillStyledArcs\",\n  \"strokeStyledArcs\",\n  \"fillStyledPolygons\",\n  \"strokeStyledPolygons\",\n  \"strokeStyledLineSegments\",\n  \"switchCanvas\",\n] as const;\n\n/**\n * Canvas 2D context attribute names, indexed by their protocol number.\n * Used by the \"set\" command to update context properties.\n */\nconst CANVAS_ATTRS = [\n  \"fillStyle\",\n  \"strokeStyle\",\n  \"globalAlpha\",\n  \"font\",\n  \"textAlign\",\n  \"textBaseline\",\n  \"direction\",\n  \"globalCompositeOperation\",\n  \"lineWidth\",\n  \"lineCap\",\n  \"lineJoin\",\n  \"miterLimit\",\n  \"lineDashOffset\",\n  \"shadowOffsetX\",\n  \"shadowOffsetY\",\n  \"shadowBlur\",\n  \"shadowColor\",\n  \"filter\",\n  \"imageSmoothingEnabled\",\n] as const;\n\n// === Binary Buffer Helpers ===\n\ntype TypedArray =\n  | Int8Array\n  | Uint8Array\n  | Int16Array\n  | Uint16Array\n  | Int32Array\n  | Uint32Array\n  | Float32Array\n  | Float64Array;\n\n/**\n * Convert a DataView to the appropriate TypedArray based on dtype metadata.\n */\nexport function getTypedArray(\n  dataview: DataView,\n  metadata: { dtype: string },\n): TypedArray {\n  const buffer = dataview.buffer;\n  switch (metadata.dtype) {\n    case \"int8\":\n      return new Int8Array(buffer);\n    case \"uint8\":\n      return new Uint8Array(buffer);\n    case \"int16\":\n      return new Int16Array(buffer);\n    case \"uint16\":\n      return new Uint16Array(buffer);\n    case \"int32\":\n      return new Int32Array(buffer);\n    case \"uint32\":\n      return new Uint32Array(buffer);\n    case \"float32\":\n      return new Float32Array(buffer);\n    case \"float64\":\n      return new Float64Array(buffer);\n    default:\n      throw new Error(`Unknown dtype: ${metadata.dtype}`);\n  }\n}\n\n// === Argument Wrappers ===\n\n/**\n * Abstract argument that can be indexed into.\n * Used for batch operations where arguments may be scalars or buffer-backed arrays.\n */\ninterface Arg {\n  getItem(idx: number): number;\n  length: number;\n}\n\nclass ScalarArg implements Arg {\n  constructor(public value: number | boolean | string | null) {\n    this.length = Infinity;\n  }\n\n  getItem(_idx: number): number {\n    return this.value as number;\n  }\n\n  length: number;\n}\n\nclass BufferArg implements Arg {\n  private data: TypedArray;\n\n  constructor(bufferMetadata: { dtype: string }, buffer: DataView) {\n    this.data = getTypedArray(buffer, bufferMetadata);\n    this.length = this.data.length;\n  }\n\n  getItem(idx: number): number {\n    return this.data[idx] as number;\n  }\n\n  length: number;\n}\n\n/**\n * Resolve a command argument to either a scalar or buffer-backed array.\n * Scalars repeat the same value for all indices.\n * Buffer args reference a specific binary buffer by index.\n */\nfunction getArg(metadata: unknown, buffers: DataView[]): Arg {\n  if (\n    metadata === null ||\n    typeof metadata === \"boolean\" ||\n    typeof metadata === \"number\" ||\n    typeof metadata === \"string\"\n  ) {\n    return new ScalarArg(metadata);\n  }\n\n  if (typeof metadata === \"object\" && metadata !== null && \"idx\" in metadata) {\n    const meta = metadata as { idx: number; dtype: string };\n    return new BufferArg(meta, buffers[meta.idx]);\n  }\n\n  throw new Error(`Could not process argument: ${JSON.stringify(metadata)}`);\n}\n\n// === Drawing Helpers ===\n\nfunction drawRects(\n  _ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  callback: (x: number, y: number, w: number, h: number) => void,\n) {\n  const x = getArg(args[0], buffers);\n  const y = getArg(args[1], buffers);\n  const width = getArg(args[2], buffers);\n  const height = getArg(args[3], buffers);\n  const count = Math.min(x.length, y.length, width.length, height.length);\n\n  for (let i = 0; i < count; i++) {\n    callback(x.getItem(i), y.getItem(i), width.getItem(i), height.getItem(i));\n  }\n}\n\nfunction drawCircles(\n  _ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  callback: (x: number, y: number, r: number) => void,\n) {\n  const x = getArg(args[0], buffers);\n  const y = getArg(args[1], buffers);\n  const radius = getArg(args[2], buffers);\n  const count = Math.min(x.length, y.length, radius.length);\n\n  for (let i = 0; i < count; i++) {\n    callback(x.getItem(i), y.getItem(i), radius.getItem(i));\n  }\n}\n\nfunction drawArcs(\n  _ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  callback: (\n    x: number,\n    y: number,\n    r: number,\n    startAngle: number,\n    endAngle: number,\n    anticlockwise: boolean,\n  ) => void,\n) {\n  const x = getArg(args[0], buffers);\n  const y = getArg(args[1], buffers);\n  const radius = getArg(args[2], buffers);\n  const startAngle = getArg(args[3], buffers);\n  const endAngle = getArg(args[4], buffers);\n  const anticlockwise = getArg(args[5], buffers);\n  const count = Math.min(\n    x.length,\n    y.length,\n    radius.length,\n    startAngle.length,\n    endAngle.length,\n  );\n\n  for (let i = 0; i < count; i++) {\n    callback(\n      x.getItem(i),\n      y.getItem(i),\n      radius.getItem(i),\n      startAngle.getItem(i),\n      endAngle.getItem(i),\n      !!anticlockwise.getItem(i),\n    );\n  }\n}\n\nfunction fillArc(\n  ctx: CanvasRenderingContext2D,\n  x: number,\n  y: number,\n  radius: number,\n  startAngle: number,\n  endAngle: number,\n  anticlockwise: boolean,\n) {\n  ctx.beginPath();\n  ctx.moveTo(x, y);\n  ctx.lineTo(\n    x + radius * Math.cos(startAngle),\n    y + radius * Math.sin(startAngle),\n  );\n  ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);\n  ctx.lineTo(x, y);\n  ctx.fill();\n  ctx.closePath();\n}\n\nfunction strokeArc(\n  ctx: CanvasRenderingContext2D,\n  x: number,\n  y: number,\n  radius: number,\n  startAngle: number,\n  endAngle: number,\n  anticlockwise: boolean,\n) {\n  ctx.beginPath();\n  ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);\n  ctx.stroke();\n  ctx.closePath();\n}\n\nfunction fillCircle(\n  ctx: CanvasRenderingContext2D,\n  x: number,\n  y: number,\n  radius: number,\n) {\n  ctx.beginPath();\n  ctx.arc(x, y, radius, 0, 2 * Math.PI);\n  ctx.fill();\n  ctx.closePath();\n}\n\nfunction strokeCircle(\n  ctx: CanvasRenderingContext2D,\n  x: number,\n  y: number,\n  radius: number,\n) {\n  ctx.beginPath();\n  ctx.arc(x, y, radius, 0, 2 * Math.PI);\n  ctx.stroke();\n  ctx.closePath();\n}\n\nfunction drawPolygonPoints(\n  ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n) {\n  const points = getArg(args[0], buffers);\n  ctx.beginPath();\n  ctx.moveTo(points.getItem(0), points.getItem(1));\n  for (let i = 2; i < points.length; i += 2) {\n    ctx.lineTo(points.getItem(i), points.getItem(i + 1));\n  }\n}\n\nfunction drawPolygonOrLineSegments(\n  ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  fill: boolean,\n  close: boolean,\n) {\n  const numPolygons = args[0] as number;\n  const points = getArg(args[1], buffers);\n  const sizes = getArg(args[2], buffers);\n\n  let start = 0;\n  for (let idx = 0; idx < numPolygons; idx++) {\n    const size = sizes.getItem(idx) * 2;\n    const stop = start + size;\n\n    ctx.beginPath();\n    ctx.moveTo(points.getItem(start), points.getItem(start + 1));\n    for (let idp = start + 2; idp < stop; idp += 2) {\n      ctx.lineTo(points.getItem(idp), points.getItem(idp + 1));\n    }\n    start = stop;\n    if (close) ctx.closePath();\n    fill ? ctx.fill() : ctx.stroke();\n  }\n}\n\nfunction setStyle(ctx: CanvasRenderingContext2D, style: string, fill: boolean) {\n  if (fill) {\n    ctx.fillStyle = style;\n  } else {\n    ctx.strokeStyle = style;\n  }\n}\n\nfunction drawStyledRects(\n  ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  fill: boolean,\n) {\n  const x = getArg(args[0], buffers);\n  const y = getArg(args[1], buffers);\n  const width = getArg(args[2], buffers);\n  const height = getArg(args[3], buffers);\n  const colors = getArg(args[4], buffers);\n  const alpha = getArg(args[5], buffers);\n  const count = Math.min(x.length, y.length, width.length, height.length);\n\n  ctx.save();\n  for (let i = 0; i < count; i++) {\n    const ci = 3 * i;\n    const color = `rgba(${colors.getItem(ci)}, ${colors.getItem(ci + 1)}, ${colors.getItem(ci + 2)}, ${alpha.getItem(i)})`;\n    setStyle(ctx, color, fill);\n    if (fill) {\n      ctx.fillRect(\n        x.getItem(i),\n        y.getItem(i),\n        width.getItem(i),\n        height.getItem(i),\n      );\n    } else {\n      ctx.strokeRect(\n        x.getItem(i),\n        y.getItem(i),\n        width.getItem(i),\n        height.getItem(i),\n      );\n    }\n  }\n  ctx.restore();\n}\n\nfunction drawStyledCircles(\n  ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  fill: boolean,\n) {\n  const x = getArg(args[0], buffers);\n  const y = getArg(args[1], buffers);\n  const radius = getArg(args[2], buffers);\n  const colors = getArg(args[3], buffers);\n  const alpha = getArg(args[4], buffers);\n  const count = Math.min(x.length, y.length, radius.length);\n\n  ctx.save();\n  for (let i = 0; i < count; i++) {\n    const ci = 3 * i;\n    const color = `rgba(${colors.getItem(ci)}, ${colors.getItem(ci + 1)}, ${colors.getItem(ci + 2)}, ${alpha.getItem(i)})`;\n    setStyle(ctx, color, fill);\n    if (fill) {\n      fillCircle(ctx, x.getItem(i), y.getItem(i), radius.getItem(i));\n    } else {\n      strokeCircle(ctx, x.getItem(i), y.getItem(i), radius.getItem(i));\n    }\n  }\n  ctx.restore();\n}\n\nfunction drawStyledArcs(\n  ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  fill: boolean,\n) {\n  const x = getArg(args[0], buffers);\n  const y = getArg(args[1], buffers);\n  const radius = getArg(args[2], buffers);\n  const startAngle = getArg(args[3], buffers);\n  const endAngle = getArg(args[4], buffers);\n  const anticlockwise = getArg(args[5], buffers);\n  const colors = getArg(args[6], buffers);\n  const alpha = getArg(args[7], buffers);\n  const count = Math.min(\n    x.length,\n    y.length,\n    radius.length,\n    startAngle.length,\n    endAngle.length,\n  );\n\n  ctx.save();\n  for (let i = 0; i < count; i++) {\n    const ci = 3 * i;\n    const color = `rgba(${colors.getItem(ci)}, ${colors.getItem(ci + 1)}, ${colors.getItem(ci + 2)}, ${alpha.getItem(i)})`;\n    setStyle(ctx, color, fill);\n    if (fill) {\n      fillArc(\n        ctx,\n        x.getItem(i),\n        y.getItem(i),\n        radius.getItem(i),\n        startAngle.getItem(i),\n        endAngle.getItem(i),\n        !!anticlockwise.getItem(i),\n      );\n    } else {\n      strokeArc(\n        ctx,\n        x.getItem(i),\n        y.getItem(i),\n        radius.getItem(i),\n        startAngle.getItem(i),\n        endAngle.getItem(i),\n        !!anticlockwise.getItem(i),\n      );\n    }\n  }\n  ctx.restore();\n}\n\nfunction drawStyledPolygonOrLineSegments(\n  ctx: CanvasRenderingContext2D,\n  args: unknown[],\n  buffers: DataView[],\n  fill: boolean,\n  close: boolean,\n) {\n  const numPolygons = args[0] as number;\n  const points = getArg(args[1], buffers);\n  const sizes = getArg(args[2], buffers);\n  const colors = getArg(args[3], buffers);\n  const alpha = getArg(args[4], buffers);\n\n  ctx.save();\n  let start = 0;\n  for (let idx = 0; idx < numPolygons; idx++) {\n    const ci = 3 * idx;\n    const color = `rgba(${colors.getItem(ci)}, ${colors.getItem(ci + 1)}, ${colors.getItem(ci + 2)}, ${alpha.getItem(idx)})`;\n    setStyle(ctx, color, fill);\n\n    const size = sizes.getItem(idx) * 2;\n    const stop = start + size;\n\n    ctx.beginPath();\n    ctx.moveTo(points.getItem(start), points.getItem(start + 1));\n    for (let idp = start + 2; idp < stop; idp += 2) {\n      ctx.lineTo(points.getItem(idp), points.getItem(idp + 1));\n    }\n    start = stop;\n    if (close) ctx.closePath();\n    fill ? ctx.fill() : ctx.stroke();\n  }\n  ctx.restore();\n}\n\n// === Command Processor ===\n\nexport interface ProcessCommandsResult {\n  /** Whether a sleep command was encountered (caller should schedule next frame) */\n  sleepMs: number | null;\n  /** The model ID of the canvas that switchCanvas last targeted (null = default/unchanged) */\n  switchedTo: string | null;\n}\n\n/**\n * Process a batch of ipycanvas drawing commands on a canvas 2D context.\n *\n * @param ctx - The 2D rendering context to draw on\n * @param command - The parsed command(s) from the JSON buffer\n * @param buffers - Remaining binary buffers for batch operations\n * @param canvasEl - The canvas element (for clear operations)\n * @param myModelId - This canvas's model ID (for switchCanvas filtering)\n * @param isActive - Whether this canvas is the current draw target\n * @returns Processing result with sleep/switch info\n */\nexport async function processCommands(\n  ctx: CanvasRenderingContext2D,\n  command: unknown,\n  buffers: DataView[],\n  canvasEl: HTMLCanvasElement,\n  myModelId: string,\n  isActive: boolean = true,\n): Promise<ProcessCommandsResult> {\n  const result: ProcessCommandsResult = { sleepMs: null, switchedTo: null };\n\n  // Handle nested command arrays (batched via hold_canvas)\n  if (\n    Array.isArray(command) &&\n    command.length > 0 &&\n    Array.isArray(command[0])\n  ) {\n    let remainingBuffers = buffers;\n    let active = isActive;\n\n    for (const subcommand of command) {\n      let subbuffers: DataView[] = [];\n      const nBuffers = subcommand[2] as number | undefined;\n      if (nBuffers) {\n        subbuffers = remainingBuffers.slice(0, nBuffers);\n        remainingBuffers = remainingBuffers.slice(nBuffers);\n      }\n      const sub = await processCommands(\n        ctx,\n        subcommand,\n        subbuffers,\n        canvasEl,\n        myModelId,\n        active,\n      );\n      if (sub.switchedTo !== null) {\n        active = sub.switchedTo === myModelId;\n        result.switchedTo = sub.switchedTo;\n      }\n      if (sub.sleepMs !== null) {\n        result.sleepMs = sub.sleepMs;\n      }\n    }\n    return result;\n  }\n\n  // Single command: [commandIndex, args, nBuffers?]\n  const cmd = command as [number, unknown[]?, number?];\n  const name = COMMANDS[cmd[0]];\n  const args: unknown[] = cmd[1] ?? [];\n\n  // Handle switchCanvas\n  if (name === \"switchCanvas\") {\n    const targetRef = args[0] as string;\n    // Extract model ID from IPY_MODEL_ reference\n    const targetId =\n      typeof targetRef === \"string\" && targetRef.startsWith(\"IPY_MODEL_\")\n        ? targetRef.slice(10)\n        : targetRef;\n    result.switchedTo = targetId;\n    return result;\n  }\n\n  // Skip commands not targeted at this canvas\n  if (!isActive) {\n    return result;\n  }\n\n  switch (name) {\n    // --- Simple drawing ---\n    case \"fillRect\":\n      ctx.fillRect(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n      );\n      break;\n    case \"strokeRect\":\n      ctx.strokeRect(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n      );\n      break;\n    case \"clearRect\":\n      ctx.clearRect(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n      );\n      break;\n    case \"fillCircle\":\n      fillCircle(ctx, args[0] as number, args[1] as number, args[2] as number);\n      break;\n    case \"strokeCircle\":\n      strokeCircle(\n        ctx,\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n      );\n      break;\n    case \"fillArc\":\n      fillArc(\n        ctx,\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as boolean,\n      );\n      break;\n    case \"strokeArc\":\n      strokeArc(\n        ctx,\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as boolean,\n      );\n      break;\n    case \"strokeLine\": {\n      ctx.beginPath();\n      ctx.moveTo(args[0] as number, args[1] as number);\n      ctx.lineTo(args[2] as number, args[3] as number);\n      ctx.stroke();\n      ctx.closePath();\n      break;\n    }\n\n    // --- Batch drawing ---\n    case \"fillRects\":\n      drawRects(ctx, args, buffers, (x, y, w, h) => ctx.fillRect(x, y, w, h));\n      break;\n    case \"strokeRects\":\n      drawRects(ctx, args, buffers, (x, y, w, h) => ctx.strokeRect(x, y, w, h));\n      break;\n    case \"fillCircles\":\n      drawCircles(ctx, args, buffers, (x, y, r) => fillCircle(ctx, x, y, r));\n      break;\n    case \"strokeCircles\":\n      drawCircles(ctx, args, buffers, (x, y, r) => strokeCircle(ctx, x, y, r));\n      break;\n    case \"fillArcs\":\n      drawArcs(ctx, args, buffers, (x, y, r, sa, ea, ac) =>\n        fillArc(ctx, x, y, r, sa, ea, ac),\n      );\n      break;\n    case \"strokeArcs\":\n      drawArcs(ctx, args, buffers, (x, y, r, sa, ea, ac) =>\n        strokeArc(ctx, x, y, r, sa, ea, ac),\n      );\n      break;\n    case \"strokeLines\": {\n      const points = getArg(args[0], buffers);\n      ctx.beginPath();\n      ctx.moveTo(points.getItem(0), points.getItem(1));\n      for (let i = 2; i < points.length; i += 2) {\n        ctx.lineTo(points.getItem(i), points.getItem(i + 1));\n      }\n      ctx.stroke();\n      ctx.closePath();\n      break;\n    }\n    case \"fillPolygon\": {\n      drawPolygonPoints(ctx, args, buffers);\n      ctx.closePath();\n      ctx.fill();\n      break;\n    }\n    case \"strokePolygon\": {\n      drawPolygonPoints(ctx, args, buffers);\n      ctx.closePath();\n      ctx.stroke();\n      break;\n    }\n    case \"fillPolygons\":\n      drawPolygonOrLineSegments(ctx, args, buffers, true, true);\n      break;\n    case \"strokePolygons\":\n      drawPolygonOrLineSegments(ctx, args, buffers, false, true);\n      break;\n    case \"strokeLineSegments\":\n      drawPolygonOrLineSegments(ctx, args, buffers, false, false);\n      break;\n\n    // --- Styled batch ---\n    case \"fillStyledRects\":\n      drawStyledRects(ctx, args, buffers, true);\n      break;\n    case \"strokeStyledRects\":\n      drawStyledRects(ctx, args, buffers, false);\n      break;\n    case \"fillStyledCircles\":\n      drawStyledCircles(ctx, args, buffers, true);\n      break;\n    case \"strokeStyledCircles\":\n      drawStyledCircles(ctx, args, buffers, false);\n      break;\n    case \"fillStyledArcs\":\n      drawStyledArcs(ctx, args, buffers, true);\n      break;\n    case \"strokeStyledArcs\":\n      drawStyledArcs(ctx, args, buffers, false);\n      break;\n    case \"fillStyledPolygons\":\n      drawStyledPolygonOrLineSegments(ctx, args, buffers, true, true);\n      break;\n    case \"strokeStyledPolygons\":\n      drawStyledPolygonOrLineSegments(ctx, args, buffers, false, true);\n      break;\n    case \"strokeStyledLineSegments\":\n      drawStyledPolygonOrLineSegments(ctx, args, buffers, false, false);\n      break;\n\n    // --- Path operations ---\n    case \"beginPath\":\n      ctx.beginPath();\n      break;\n    case \"closePath\":\n      ctx.closePath();\n      break;\n    case \"stroke\":\n      ctx.stroke();\n      break;\n    case \"fill\":\n      ctx.fill();\n      break;\n    case \"moveTo\":\n      ctx.moveTo(args[0] as number, args[1] as number);\n      break;\n    case \"lineTo\":\n      ctx.lineTo(args[0] as number, args[1] as number);\n      break;\n    case \"rect\":\n      ctx.rect(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n      );\n      break;\n    case \"arc\":\n      ctx.arc(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as boolean,\n      );\n      break;\n    case \"ellipse\":\n      ctx.ellipse(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as number,\n        args[6] as number,\n        args[7] as boolean,\n      );\n      break;\n    case \"arcTo\":\n      ctx.arcTo(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n      );\n      break;\n    case \"quadraticCurveTo\":\n      ctx.quadraticCurveTo(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n      );\n      break;\n    case \"bezierCurveTo\":\n      ctx.bezierCurveTo(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as number,\n      );\n      break;\n    case \"clip\":\n      ctx.clip();\n      break;\n\n    // --- Text ---\n    case \"fillText\":\n      ctx.fillText(args[0] as string, args[1] as number, args[2] as number);\n      break;\n    case \"strokeText\":\n      ctx.strokeText(args[0] as string, args[1] as number, args[2] as number);\n      break;\n\n    // --- Transform ---\n    case \"translate\":\n      ctx.translate(args[0] as number, args[1] as number);\n      break;\n    case \"rotate\":\n      ctx.rotate(args[0] as number);\n      break;\n    case \"scale\":\n      ctx.scale(args[0] as number, args[1] as number);\n      break;\n    case \"transform\":\n      ctx.transform(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as number,\n      );\n      break;\n    case \"setTransform\":\n      ctx.setTransform(\n        args[0] as number,\n        args[1] as number,\n        args[2] as number,\n        args[3] as number,\n        args[4] as number,\n        args[5] as number,\n      );\n      break;\n    case \"resetTransform\":\n      ctx.resetTransform();\n      break;\n\n    // --- State ---\n    case \"save\":\n      ctx.save();\n      break;\n    case \"restore\":\n      ctx.restore();\n      break;\n    case \"set\": {\n      const attrIndex = args[0] as number;\n      const attrName = CANVAS_ATTRS[attrIndex];\n      if (attrName) {\n        (ctx as unknown as Record<string, unknown>)[attrName] = args[1];\n      }\n      break;\n    }\n    case \"setLineDash\":\n      ctx.setLineDash(args[0] as number[]);\n      break;\n\n    // --- Utility ---\n    case \"clear\":\n      ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);\n      break;\n    case \"sleep\":\n      result.sleepMs = args[0] as number;\n      await new Promise((resolve) => setTimeout(resolve, result.sleepMs!));\n      break;\n\n    // --- Deferred (require cross-widget model resolution) ---\n    case \"drawImage\":\n    case \"putImageData\":\n    case \"strokePath\":\n    case \"fillPath\":\n      // These require resolving IPY_MODEL_ references to other widget models\n      // which is not yet supported. Silently skip.\n      break;\n\n    default:\n      // Fallback: try to call the method directly on the context\n      if (\n        name &&\n        typeof (ctx as unknown as Record<string, unknown>)[name] === \"function\"\n      ) {\n        (ctx as unknown as Record<string, (...a: unknown[]) => void>)[name](\n          ...args,\n        );\n      }\n      break;\n  }\n\n  return result;\n}\n",
      "type": "registry:lib",
      "target": "components/widgets/ipycanvas/ipycanvas-commands.ts"
    }
  ],
  "type": "registry:component"
}