{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "xlsx-editor",
  "title": "Excel Editor",
  "description": "An experimental XLSX editor with formula input, undo and redo, sheet controls, export, and live workbook mutation.",
  "dependencies": [
    "@extend-ai/react-xlsx@0.16.1"
  ],
  "registryDependencies": [
    "utils",
    "@extend/xlsx-viewer",
    "button",
    "button-group",
    "dropdown-menu",
    "input",
    "select",
    "separator",
    "toggle-group",
    "tooltip",
    "@extend/document-color-picker"
  ],
  "files": [
    {
      "path": "components/extend/xlsx-editor.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport {\n  useXlsxViewer,\n  useXlsxViewerController,\n  useXlsxViewerEditing,\n  useXlsxViewerSelection,\n  useXlsxViewerZoom,\n  XlsxViewer,\n  XlsxViewerProvider,\n  type XlsxCellAlignmentInput,\n  type XlsxCellBorderEdgeInput,\n  type XlsxCellRange,\n  type XlsxCellStyleInput,\n  type XlsxSheetData,\n  type XlsxTableHeaderMenuRenderProps,\n} from \"@extend-ai/react-xlsx\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  ButtonGroup as Group,\n  ButtonGroupSeparator as GroupSeparator,\n  ButtonGroupText as GroupText,\n} from \"@/components/ui/button-group\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuSub,\n  DropdownMenuSubContent,\n  DropdownMenuSubTrigger,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport {\n  ColorPicker,\n  ColorPickerPanel,\n} from \"@/components/extend/document-color-picker\"\nimport {\n  renderXlsxScroller,\n  WorkbookSheetTabs,\n  WorkbookTableHeaderMenu,\n} from \"@/components/extend/xlsx-viewer\"\nimport { IconPlaceholder } from \"@/components/icon-placeholder\"\n\nfunction BorderAll01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Grid2x2\"\n      tabler=\"IconBorderAll\"\n      hugeicons=\"BorderAll01Icon\"\n      phosphor=\"GridFourIcon\"\n      remixicon=\"RiGridLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderBottom01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelBottom\"\n      tabler=\"IconBorderBottom\"\n      hugeicons=\"BorderBottom01Icon\"\n      phosphor=\"SquareHalfBottomIcon\"\n      remixicon=\"RiLayoutBottomLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderLeft01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelLeft\"\n      tabler=\"IconBorderLeft\"\n      hugeicons=\"BorderLeft01Icon\"\n      phosphor=\"SquareHalfIcon\"\n      remixicon=\"RiLayoutLeftLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderNone01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"SquareDashed\"\n      tabler=\"IconBorderNone\"\n      hugeicons=\"BorderNone01Icon\"\n      phosphor=\"SelectionIcon\"\n      remixicon=\"RiCheckboxBlankLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderRight01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelRight\"\n      tabler=\"IconBorderRight\"\n      hugeicons=\"BorderRight01Icon\"\n      phosphor=\"SquareHalfIcon\"\n      remixicon=\"RiLayoutRightLine\"\n      {...props}\n    />\n  )\n}\nfunction BorderTop01Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PanelTop\"\n      tabler=\"IconBorderTop\"\n      hugeicons=\"BorderTop01Icon\"\n      phosphor=\"AlignTopSimpleIcon\"\n      remixicon=\"RiLayoutTopLine\"\n      {...props}\n    />\n  )\n}\nfunction Calendar03Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Calendar\"\n      tabler=\"IconCalendar\"\n      hugeicons=\"Calendar03Icon\"\n      phosphor=\"CalendarIcon\"\n      remixicon=\"RiCalendarLine\"\n      {...props}\n    />\n  )\n}\nfunction DollarCircleGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"CircleDollarSign\"\n      tabler=\"IconCurrencyDollar\"\n      hugeicons=\"DollarCircleIcon\"\n      phosphor=\"CurrencyCircleDollarIcon\"\n      remixicon=\"RiMoneyDollarCircleLine\"\n      {...props}\n    />\n  )\n}\nfunction Moon02Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Moon\"\n      tabler=\"IconMoon\"\n      hugeicons=\"Moon02Icon\"\n      phosphor=\"MoonIcon\"\n      remixicon=\"RiMoonLine\"\n      {...props}\n    />\n  )\n}\nfunction PaintBucketGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"PaintBucket\"\n      tabler=\"IconPaint\"\n      hugeicons=\"PaintBucketIcon\"\n      phosphor=\"PaintBucketIcon\"\n      remixicon=\"RiPaintFill\"\n      {...props}\n    />\n  )\n}\nfunction PercentGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Percent\"\n      tabler=\"IconPercentage\"\n      hugeicons=\"PercentIcon\"\n      phosphor=\"PercentIcon\"\n      remixicon=\"RiPercentLine\"\n      {...props}\n    />\n  )\n}\nfunction Sun03Glyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Sun\"\n      tabler=\"IconSun\"\n      hugeicons=\"Sun03Icon\"\n      phosphor=\"SunIcon\"\n      remixicon=\"RiSunLine\"\n      {...props}\n    />\n  )\n}\nfunction TextColorGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Baseline\"\n      tabler=\"IconTextColor\"\n      hugeicons=\"TextColorIcon\"\n      phosphor=\"TextAaIcon\"\n      remixicon=\"RiFontColor\"\n      {...props}\n    />\n  )\n}\nfunction TextNumberSignGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"Hash\"\n      tabler=\"IconHash\"\n      hugeicons=\"TextNumberSignIcon\"\n      phosphor=\"HashIcon\"\n      remixicon=\"RiHashtag\"\n      {...props}\n    />\n  )\n}\nfunction TextWrapGlyph(props: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"WrapText\"\n      tabler=\"IconTextWrap\"\n      hugeicons=\"TextWrapIcon\"\n      phosphor=\"ArrowElbowDownLeftIcon\"\n      remixicon=\"RiTextWrap\"\n      {...props}\n    />\n  )\n}\nconst XLSX_LOADING_INDICATOR_DELAY_MS = 300\nconst XLSX_EDITOR_READ_ONLY_THRESHOLD_BYTES = 5 * 1024 * 1024\nconst XLSX_DROPDOWN_Z_INDEX_CLASS = \"z-40\"\nconst ZOOM_OPTIONS = [50, 75, 100, 125, 150, 200, 400] as const\nconst DEFAULT_FONT_FAMILY = \"Arial\"\nconst FONT_FAMILIES = [\n  \"Arial\",\n  \"Times New Roman\",\n  \"Georgia\",\n  \"Helvetica\",\n  \"Courier New\",\n] as const\nconst FONT_SIZE_OPTIONS = [\n  8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48,\n] as const\nconst DEFAULT_TEXT_COLOR = \"#111827\"\nconst DEFAULT_FILL_COLOR = \"#fde68a\"\nconst DEFAULT_BORDER_COLOR = \"#111827\"\nconst XLSX_EDITOR_SELECT_CHROME_CLASS =\n  \"shadow-none before:shadow-none not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-none dark:not-data-disabled:not-focus-visible:not-aria-invalid:not-data-pressed:before:shadow-none\"\nconst XLSX_EDITOR_FORMULA_INPUT_CHROME_CLASS =\n  \"has-disabled:opacity-100 dark:has-disabled:before:shadow-[0_-1px_--theme(--color-white/6%)]\"\ntype UploadedWorkbook = {\n  buffer: ArrayBuffer\n  fileName: string\n  identity: string\n}\ntype NumberFormatOption = {\n  formatString?: string\n  formatType: NonNullable<XlsxCellStyleInput[\"numberFormat\"]>[\"formatType\"]\n  icon: React.ComponentType<InlineRegistryIconProps>\n  id?: number\n  label: string\n  value: string\n}\ntype BorderOption = {\n  action: XlsxBorderAction\n  icon: React.ComponentType<InlineRegistryIconProps>\n  label: string\n}\ntype XlsxResolvedCellStyle = XlsxSheetData[\"styleById\"][number]\ntype XlsxResolvedStyleGroup = Record<string, unknown>\ntype HorizontalAlignment = NonNullable<XlsxCellAlignmentInput[\"horizontal\"]>\ntype VerticalAlignment = NonNullable<XlsxCellAlignmentInput[\"vertical\"]>\ntype HorizontalAlignmentToggleValue = Extract<\n  HorizontalAlignment,\n  \"left\" | \"center\" | \"right\"\n>\ntype VerticalAlignmentToggleValue = Extract<\n  VerticalAlignment,\n  \"top\" | \"center\" | \"bottom\"\n>\ntype XlsxFontToggleValue = \"bold\" | \"italic\" | \"underline\" | \"strikethrough\"\ntype XlsxMergeRegion = XlsxCellRange\ntype XlsxBorderEdgeKey = \"bottom\" | \"left\" | \"top\" | \"right\"\ntype XlsxBorderAction = \"all\" | \"outside\" | \"none\" | XlsxBorderEdgeKey\ntype XlsxWorksheetWithMergeRegions = {\n  mergedRegions?: unknown[]\n  unmergeCells?: (range: string) => boolean\n}\nconst NUMBER_FORMAT_OPTIONS: NumberFormatOption[] = [\n  {\n    formatType: \"general\",\n    icon: TextNumberSignGlyph,\n    label: \"General\",\n    value: \"General\",\n  },\n  {\n    formatString: \"0.00\",\n    formatType: \"custom\",\n    icon: TextNumberSignGlyph,\n    label: \"Number\",\n    value: \"Number\",\n  },\n  {\n    formatString: \"$#,##0.00\",\n    formatType: \"custom\",\n    icon: DollarCircleGlyph,\n    label: \"Currency\",\n    value: \"Currency\",\n  },\n  {\n    formatString: \"0.00%\",\n    formatType: \"custom\",\n    icon: PercentGlyph,\n    label: \"Percent\",\n    value: \"Percent\",\n  },\n  {\n    formatString: \"m/d/yyyy\",\n    formatType: \"custom\",\n    icon: Calendar03Glyph,\n    label: \"Date\",\n    value: \"Date\",\n  },\n]\nconst GENERAL_NUMBER_FORMAT_VALUE = \"General\"\nconst BUILTIN_NUMBER_FORMAT_VALUE_PREFIX = \"builtin:\"\nfunction formatWorkbookName(fileName: string | undefined, url: string) {\n  if (fileName?.trim()) return fileName\n  const pathname = url.split(\"?\")[0] ?? \"\"\n  const rawName = pathname.split(\"/\").pop() ?? \"workbook.xlsx\"\n  try {\n    return decodeURIComponent(rawName)\n  } catch {\n    return rawName\n  }\n}\nfunction useDelayedLoadingIndicator(isLoading: boolean, delayMs: number) {\n  const [showSpinner, setShowSpinner] = React.useState(false)\n  const [previousIsLoading, setPreviousIsLoading] = React.useState(isLoading)\n  if (previousIsLoading !== isLoading) {\n    setPreviousIsLoading(isLoading)\n    setShowSpinner(false)\n  }\n  React.useEffect(() => {\n    if (!isLoading) return\n    const timeoutId = window.setTimeout(() => {\n      setShowSpinner(true)\n    }, delayMs)\n    return () => window.clearTimeout(timeoutId)\n  }, [delayMs, isLoading])\n  return isLoading && showSpinner\n}\nfunction normalizeHexColor(value: string, fallback = DEFAULT_TEXT_COLOR) {\n  const trimmed = value.trim()\n  const threeDigit = /^#?([0-9a-f]{3})$/i.exec(trimmed)\n  if (threeDigit?.[1]) {\n    const [red, green, blue] = threeDigit[1].split(\"\")\n    return `#${red}${red}${green}${green}${blue}${blue}`.toLowerCase()\n  }\n  const sixDigit = /^#?([0-9a-f]{6})$/i.exec(trimmed)\n  if (sixDigit?.[1]) {\n    return `#${sixDigit[1].toLowerCase()}`\n  }\n  return fallback\n}\nfunction toXlsxRgbColor(value: string) {\n  return {\n    colorType: \"rgb\" as const,\n    hex: normalizeHexColor(value).slice(1).toUpperCase(),\n  }\n}\nfunction asResolvedStyleGroup(\n  value: unknown\n): XlsxResolvedStyleGroup | undefined {\n  return value && typeof value === \"object\" && !Array.isArray(value)\n    ? (value as XlsxResolvedStyleGroup)\n    : undefined\n}\nfunction mergeResolvedCellStyle(\n  base: XlsxResolvedCellStyle | null | undefined,\n  overlay: XlsxResolvedCellStyle | null | undefined\n): XlsxResolvedCellStyle | null {\n  if (!base && !overlay) return null\n  const nextStyle = { ...(base ?? {}), ...(overlay ?? {}) }\n  const baseFont = asResolvedStyleGroup(base?.font)\n  const overlayFont = asResolvedStyleGroup(overlay?.font)\n  if (baseFont || overlayFont) {\n    nextStyle.font = overlayFont ?? baseFont\n  }\n  return nextStyle\n}\nfunction resolveInheritedCellStyle(\n  sheet: XlsxSheetData | null,\n  row: number,\n  col: number\n): XlsxResolvedCellStyle | null {\n  if (!sheet) return null\n  const colStyleId = sheet.colStyleIds[col]\n  const rowStyleId = sheet.rowStyleIds[row]\n  const colStyle =\n    colStyleId !== undefined ? sheet.styleById[colStyleId] : undefined\n  const rowStyle =\n    rowStyleId !== undefined ? sheet.styleById[rowStyleId] : undefined\n  return mergeResolvedCellStyle(colStyle, rowStyle)\n}\nfunction getFontToggleValuesFromStyle(\n  style: XlsxResolvedCellStyle | null\n): XlsxFontToggleValue[] {\n  const font = asResolvedStyleGroup(style?.font)\n  const values: XlsxFontToggleValue[] = []\n  if (font?.bold === true) values.push(\"bold\")\n  if (font?.italic === true) values.push(\"italic\")\n  if (font?.underline && font.underline !== \"none\") values.push(\"underline\")\n  if (font?.strikethrough === true) values.push(\"strikethrough\")\n  return values\n}\nfunction getFontFamilyValue(style: XlsxResolvedCellStyle | null) {\n  const font = asResolvedStyleGroup(style?.font)\n  const fontName = font?.name\n  return typeof fontName === \"string\" && fontName.trim()\n    ? fontName.trim()\n    : undefined\n}\nfunction getHorizontalAlignmentValue(\n  style: XlsxResolvedCellStyle | null\n): HorizontalAlignmentToggleValue[] {\n  const alignment = asResolvedStyleGroup(style?.alignment)\n  const horizontal = alignment?.horizontal\n  return horizontal === \"left\" ||\n    horizontal === \"center\" ||\n    horizontal === \"right\"\n    ? [horizontal]\n    : []\n}\nfunction getVerticalAlignmentValue(\n  style: XlsxResolvedCellStyle | null\n): VerticalAlignmentToggleValue[] {\n  const alignment = asResolvedStyleGroup(style?.alignment)\n  const vertical = alignment?.vertical\n  if (vertical === undefined || vertical === null) return [\"bottom\"]\n  return vertical === \"top\" || vertical === \"center\" || vertical === \"bottom\"\n    ? [vertical]\n    : []\n}\nfunction getNumberFormatValue(style: XlsxResolvedCellStyle | null) {\n  const numberFormat = asResolvedStyleGroup(style?.numberFormat)\n  if (!numberFormat) return GENERAL_NUMBER_FORMAT_VALUE\n  const formatType = numberFormat.formatType\n  const formatString =\n    typeof numberFormat.formatString === \"string\"\n      ? numberFormat.formatString.trim()\n      : \"\"\n  const id = getNumericValue(numberFormat.id)\n  if (formatString) {\n    const matchingOption = NUMBER_FORMAT_OPTIONS.find(\n      (option) => option.formatString === formatString\n    )\n    return matchingOption?.value ?? formatString\n  }\n  if (formatType === \"builtin\" && id !== undefined) {\n    const matchingOption = NUMBER_FORMAT_OPTIONS.find(\n      (option) => option.id === id\n    )\n    if (matchingOption) return matchingOption.value\n    return `${BUILTIN_NUMBER_FORMAT_VALUE_PREFIX}${id}`\n  }\n  if (formatType === \"general\") return GENERAL_NUMBER_FORMAT_VALUE\n  return GENERAL_NUMBER_FORMAT_VALUE\n}\nfunction getNumberFormatLabel(value: string) {\n  if (value === GENERAL_NUMBER_FORMAT_VALUE) return \"General\"\n  const matchingOption = NUMBER_FORMAT_OPTIONS.find(\n    (option) => option.value === value\n  )\n  if (matchingOption) return matchingOption.label\n  if (value.startsWith(BUILTIN_NUMBER_FORMAT_VALUE_PREFIX)) {\n    return `Builtin ${value.slice(BUILTIN_NUMBER_FORMAT_VALUE_PREFIX.length)}`\n  }\n  return value\n}\nfunction getNumberFormatIcon(value: string) {\n  return (\n    NUMBER_FORMAT_OPTIONS.find((option) => option.value === value)?.icon ??\n    TextNumberSignGlyph\n  )\n}\nfunction createNumberFormatStyle(value: string): XlsxCellStyleInput {\n  const matchingOption = NUMBER_FORMAT_OPTIONS.find(\n    (option) => option.value === value\n  )\n  if (matchingOption) {\n    return {\n      numberFormat: {\n        formatString: matchingOption.formatString,\n        formatType: matchingOption.formatType,\n        id: matchingOption.id,\n      },\n    }\n  }\n  if (value.startsWith(BUILTIN_NUMBER_FORMAT_VALUE_PREFIX)) {\n    const id = Number(value.slice(BUILTIN_NUMBER_FORMAT_VALUE_PREFIX.length))\n    if (Number.isFinite(id)) {\n      return {\n        numberFormat: {\n          formatType: \"builtin\",\n          id: Math.max(0, Math.trunc(id)),\n        },\n      }\n    }\n  }\n  return {\n    numberFormat: {\n      formatString: value,\n      formatType: \"custom\",\n    },\n  }\n}\nfunction normalizeCellRange(range: XlsxCellRange): XlsxCellRange {\n  return {\n    start: {\n      row: Math.min(range.start.row, range.end.row),\n      col: Math.min(range.start.col, range.end.col),\n    },\n    end: {\n      row: Math.max(range.start.row, range.end.row),\n      col: Math.max(range.start.col, range.end.col),\n    },\n  }\n}\nfunction getNumericValue(value: unknown) {\n  return typeof value === \"number\" && Number.isFinite(value)\n    ? Math.max(0, Math.trunc(value))\n    : undefined\n}\nfunction getWorksheetMergeRegions(\n  worksheet: XlsxWorksheetWithMergeRegions | null\n): XlsxMergeRegion[] {\n  if (!Array.isArray(worksheet?.mergedRegions)) return []\n  return worksheet.mergedRegions.flatMap((entry) => {\n    if (!entry || typeof entry !== \"object\") return []\n    const region = entry as Record<string, unknown>\n    const start = asResolvedStyleGroup(region.start)\n    const end = asResolvedStyleGroup(region.end)\n    const startRow = getNumericValue(region.startRow ?? start?.row)\n    const startCol = getNumericValue(region.startCol ?? start?.col)\n    const endRow = getNumericValue(region.endRow ?? end?.row)\n    const endCol = getNumericValue(region.endCol ?? end?.col)\n    if (\n      startRow === undefined ||\n      startCol === undefined ||\n      endRow === undefined ||\n      endCol === undefined\n    ) {\n      return []\n    }\n    return [\n      normalizeCellRange({\n        start: { row: startRow, col: startCol },\n        end: { row: endRow, col: endCol },\n      }),\n    ]\n  })\n}\nfunction rangesIntersect(\n  firstRange: XlsxCellRange,\n  secondRange: XlsxCellRange\n) {\n  const first = normalizeCellRange(firstRange)\n  const second = normalizeCellRange(secondRange)\n  return (\n    first.start.row <= second.end.row &&\n    first.end.row >= second.start.row &&\n    first.start.col <= second.end.col &&\n    first.end.col >= second.start.col\n  )\n}\nfunction isSingleCellRange(range: XlsxCellRange) {\n  const normalizedRange = normalizeCellRange(range)\n  return (\n    normalizedRange.start.row === normalizedRange.end.row &&\n    normalizedRange.start.col === normalizedRange.end.col\n  )\n}\nfunction getCellColumnLabel(col: number) {\n  let label = \"\"\n  let value = col\n  do {\n    label = String.fromCharCode(65 + (value % 26)) + label\n    value = Math.floor(value / 26) - 1\n  } while (value >= 0)\n  return label\n}\nfunction cellAddressToA1(cell: XlsxCellRange[\"start\"]) {\n  return `${getCellColumnLabel(cell.col)}${cell.row + 1}`\n}\nfunction rangeToA1(range: XlsxCellRange) {\n  const normalizedRange = normalizeCellRange(range)\n  return `${cellAddressToA1(normalizedRange.start)}:${cellAddressToA1(normalizedRange.end)}`\n}\nfunction getRangeKey(range: XlsxCellRange) {\n  const normalizedRange = normalizeCellRange(range)\n  return `${normalizedRange.start.row}:${normalizedRange.start.col}:${normalizedRange.end.row}:${normalizedRange.end.col}`\n}\nfunction createBorderEdge(\n  color = DEFAULT_BORDER_COLOR\n): XlsxCellBorderEdgeInput {\n  return {\n    color: toXlsxRgbColor(color),\n    style: \"thin\",\n  }\n}\nfunction createBorderPatch(\n  entries: Partial<Record<XlsxBorderEdgeKey, XlsxCellBorderEdgeInput>>\n): XlsxCellStyleInput {\n  return {\n    border: entries,\n  }\n}\nfunction createNoBorderPatch(): XlsxCellStyleInput {\n  return {\n    border: {\n      bottom: { style: \"none\" },\n      left: { style: \"none\" },\n      right: { style: \"none\" },\n      top: { style: \"none\" },\n    },\n  }\n}\nfunction createBorderOptions(): BorderOption[] {\n  return [\n    {\n      action: \"bottom\",\n      icon: BorderBottom01Glyph,\n      label: \"Bottom border\",\n    },\n    {\n      action: \"left\",\n      icon: BorderLeft01Glyph,\n      label: \"Left border\",\n    },\n    {\n      action: \"top\",\n      icon: BorderTop01Glyph,\n      label: \"Top border\",\n    },\n    {\n      action: \"right\",\n      icon: BorderRight01Glyph,\n      label: \"Right border\",\n    },\n    {\n      action: \"all\",\n      icon: BorderAll01Glyph,\n      label: \"All borders\",\n    },\n    {\n      action: \"outside\",\n      icon: BorderAll01Glyph,\n      label: \"Outside border\",\n    },\n    {\n      action: \"none\",\n      icon: BorderNone01Glyph,\n      label: \"No border\",\n    },\n  ]\n}\nfunction getBorderEdgeRange(\n  range: XlsxCellRange,\n  edgeKey: XlsxBorderEdgeKey\n): XlsxCellRange {\n  const normalizedRange = normalizeCellRange(range)\n  if (edgeKey === \"top\") {\n    return {\n      start: normalizedRange.start,\n      end: { row: normalizedRange.start.row, col: normalizedRange.end.col },\n    }\n  }\n  if (edgeKey === \"bottom\") {\n    return {\n      start: { row: normalizedRange.end.row, col: normalizedRange.start.col },\n      end: normalizedRange.end,\n    }\n  }\n  if (edgeKey === \"left\") {\n    return {\n      start: normalizedRange.start,\n      end: { row: normalizedRange.end.row, col: normalizedRange.start.col },\n    }\n  }\n  return {\n    start: { row: normalizedRange.start.row, col: normalizedRange.end.col },\n    end: normalizedRange.end,\n  }\n}\nfunction ToolbarTooltip({\n  label,\n  children,\n}: {\n  label: string\n  children: React.ReactNode\n}) {\n  return (\n    <Tooltip>\n      <TooltipTrigger\n        render={<span className=\"inline-flex\">{children}</span>}\n      ></TooltipTrigger>\n      <TooltipContent side=\"bottom\">{label}</TooltipContent>\n    </Tooltip>\n  )\n}\nfunction EditorLoadingSurface({\n  showSpinner = true,\n}: {\n  showSpinner?: boolean\n}) {\n  return (\n    <div className=\"grid h-full min-h-52 w-full min-w-full place-items-center bg-transparent\">\n      {showSpinner ? <InlineSpinner className=\"size-4\" /> : null}\n    </div>\n  )\n}\nfunction ToolbarIconButton({\n  active,\n  children,\n  label,\n  ...props\n}: React.ComponentProps<typeof Button> & {\n  active?: boolean\n  label: string\n}) {\n  return (\n    <ToolbarTooltip label={label}>\n      <Button\n        type=\"button\"\n        variant={active ? \"secondary\" : \"ghost\"}\n        size=\"icon-sm\"\n        aria-label={label}\n        data-pressed={active ? \"\" : undefined}\n        {...props}\n      >\n        {children}\n      </Button>\n    </ToolbarTooltip>\n  )\n}\nfunction ToolbarSeparator() {\n  return <Separator orientation=\"vertical\" className=\"mx-1 h-4 self-center\" />\n}\nfunction StyleDropdownItem({\n  children,\n  icon,\n  onClick,\n}: {\n  children: React.ReactNode\n  icon: React.ComponentType<InlineRegistryIconProps>\n  onClick: () => void\n}) {\n  const IconComponent = icon\n  return (\n    <DropdownMenuItem onClick={onClick}>\n      <IconComponent className=\"size-4\" />\n      {children}\n    </DropdownMenuItem>\n  )\n}\nfunction MergeCellsMenu({\n  canMerge,\n  canMergeAcross,\n  canUnmerge,\n  disabled,\n  isMerged,\n  onMergeAcross,\n  onMergeAndCenter,\n  onMergeCells,\n  onUnmergeCells,\n}: {\n  canMerge: boolean\n  canMergeAcross: boolean\n  canUnmerge: boolean\n  disabled: boolean\n  isMerged: boolean\n  onMergeAcross: () => void\n  onMergeAndCenter: () => void\n  onMergeCells: () => void\n  onUnmergeCells: () => void\n}) {\n  return (\n    <DropdownMenu>\n      <div\n        className={cn(\n          \"inline-flex shrink-0 overflow-hidden rounded-lg border border-transparent\"\n        )}\n      >\n        <ToolbarTooltip\n          label={isMerged ? \"Unmerge cells from the menu\" : \"Merge & Center\"}\n        >\n          <Button\n            type=\"button\"\n            variant={isMerged ? \"secondary\" : \"ghost\"}\n            size=\"sm\"\n            aria-label=\"Merge & Center\"\n            className=\"h-7 rounded-r-none border-0 px-2.5 before:rounded-r-none\"\n            data-pressed={isMerged ? \"\" : undefined}\n            disabled={disabled || !canMerge}\n            onClick={onMergeAndCenter}\n          >\n            <IconPlaceholder\n              lucide=\"TableCellsMerge\"\n              tabler=\"IconArrowMerge\"\n              hugeicons=\"PathfinderMergeIcon\"\n              phosphor=\"ArrowsMergeIcon\"\n              remixicon=\"RiMergeCellsVertical\"\n              className=\"size-4\"\n            />\n            <span className=\"hidden text-xs font-medium lg:inline\">\n              Merge & Center\n            </span>\n          </Button>\n        </ToolbarTooltip>\n        <DropdownMenuTrigger\n          render={\n            <Button\n              type=\"button\"\n              variant={isMerged ? \"secondary\" : \"ghost\"}\n              size=\"icon-sm\"\n              aria-label=\"Merge cells options\"\n              className=\"h-7 w-6 rounded-l-none border-0 px-0 before:rounded-l-none\"\n              data-pressed={isMerged ? \"\" : undefined}\n              disabled={disabled}\n            >\n              <IconPlaceholder\n                lucide=\"ChevronDown\"\n                tabler=\"IconChevronDown\"\n                hugeicons=\"ChevronDownIcon\"\n                phosphor=\"CaretDownIcon\"\n                remixicon=\"RiArrowDownSLine\"\n                className=\"size-3.5 opacity-100\"\n              />\n            </Button>\n          }\n        ></DropdownMenuTrigger>\n      </div>\n      <DropdownMenuContent\n        align=\"start\"\n        className={cn(\"w-56\", XLSX_DROPDOWN_Z_INDEX_CLASS)}\n      >\n        <DropdownMenuItem disabled={!canMerge} onClick={onMergeAndCenter}>\n          <IconPlaceholder\n            lucide=\"TableCellsMerge\"\n            tabler=\"IconArrowMerge\"\n            hugeicons=\"PathfinderMergeIcon\"\n            phosphor=\"ArrowsMergeIcon\"\n            remixicon=\"RiMergeCellsVertical\"\n            className=\"size-4\"\n          />\n          Merge & Center\n        </DropdownMenuItem>\n        <DropdownMenuItem disabled={!canMergeAcross} onClick={onMergeAcross}>\n          <IconPlaceholder\n            lucide=\"TableRowsSplit\"\n            tabler=\"IconArrowsSplit\"\n            hugeicons=\"TableRowsSplitIcon\"\n            phosphor=\"ArrowsSplitIcon\"\n            remixicon=\"RiSplitCellsVertical\"\n            className=\"size-4\"\n          />\n          Merge Across\n        </DropdownMenuItem>\n        <DropdownMenuItem disabled={!canMerge} onClick={onMergeCells}>\n          <IconPlaceholder\n            lucide=\"Grid3x3\"\n            tabler=\"IconGrid4x4\"\n            hugeicons=\"CellsIcon\"\n            phosphor=\"GridNineIcon\"\n            remixicon=\"RiMergeCellsHorizontal\"\n            className=\"size-4\"\n          />\n          Merge Cells\n        </DropdownMenuItem>\n        <DropdownMenuSeparator />\n        <DropdownMenuItem disabled={!canUnmerge} onClick={onUnmergeCells}>\n          <IconPlaceholder\n            lucide=\"Grid2x2X\"\n            tabler=\"IconTableMinus\"\n            hugeicons=\"Grid2X2XIcon\"\n            phosphor=\"SelectionSlashIcon\"\n            remixicon=\"RiSplitCellsHorizontal\"\n            className=\"size-4\"\n          />\n          Unmerge Cells\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction NumberFormatSelect({\n  disabled,\n  onApplyStyle,\n  value,\n}: {\n  disabled: boolean\n  onApplyStyle: (style: XlsxCellStyleInput) => void\n  value: string\n}) {\n  const hasMatchingOption = NUMBER_FORMAT_OPTIONS.some(\n    (option) => option.value === value\n  )\n  const currentLabel = getNumberFormatLabel(value)\n  const currentIcon = React.createElement(getNumberFormatIcon(value), {\n    className: \"size-4\",\n  })\n  return (\n    <Select\n      value={value}\n      onValueChange={(nextValue) => {\n        if (!nextValue) return\n        onApplyStyle(createNumberFormatStyle(nextValue))\n      }}\n      disabled={disabled}\n    >\n      <SelectTrigger\n        size=\"sm\"\n        className={cn(\n          \"w-[132px] min-w-[132px]\",\n          XLSX_EDITOR_SELECT_CHROME_CLASS\n        )}\n        aria-label=\"Number format\"\n      >\n        {currentIcon}\n        <SelectValue placeholder=\"General\" />\n      </SelectTrigger>\n      <SelectContent\n        align=\"start\"\n        alignItemWithTrigger={false}\n        className={XLSX_DROPDOWN_Z_INDEX_CLASS}\n      >\n        {NUMBER_FORMAT_OPTIONS.map((option) => (\n          <SelectItem key={option.value} value={option.value}>\n            <span className=\"flex min-w-0 items-center gap-2\">\n              <option.icon className=\"size-4\" />\n              <span className=\"truncate\">{option.label}</span>\n            </span>\n          </SelectItem>\n        ))}\n        {!hasMatchingOption && value !== GENERAL_NUMBER_FORMAT_VALUE ? (\n          <SelectItem value={value}>\n            <span className=\"flex min-w-0 items-center gap-2\">\n              {currentIcon}\n              <span className=\"truncate\">{currentLabel}</span>\n            </span>\n          </SelectItem>\n        ) : null}\n      </SelectContent>\n    </Select>\n  )\n}\nfunction BorderMenu({\n  borderColor,\n  disabled,\n  onApplyBorder,\n  onBorderColorChange,\n}: {\n  borderColor: string\n  disabled: boolean\n  onApplyBorder: (action: XlsxBorderAction) => void\n  onBorderColorChange: (color: string) => void\n}) {\n  const borderOptions = React.useMemo(() => createBorderOptions(), [])\n  const edgeBorderOptions = borderOptions.filter(\n    (option) =>\n      option.action === \"bottom\" ||\n      option.action === \"left\" ||\n      option.action === \"top\" ||\n      option.action === \"right\"\n  )\n  const groupedBorderOptions = borderOptions.filter(\n    (option) =>\n      option.action === \"all\" ||\n      option.action === \"outside\" ||\n      option.action === \"none\"\n  )\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"icon-sm\"\n            disabled={disabled}\n            aria-label=\"Cell borders\"\n            className=\"relative\"\n          >\n            <IconPlaceholder\n              lucide=\"Grid2x2\"\n              tabler=\"IconBorderAll\"\n              hugeicons=\"BorderAll01Icon\"\n              phosphor=\"GridFourIcon\"\n              remixicon=\"RiGridLine\"\n              className=\"size-4\"\n            />\n            <span\n              className=\"absolute right-1 bottom-1 h-1 w-4 rounded-full border border-background\"\n              style={{ backgroundColor: borderColor }}\n            />\n          </Button>\n        }\n      ></DropdownMenuTrigger>\n      <DropdownMenuContent\n        align=\"start\"\n        className={cn(\"w-48\", XLSX_DROPDOWN_Z_INDEX_CLASS)}\n      >\n        {edgeBorderOptions.map((option) => (\n          <StyleDropdownItem\n            key={option.label}\n            icon={option.icon}\n            onClick={() => onApplyBorder(option.action)}\n          >\n            {option.label}\n          </StyleDropdownItem>\n        ))}\n        <DropdownMenuSeparator />\n        <DropdownMenuGroup>\n          {groupedBorderOptions.map((option) => (\n            <StyleDropdownItem\n              key={option.label}\n              icon={option.icon}\n              onClick={() => onApplyBorder(option.action)}\n            >\n              {option.label}\n            </StyleDropdownItem>\n          ))}\n        </DropdownMenuGroup>\n        <DropdownMenuSeparator />\n        <DropdownMenuSub>\n          <DropdownMenuSubTrigger>\n            <IconPlaceholder\n              lucide=\"Baseline\"\n              tabler=\"IconTextColor\"\n              hugeicons=\"TextColorIcon\"\n              phosphor=\"TextAaIcon\"\n              remixicon=\"RiFontColor\"\n              className=\"size-4 text-muted-foreground\"\n            />\n            Border color\n            <span\n              className=\"h-3 w-5 rounded-full border border-border\"\n              style={{ backgroundColor: borderColor }}\n            />\n          </DropdownMenuSubTrigger>\n          <DropdownMenuSubContent\n            className={cn(\"w-auto p-3\", XLSX_DROPDOWN_Z_INDEX_CLASS)}\n          >\n            <ColorPickerPanel\n              label=\"Border color\"\n              color={borderColor}\n              onChange={(color) =>\n                onBorderColorChange(normalizeHexColor(color))\n              }\n            />\n          </DropdownMenuSubContent>\n        </DropdownMenuSub>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction AlignmentMoreMenu({\n  disabled,\n  onApplyStyle,\n}: {\n  disabled: boolean\n  onApplyStyle: (style: XlsxCellStyleInput) => void\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"icon-sm\"\n            disabled={disabled}\n            aria-label=\"More alignment\"\n          >\n            <IconPlaceholder\n              lucide=\"Ellipsis\"\n              tabler=\"IconDots\"\n              hugeicons=\"MoreHorizontalIcon\"\n              phosphor=\"DotsThreeIcon\"\n              remixicon=\"RiMoreLine\"\n              className=\"size-4\"\n            />\n          </Button>\n        }\n      ></DropdownMenuTrigger>\n      <DropdownMenuContent\n        align=\"start\"\n        className={cn(\"w-48\", XLSX_DROPDOWN_Z_INDEX_CLASS)}\n      >\n        <StyleDropdownItem\n          icon={TextWrapGlyph}\n          onClick={() => onApplyStyle({ alignment: { wrapText: false } })}\n        >\n          Do not wrap\n        </StyleDropdownItem>\n        <StyleDropdownItem\n          icon={TextWrapGlyph}\n          onClick={() => onApplyStyle({ alignment: { shrinkToFit: true } })}\n        >\n          Shrink to fit\n        </StyleDropdownItem>\n        <StyleDropdownItem\n          icon={TextWrapGlyph}\n          onClick={() => onApplyStyle({ alignment: { shrinkToFit: false } })}\n        >\n          Do not shrink\n        </StyleDropdownItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction EditorFileActionsMenu({\n  canExport,\n  exportXlsx,\n  onUploadClick,\n}: {\n  canExport: boolean\n  exportXlsx: () => void\n  onUploadClick: () => void\n}) {\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger\n        render={\n          <Button\n            type=\"button\"\n            variant=\"ghost\"\n            size=\"icon-sm\"\n            aria-label=\"Open workbook actions\"\n          >\n            <IconPlaceholder\n              lucide=\"Ellipsis\"\n              tabler=\"IconDots\"\n              hugeicons=\"MoreHorizontalIcon\"\n              phosphor=\"DotsThreeIcon\"\n              remixicon=\"RiMoreLine\"\n              className=\"size-4\"\n            />\n          </Button>\n        }\n      ></DropdownMenuTrigger>\n      <DropdownMenuContent\n        align=\"end\"\n        className={cn(\"w-40\", XLSX_DROPDOWN_Z_INDEX_CLASS)}\n      >\n        <DropdownMenuItem disabled={!canExport} onClick={exportXlsx}>\n          <IconPlaceholder\n            lucide=\"Download\"\n            tabler=\"IconDownload\"\n            hugeicons=\"Download01Icon\"\n            phosphor=\"DownloadSimpleIcon\"\n            remixicon=\"RiDownload2Line\"\n            className=\"size-4\"\n          />\n          Download\n        </DropdownMenuItem>\n        <DropdownMenuItem onClick={onUploadClick}>\n          <IconPlaceholder\n            lucide=\"Upload\"\n            tabler=\"IconUpload\"\n            hugeicons=\"Upload01Icon\"\n            phosphor=\"UploadSimpleIcon\"\n            remixicon=\"RiUpload2Line\"\n            className=\"size-4\"\n          />\n          Upload\n        </DropdownMenuItem>\n      </DropdownMenuContent>\n    </DropdownMenu>\n  )\n}\nfunction EditorToolbar({\n  isDark,\n  onIsDarkChange,\n  onUploadClick,\n  showNightRenderToggle,\n  workbookIdentity,\n}: {\n  isDark: boolean\n  onIsDarkChange: (checked: boolean) => void\n  onUploadClick: () => void\n  showNightRenderToggle: boolean\n  workbookIdentity: string\n}) {\n  const {\n    activeSheet,\n    activeSheetIndex,\n    canExport,\n    exportXlsx,\n    getActiveWorksheet,\n    revision,\n    setActiveSheetIndex,\n    sheets,\n  } = useXlsxViewer()\n  const { activeCell, activeCellAddress, selectRange, selection } =\n    useXlsxViewerSelection()\n  const {\n    addSheet,\n    canRedo,\n    canUndo,\n    mergeSelection,\n    pasteStructuredClipboardData,\n    readOnly,\n    redo,\n    removeActiveSheet,\n    selectedFormula,\n    selectedValue,\n    setCellStyle,\n    setCellFormula,\n    setCellValue,\n    setRangeStyle,\n    setSelectedCellStyle,\n    undo,\n    unmergeSelection,\n  } = useXlsxViewerEditing()\n  const { canZoomIn, canZoomOut, setZoomScale, zoomIn, zoomOut, zoomScale } =\n    useXlsxViewerZoom()\n  const [formulaDraft, setFormulaDraft] = React.useState(\"\")\n  const [formulaFocused, setFormulaFocused] = React.useState(false)\n  const [fontFamily, setFontFamily] = React.useState(DEFAULT_FONT_FAMILY)\n  const [fontSize, setFontSize] = React.useState(11)\n  const [textColor, setTextColor] = React.useState(DEFAULT_TEXT_COLOR)\n  const [fillColor, setFillColor] = React.useState(DEFAULT_FILL_COLOR)\n  const [borderColor, setBorderColor] = React.useState(DEFAULT_BORDER_COLOR)\n  const [appliedMergeRegions, setAppliedMergeRegions] = React.useState<\n    XlsxMergeRegion[]\n  >([])\n  const lastStyleTargetRef = React.useRef<{\n    range: XlsxCellRange\n    sheetIndex: number\n    workbookIdentity: string\n  } | null>(null)\n  const formulaEditCellRef = React.useRef<typeof activeCell>(null)\n  const formulaInitialValueRef = React.useRef(\"\")\n  const hasWorkbook = sheets.length > 0\n  const hasSelection = Boolean(selection)\n  const hasActiveCell = Boolean(activeCell)\n  const canStyleSelection = (hasSelection || hasActiveCell) && !readOnly\n  const activeRange = React.useMemo<XlsxCellRange | null>(() => {\n    if (selection) return normalizeCellRange(selection)\n    if (!activeCell) return null\n    return {\n      start: activeCell,\n      end: activeCell,\n    }\n  }, [activeCell, selection])\n  React.useEffect(() => {\n    if (!activeRange) return\n    lastStyleTargetRef.current = {\n      range: activeRange,\n      sheetIndex: activeSheetIndex,\n      workbookIdentity,\n    }\n  }, [activeRange, activeSheetIndex, workbookIdentity])\n  const worksheetMergeRegions = React.useMemo(() => {\n    void revision\n    return getWorksheetMergeRegions(\n      getActiveWorksheet() as XlsxWorksheetWithMergeRegions | null\n    )\n  }, [getActiveWorksheet, revision])\n  const mergeRegions = React.useMemo(\n    () => [...worksheetMergeRegions, ...appliedMergeRegions],\n    [appliedMergeRegions, worksheetMergeRegions]\n  )\n  const selectionIntersectsMerge = Boolean(\n    activeRange &&\n      mergeRegions.some((mergeRegion) =>\n        rangesIntersect(activeRange, mergeRegion)\n      )\n  )\n  const pendingMergeRegions = appliedMergeRegions.filter(\n    (currentRegion) =>\n      !worksheetMergeRegions.some((worksheetRegion) =>\n        rangesIntersect(currentRegion, worksheetRegion)\n      )\n  )\n  if (pendingMergeRegions.length !== appliedMergeRegions.length) {\n    setAppliedMergeRegions(pendingMergeRegions)\n  }\n  const [previousSheetIndex, setPreviousSheetIndex] =\n    React.useState(activeSheetIndex)\n  if (!Object.is(previousSheetIndex, activeSheetIndex)) {\n    setPreviousSheetIndex(activeSheetIndex)\n    setAppliedMergeRegions([])\n  }\n  const selectionIsSingleCell = activeRange\n    ? isSingleCellRange(activeRange)\n    : true\n  const selectionSpansMultipleColumns = activeRange\n    ? activeRange.start.col !== activeRange.end.col\n    : false\n  const canMergeSelectedCells = Boolean(\n    selection &&\n      !readOnly &&\n      !selectionIntersectsMerge &&\n      !selectionIsSingleCell\n  )\n  const canMergeAcross = Boolean(\n    selection &&\n      !readOnly &&\n      !selectionIntersectsMerge &&\n      selectionSpansMultipleColumns\n  )\n  const canUnmergeSelectedCells = Boolean(\n    selection && !readOnly && selectionIntersectsMerge\n  )\n  const currentZoom = Math.round(zoomScale)\n  const selectedCellInputValue = selectedFormula || selectedValue\n  const activeCellStyle = React.useMemo(() => {\n    // Worksheet style objects are mutable; revision invalidates this read after edits.\n    void revision\n    if (!activeCell) return null\n    const worksheet = getActiveWorksheet()\n    const inheritedStyle = resolveInheritedCellStyle(\n      activeSheet,\n      activeCell.row,\n      activeCell.col\n    )\n    const cellStyle = worksheet?.getCellStyleAt(\n      activeCell.row,\n      activeCell.col\n    ) as XlsxResolvedCellStyle | null | undefined\n    return mergeResolvedCellStyle(inheritedStyle, cellStyle)\n  }, [activeCell, activeSheet, getActiveWorksheet, revision])\n  const fontToggleValues = React.useMemo(\n    () => getFontToggleValuesFromStyle(activeCellStyle),\n    [activeCellStyle]\n  )\n  const fontFamilyValue = getFontFamilyValue(activeCellStyle) ?? fontFamily\n  const horizontalAlignmentValue = React.useMemo(\n    () => getHorizontalAlignmentValue(activeCellStyle),\n    [activeCellStyle]\n  )\n  const verticalAlignmentValue = React.useMemo(\n    () => getVerticalAlignmentValue(activeCellStyle),\n    [activeCellStyle]\n  )\n  const numberFormatValue = React.useMemo(\n    () => getNumberFormatValue(activeCellStyle),\n    [activeCellStyle]\n  )\n  const commitFormula = React.useCallback(() => {\n    const targetCell = formulaEditCellRef.current ?? activeCell\n    if (!targetCell) return\n    if (formulaDraft === formulaInitialValueRef.current) return\n    if (formulaDraft.trim().startsWith(\"=\")) {\n      setCellFormula(targetCell, formulaDraft)\n    } else {\n      setCellValue(targetCell, formulaDraft)\n    }\n    formulaInitialValueRef.current = formulaDraft\n  }, [activeCell, formulaDraft, setCellFormula, setCellValue])\n  const applyStyle = React.useCallback(\n    (style: XlsxCellStyleInput) => {\n      const applyRangeStyle = (range: XlsxCellRange) => {\n        const normalizedRange = normalizeCellRange(range)\n        const targetRanges = [\n          normalizedRange,\n          ...mergeRegions.filter((mergeRegion) =>\n            rangesIntersect(normalizedRange, mergeRegion)\n          ),\n        ]\n        const uniqueTargetRanges = new Map<string, XlsxCellRange>()\n        targetRanges.forEach((targetRange) => {\n          const normalizedTargetRange = normalizeCellRange(targetRange)\n          uniqueTargetRanges.set(\n            getRangeKey(normalizedTargetRange),\n            normalizedTargetRange\n          )\n        })\n        uniqueTargetRanges.forEach((targetRange) => {\n          setRangeStyle(targetRange, style)\n        })\n      }\n      if (selection) {\n        const normalizedSelection = normalizeCellRange(selection)\n        if (!isSingleCellRange(normalizedSelection)) {\n          applyRangeStyle(normalizedSelection)\n          return true\n        }\n        if (activeCell) {\n          setSelectedCellStyle(style)\n          return true\n        }\n        setCellStyle(normalizedSelection.start, style)\n        return true\n      }\n      if (activeCell) {\n        setSelectedCellStyle(style)\n        return true\n      }\n      const fallbackTarget = lastStyleTargetRef.current\n      const targetRange =\n        fallbackTarget?.sheetIndex === activeSheetIndex &&\n        fallbackTarget.workbookIdentity === workbookIdentity\n          ? fallbackTarget.range\n          : null\n      if (!targetRange) return false\n      if (isSingleCellRange(targetRange)) {\n        setCellStyle(normalizeCellRange(targetRange).start, style)\n        return true\n      }\n      applyRangeStyle(targetRange)\n      return true\n    },\n    [\n      activeCell,\n      activeSheetIndex,\n      mergeRegions,\n      selection,\n      setCellStyle,\n      setRangeStyle,\n      setSelectedCellStyle,\n      workbookIdentity,\n    ]\n  )\n  const applyBorderAction = React.useCallback(\n    (action: XlsxBorderAction) => {\n      if (!activeRange) return\n      const normalizedRange = normalizeCellRange(activeRange)\n      const edge = createBorderEdge(borderColor)\n      const allBorderPatch = createBorderPatch({\n        bottom: edge,\n        left: edge,\n        right: edge,\n        top: edge,\n      })\n      if (action === \"all\") {\n        setRangeStyle(normalizedRange, allBorderPatch)\n        return\n      }\n      if (action === \"none\") {\n        setRangeStyle(normalizedRange, createNoBorderPatch())\n        return\n      }\n      if (action === \"outside\") {\n        if (isSingleCellRange(normalizedRange)) {\n          setCellStyle(normalizedRange.start, allBorderPatch)\n          return\n        }\n        const outsideEdgeKeys: XlsxBorderEdgeKey[] = [\n          \"top\",\n          \"bottom\",\n          \"left\",\n          \"right\",\n        ]\n        outsideEdgeKeys.forEach((edgeKey) => {\n          setRangeStyle(\n            getBorderEdgeRange(normalizedRange, edgeKey),\n            createBorderPatch({ [edgeKey]: edge })\n          )\n        })\n        return\n      }\n      setRangeStyle(\n        getBorderEdgeRange(normalizedRange, action),\n        createBorderPatch({ [action]: edge })\n      )\n    },\n    [activeRange, borderColor, setCellStyle, setRangeStyle]\n  )\n  const applyHorizontalAlignment = React.useCallback(\n    (horizontal: HorizontalAlignmentToggleValue | undefined) => {\n      if (!horizontal) return\n      applyStyle({ alignment: { horizontal } })\n    },\n    [applyStyle]\n  )\n  const applyVerticalAlignment = React.useCallback(\n    (vertical: VerticalAlignmentToggleValue | undefined) => {\n      if (!vertical) return\n      applyStyle({ alignment: { vertical } })\n    },\n    [applyStyle]\n  )\n  const mergeSelectedCells = React.useCallback(() => {\n    if (!canMergeSelectedCells || !selection) return\n    setAppliedMergeRegions([normalizeCellRange(selection)])\n    mergeSelection()\n  }, [canMergeSelectedCells, mergeSelection, selection])\n  const mergeAcrossSelectedCells = React.useCallback(() => {\n    if (!activeCell || !canMergeAcross || !selection) return\n    const normalizedRange = normalizeCellRange(selection)\n    const worksheet = getActiveWorksheet()\n    if (!worksheet) return\n    const formula = worksheet.getFormulaAt(activeCell.row, activeCell.col)\n    const rawValue = worksheet.getCellAt(activeCell.row, activeCell.col).toJs()\n    const colSpan = normalizedRange.end.col - normalizedRange.start.col + 1\n    const rowSpan = normalizedRange.end.row - normalizedRange.start.row + 1\n    const merges = Array.from({ length: rowSpan }, (_, rowOffset) => ({\n      colOffset: normalizedRange.start.col - activeCell.col,\n      colSpan,\n      rowOffset: normalizedRange.start.row + rowOffset - activeCell.row,\n      rowSpan: 1,\n    }))\n    const mergeRegionsByRow = Array.from({ length: rowSpan }, (_, rowOffset) =>\n      normalizeCellRange({\n        start: {\n          col: normalizedRange.start.col,\n          row: normalizedRange.start.row + rowOffset,\n        },\n        end: {\n          col: normalizedRange.end.col,\n          row: normalizedRange.start.row + rowOffset,\n        },\n      })\n    )\n    const didPaste = pasteStructuredClipboardData(\n      JSON.stringify({\n        cells: [\n          formula\n            ? { colOffset: 0, formula, rowOffset: 0 }\n            : { colOffset: 0, rowOffset: 0, value: rawValue ?? \"\" },\n        ],\n        cols: colSpan,\n        merges,\n        rows: rowSpan,\n      })\n    )\n    if (!didPaste) return\n    setAppliedMergeRegions(mergeRegionsByRow)\n    selectRange(normalizedRange)\n  }, [\n    activeCell,\n    canMergeAcross,\n    getActiveWorksheet,\n    pasteStructuredClipboardData,\n    selectRange,\n    selection,\n  ])\n  const mergeAndCenterSelectedCells = React.useCallback(() => {\n    if (!canMergeSelectedCells || !selection) return\n    setAppliedMergeRegions([normalizeCellRange(selection)])\n    setRangeStyle(selection, { alignment: { horizontal: \"center\" } })\n    mergeSelection()\n  }, [canMergeSelectedCells, mergeSelection, selection, setRangeStyle])\n  const unmergeSelectedCells = React.useCallback(() => {\n    if (!canUnmergeSelectedCells || !activeRange) return\n    const worksheet =\n      getActiveWorksheet() as XlsxWorksheetWithMergeRegions | null\n    if (!worksheet?.unmergeCells) return\n    const normalizedRange = normalizeCellRange(activeRange)\n    const uniqueMergeRegions = new Map<string, XlsxMergeRegion>()\n    ;[...worksheetMergeRegions, ...appliedMergeRegions].forEach(\n      (mergeRegion) => {\n        if (!rangesIntersect(normalizedRange, mergeRegion)) return\n        uniqueMergeRegions.set(getRangeKey(mergeRegion), mergeRegion)\n      }\n    )\n    let didUnmerge = false\n    uniqueMergeRegions.forEach((mergeRegion) => {\n      didUnmerge =\n        worksheet.unmergeCells?.(rangeToA1(mergeRegion)) || didUnmerge\n    })\n    if (!didUnmerge) {\n      unmergeSelection()\n      return\n    }\n    setAppliedMergeRegions([])\n    setRangeStyle(normalizedRange, {})\n    selectRange(normalizedRange)\n  }, [\n    activeRange,\n    appliedMergeRegions,\n    canUnmergeSelectedCells,\n    getActiveWorksheet,\n    selectRange,\n    setRangeStyle,\n    unmergeSelection,\n    worksheetMergeRegions,\n  ])\n  const applyFontToggleValues = React.useCallback(\n    (nextValues: XlsxFontToggleValue[]) => {\n      const previousValues = fontToggleValues\n      const changedValue =\n        nextValues.find((value) => !previousValues.includes(value)) ??\n        previousValues.find((value) => !nextValues.includes(value))\n      if (!changedValue) return\n      const isPressed = nextValues.includes(changedValue)\n      if (changedValue === \"bold\") {\n        applyStyle({ font: { bold: isPressed } })\n      } else if (changedValue === \"italic\") {\n        applyStyle({ font: { italic: isPressed } })\n      } else if (changedValue === \"underline\") {\n        applyStyle({ font: { underline: isPressed ? \"single\" : \"none\" } })\n      } else if (changedValue === \"strikethrough\") {\n        applyStyle({ font: { strikethrough: isPressed } })\n      }\n    },\n    [applyStyle, fontToggleValues]\n  )\n  return (\n    <div className=\"border-b bg-background\">\n      <div className=\"flex min-h-11 items-center justify-between gap-3 border-b px-3\">\n        <div className=\"min-w-0 flex-1\" />\n        <TooltipProvider>\n          <div className=\"flex shrink-0 items-center gap-1\">\n            {showNightRenderToggle ? (\n              <>\n                <ToolbarTooltip\n                  label={isDark ? \"Use light workbook\" : \"Use dark workbook\"}\n                >\n                  <Button\n                    type=\"button\"\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    aria-label={\n                      isDark ? \"Use light workbook\" : \"Use dark workbook\"\n                    }\n                    onClick={() => onIsDarkChange(!isDark)}\n                  >\n                    {isDark ? (\n                      <Sun03Glyph className=\"size-4\" />\n                    ) : (\n                      <Moon02Glyph className=\"size-4\" />\n                    )}\n                  </Button>\n                </ToolbarTooltip>\n                <Separator\n                  orientation=\"vertical\"\n                  className=\"mx-1 h-4 self-center\"\n                />\n              </>\n            ) : null}\n            <EditorFileActionsMenu\n              canExport={canExport}\n              exportXlsx={exportXlsx}\n              onUploadClick={onUploadClick}\n            />\n          </div>\n        </TooltipProvider>\n      </div>\n      <TooltipProvider>\n        <div className=\"flex min-h-12 flex-wrap items-center gap-2 border-b bg-background px-3 py-2\">\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToolbarTooltip label=\"Undo\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Undo\"\n                disabled={!canUndo || readOnly}\n                onClick={undo}\n              >\n                <IconPlaceholder\n                  lucide=\"Undo2\"\n                  tabler=\"IconArrowBackUp\"\n                  hugeicons=\"Undo02Icon\"\n                  phosphor=\"ArrowUUpLeftIcon\"\n                  remixicon=\"RiArrowGoBackLine\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Redo\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Redo\"\n                disabled={!canRedo || readOnly}\n                onClick={redo}\n              >\n                <IconPlaceholder\n                  lucide=\"Redo2\"\n                  tabler=\"IconArrowForwardUp\"\n                  hugeicons=\"Redo02Icon\"\n                  phosphor=\"ArrowUUpRightIcon\"\n                  remixicon=\"RiArrowGoForwardLine\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n          </div>\n          <ToolbarSeparator />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <Select\n              value={fontFamilyValue}\n              onValueChange={(value) => {\n                if (!value) return\n                if (applyStyle({ font: { name: value } })) {\n                  setFontFamily(value)\n                }\n              }}\n              disabled={!canStyleSelection}\n              modal={false}\n            >\n              <SelectTrigger\n                size=\"sm\"\n                className={cn(\n                  \"w-[132px] min-w-[132px]\",\n                  XLSX_EDITOR_SELECT_CHROME_CLASS\n                )}\n                aria-label=\"Font family\"\n              >\n                <SelectValue placeholder=\"Font\">{fontFamilyValue}</SelectValue>\n              </SelectTrigger>\n              <SelectContent\n                align=\"start\"\n                alignItemWithTrigger={false}\n                className={XLSX_DROPDOWN_Z_INDEX_CLASS}\n              >\n                {FONT_FAMILIES.map((option) => (\n                  <SelectItem key={option} value={option}>\n                    <span style={{ fontFamily: option }}>{option}</span>\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n            <Select\n              value={String(fontSize)}\n              onValueChange={(value) => {\n                const nextFontSize = Number(value)\n                if (!Number.isFinite(nextFontSize)) return\n                setFontSize(nextFontSize)\n                applyStyle({ font: { size: nextFontSize } })\n              }}\n              disabled={!canStyleSelection}\n              modal={false}\n            >\n              <SelectTrigger\n                size=\"sm\"\n                className={cn(\n                  \"w-[78px] min-w-[78px]\",\n                  XLSX_EDITOR_SELECT_CHROME_CLASS\n                )}\n                aria-label=\"Font size\"\n              >\n                <SelectValue placeholder=\"Size\" />\n              </SelectTrigger>\n              <SelectContent\n                align=\"start\"\n                alignItemWithTrigger={false}\n                className={XLSX_DROPDOWN_Z_INDEX_CLASS}\n              >\n                {FONT_SIZE_OPTIONS.map((size) => (\n                  <SelectItem key={size} value={String(size)}>\n                    {size} pt\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n          <ToolbarSeparator />\n          <ToggleGroup\n            className=\"shrink-0\"\n            disabled={!canStyleSelection}\n            multiple\n            value={fontToggleValues}\n            onValueChange={(value) =>\n              applyFontToggleValues(value as XlsxFontToggleValue[])\n            }\n            spacing={1}\n          >\n            <ToolbarTooltip label=\"Bold\">\n              <ToggleGroupItem aria-label=\"Bold\" size=\"sm\" value=\"bold\">\n                <IconPlaceholder\n                  lucide=\"Bold\"\n                  tabler=\"IconBold\"\n                  hugeicons=\"TextBoldIcon\"\n                  phosphor=\"TextBIcon\"\n                  remixicon=\"RiBold\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Italic\">\n              <ToggleGroupItem aria-label=\"Italic\" size=\"sm\" value=\"italic\">\n                <IconPlaceholder\n                  lucide=\"Italic\"\n                  tabler=\"IconItalic\"\n                  hugeicons=\"TextItalicIcon\"\n                  phosphor=\"TextItalicIcon\"\n                  remixicon=\"RiItalic\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Underline\">\n              <ToggleGroupItem\n                aria-label=\"Underline\"\n                size=\"sm\"\n                value=\"underline\"\n              >\n                <IconPlaceholder\n                  lucide=\"Underline\"\n                  tabler=\"IconUnderline\"\n                  hugeicons=\"TextUnderlineIcon\"\n                  phosphor=\"TextUnderlineIcon\"\n                  remixicon=\"RiUnderline\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Strikethrough\">\n              <ToggleGroupItem\n                aria-label=\"Strikethrough\"\n                size=\"sm\"\n                value=\"strikethrough\"\n              >\n                <IconPlaceholder\n                  lucide=\"Strikethrough\"\n                  tabler=\"IconStrikethrough\"\n                  hugeicons=\"TextStrikethroughIcon\"\n                  phosphor=\"TextStrikethroughIcon\"\n                  remixicon=\"RiStrikethrough\"\n                  className=\"size-4\"\n                />\n              </ToggleGroupItem>\n            </ToolbarTooltip>\n          </ToggleGroup>\n          <ToolbarSeparator />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ColorPicker\n              label=\"Text color\"\n              icon={TextColorGlyph}\n              color={textColor}\n              disabled={!canStyleSelection}\n              onChange={(color) => {\n                const nextColor = normalizeHexColor(color)\n                setTextColor(nextColor)\n                applyStyle({ font: { color: toXlsxRgbColor(nextColor) } })\n              }}\n            />\n            <ColorPicker\n              label=\"Fill color\"\n              icon={PaintBucketGlyph}\n              color={fillColor}\n              disabled={!canStyleSelection}\n              onChange={(color) => {\n                const nextColor = normalizeHexColor(color, DEFAULT_FILL_COLOR)\n                setFillColor(nextColor)\n                applyStyle({\n                  fill: {\n                    color: toXlsxRgbColor(nextColor),\n                    fillType: \"solid\",\n                  },\n                })\n              }}\n            />\n            <ToolbarIconButton\n              label=\"No fill\"\n              disabled={!canStyleSelection}\n              onClick={() => applyStyle({ fill: { fillType: \"none\" } })}\n            >\n              <IconPlaceholder\n                lucide=\"DropletOff\"\n                tabler=\"IconDropletOff\"\n                hugeicons=\"DropletOffIcon\"\n                phosphor=\"DropSlashIcon\"\n                remixicon=\"RiDropLine\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n          </div>\n          <ToolbarSeparator />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToggleGroup\n              className=\"shrink-0\"\n              disabled={!canStyleSelection}\n              value={horizontalAlignmentValue}\n              spacing={1}\n            >\n              <ToolbarTooltip label=\"Align left\">\n                <ToggleGroupItem\n                  aria-label=\"Align left\"\n                  size=\"sm\"\n                  value=\"left\"\n                  onClick={() => applyHorizontalAlignment(\"left\")}\n                >\n                  <IconPlaceholder\n                    lucide=\"AlignLeft\"\n                    tabler=\"IconAlignLeft\"\n                    hugeicons=\"TextAlignLeft01Icon\"\n                    phosphor=\"TextAlignLeftIcon\"\n                    remixicon=\"RiAlignLeft\"\n                    className=\"size-4\"\n                  />\n                </ToggleGroupItem>\n              </ToolbarTooltip>\n              <ToolbarTooltip label=\"Align center\">\n                <ToggleGroupItem\n                  aria-label=\"Align center\"\n                  size=\"sm\"\n                  value=\"center\"\n                  onClick={() => applyHorizontalAlignment(\"center\")}\n                >\n                  <IconPlaceholder\n                    lucide=\"AlignCenter\"\n                    tabler=\"IconAlignCenter\"\n                    hugeicons=\"TextAlignCenterIcon\"\n                    phosphor=\"TextAlignCenterIcon\"\n                    remixicon=\"RiAlignCenter\"\n                    className=\"size-4\"\n                  />\n                </ToggleGroupItem>\n              </ToolbarTooltip>\n              <ToolbarTooltip label=\"Align right\">\n                <ToggleGroupItem\n                  aria-label=\"Align right\"\n                  size=\"sm\"\n                  value=\"right\"\n                  onClick={() => applyHorizontalAlignment(\"right\")}\n                >\n                  <IconPlaceholder\n                    lucide=\"AlignRight\"\n                    tabler=\"IconAlignRight\"\n                    hugeicons=\"TextAlignRight01Icon\"\n                    phosphor=\"TextAlignRightIcon\"\n                    remixicon=\"RiAlignRight\"\n                    className=\"size-4\"\n                  />\n                </ToggleGroupItem>\n              </ToolbarTooltip>\n            </ToggleGroup>\n            <ToggleGroup\n              className=\"shrink-0\"\n              disabled={!canStyleSelection}\n              value={verticalAlignmentValue}\n              spacing={1}\n            >\n              <ToolbarTooltip label=\"Align top\">\n                <ToggleGroupItem\n                  aria-label=\"Align top\"\n                  size=\"sm\"\n                  value=\"top\"\n                  onClick={() => applyVerticalAlignment(\"top\")}\n                >\n                  <IconPlaceholder\n                    lucide=\"AlignVerticalJustifyStart\"\n                    tabler=\"IconAlignBoxTopCenter\"\n                    hugeicons=\"AlignBoxTopCenterIcon\"\n                    phosphor=\"AlignTopIcon\"\n                    remixicon=\"RiAlignTop\"\n                    className=\"size-4\"\n                  />\n                </ToggleGroupItem>\n              </ToolbarTooltip>\n              <ToolbarTooltip label=\"Align middle\">\n                <ToggleGroupItem\n                  aria-label=\"Align middle\"\n                  size=\"sm\"\n                  value=\"center\"\n                  onClick={() => applyVerticalAlignment(\"center\")}\n                >\n                  <IconPlaceholder\n                    lucide=\"AlignVerticalJustifyCenter\"\n                    tabler=\"IconAlignBoxCenterMiddle\"\n                    hugeicons=\"AlignBoxMiddleCenterIcon\"\n                    phosphor=\"AlignCenterVerticalIcon\"\n                    remixicon=\"RiAlignVertically\"\n                    className=\"size-4\"\n                  />\n                </ToggleGroupItem>\n              </ToolbarTooltip>\n              <ToolbarTooltip label=\"Align bottom\">\n                <ToggleGroupItem\n                  aria-label=\"Align bottom\"\n                  size=\"sm\"\n                  value=\"bottom\"\n                  onClick={() => applyVerticalAlignment(\"bottom\")}\n                >\n                  <IconPlaceholder\n                    lucide=\"AlignVerticalJustifyEnd\"\n                    tabler=\"IconAlignBoxBottomCenter\"\n                    hugeicons=\"AlignBoxBottomCenterIcon\"\n                    phosphor=\"AlignBottomIcon\"\n                    remixicon=\"RiAlignBottom\"\n                    className=\"size-4\"\n                  />\n                </ToggleGroupItem>\n              </ToolbarTooltip>\n            </ToggleGroup>\n            <ToolbarIconButton\n              label=\"Wrap text\"\n              disabled={!canStyleSelection}\n              onClick={() => applyStyle({ alignment: { wrapText: true } })}\n            >\n              <IconPlaceholder\n                lucide=\"WrapText\"\n                tabler=\"IconTextWrap\"\n                hugeicons=\"TextWrapIcon\"\n                phosphor=\"ArrowElbowDownLeftIcon\"\n                remixicon=\"RiTextWrap\"\n                className=\"size-4\"\n              />\n            </ToolbarIconButton>\n            <AlignmentMoreMenu\n              disabled={!canStyleSelection}\n              onApplyStyle={applyStyle}\n            />\n          </div>\n          <ToolbarSeparator />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <NumberFormatSelect\n              disabled={!canStyleSelection}\n              onApplyStyle={applyStyle}\n              value={numberFormatValue}\n            />\n            <BorderMenu\n              borderColor={borderColor}\n              disabled={!canStyleSelection}\n              onApplyBorder={applyBorderAction}\n              onBorderColorChange={setBorderColor}\n            />\n          </div>\n          <ToolbarSeparator />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <MergeCellsMenu\n              canMerge={canMergeSelectedCells}\n              canMergeAcross={canMergeAcross}\n              canUnmerge={canUnmergeSelectedCells}\n              disabled={!hasSelection || readOnly}\n              isMerged={selectionIntersectsMerge}\n              onMergeAcross={mergeAcrossSelectedCells}\n              onMergeAndCenter={mergeAndCenterSelectedCells}\n              onMergeCells={mergeSelectedCells}\n              onUnmergeCells={unmergeSelectedCells}\n            />\n          </div>\n          <ToolbarSeparator />\n          <div className=\"flex shrink-0 items-center gap-1\">\n            <ToolbarTooltip label=\"Add sheet\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Add sheet\"\n                disabled={!hasWorkbook || readOnly}\n                onClick={() => addSheet()}\n              >\n                <IconPlaceholder\n                  lucide=\"Plus\"\n                  tabler=\"IconPlus\"\n                  hugeicons=\"Add01Icon\"\n                  phosphor=\"PlusIcon\"\n                  remixicon=\"RiAddLine\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n            <ToolbarTooltip label=\"Remove active sheet\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                aria-label=\"Remove active sheet\"\n                disabled={sheets.length <= 1 || readOnly}\n                onClick={removeActiveSheet}\n              >\n                <IconPlaceholder\n                  lucide=\"Trash2Icon\"\n                  tabler=\"IconTrash\"\n                  hugeicons=\"Delete02Icon\"\n                  phosphor=\"TrashIcon\"\n                  remixicon=\"RiDeleteBinLine\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n            <Select\n              value={String(activeSheetIndex)}\n              onValueChange={(value) => setActiveSheetIndex(Number(value))}\n              disabled={!hasWorkbook}\n            >\n              <SelectTrigger\n                size=\"sm\"\n                className={cn(\n                  \"w-[150px] min-w-[150px]\",\n                  XLSX_EDITOR_SELECT_CHROME_CLASS\n                )}\n                aria-label=\"Active sheet\"\n              >\n                <SelectValue>{activeSheet?.name ?? \"Sheet\"}</SelectValue>\n              </SelectTrigger>\n              <SelectContent\n                align=\"start\"\n                className={XLSX_DROPDOWN_Z_INDEX_CLASS}\n              >\n                {sheets.map((sheet, index) => (\n                  <SelectItem\n                    key={`${sheet.name}-${index}`}\n                    value={String(index)}\n                  >\n                    {sheet.name}\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n          </div>\n          <ToolbarSeparator />\n          <div className=\"flex flex-none items-center gap-1\">\n            <ToolbarTooltip label=\"Zoom out\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                disabled={!hasWorkbook || !canZoomOut}\n                aria-label=\"Zoom out\"\n                onClick={zoomOut}\n              >\n                <IconPlaceholder\n                  lucide=\"CircleMinus\"\n                  tabler=\"IconCircleMinus\"\n                  hugeicons=\"MinusSignCircleIcon\"\n                  phosphor=\"MinusCircleIcon\"\n                  remixicon=\"RiIndeterminateCircleLine\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n            <Select\n              value={currentZoom.toString()}\n              onValueChange={(value) => setZoomScale(Number(value))}\n              disabled={!hasWorkbook}\n              modal={false}\n            >\n              <SelectTrigger\n                size=\"sm\"\n                className={cn(\n                  \"w-[84px] min-w-[84px]\",\n                  XLSX_EDITOR_SELECT_CHROME_CLASS\n                )}\n                aria-label=\"Zoom level\"\n              >\n                <SelectValue>{currentZoom}%</SelectValue>\n              </SelectTrigger>\n              <SelectContent\n                align=\"end\"\n                alignItemWithTrigger={false}\n                className={XLSX_DROPDOWN_Z_INDEX_CLASS}\n              >\n                {ZOOM_OPTIONS.map((value) => (\n                  <SelectItem key={value} value={value.toString()}>\n                    {value}%\n                  </SelectItem>\n                ))}\n              </SelectContent>\n            </Select>\n            <ToolbarTooltip label=\"Zoom in\">\n              <Button\n                type=\"button\"\n                variant=\"ghost\"\n                size=\"icon-sm\"\n                disabled={!hasWorkbook || !canZoomIn}\n                aria-label=\"Zoom in\"\n                onClick={zoomIn}\n              >\n                <IconPlaceholder\n                  lucide=\"CirclePlusIcon\"\n                  tabler=\"IconCirclePlusFilled\"\n                  hugeicons=\"PlusSignCircleIcon\"\n                  phosphor=\"PlusCircleIcon\"\n                  remixicon=\"RiAddCircleFill\"\n                  className=\"size-4\"\n                />\n              </Button>\n            </ToolbarTooltip>\n          </div>\n        </div>\n        <div className=\"border-t bg-background px-2 py-1\">\n          <Group className=\"w-full\">\n            <Input\n              className={cn(\n                \"h-8 w-[92px] shrink-0 font-mono text-xs\",\n                XLSX_EDITOR_FORMULA_INPUT_CHROME_CLASS\n              )}\n              readOnly\n              value={activeCellAddress ?? \"\"}\n            />\n            <GroupSeparator />\n            <GroupText className=\"h-8 w-9 shrink-0 justify-center px-0 text-[11px] font-semibold italic\">\n              fx\n            </GroupText>\n            <GroupSeparator />\n            <Input\n              className={cn(\n                \"h-8 flex-1\",\n                XLSX_EDITOR_FORMULA_INPUT_CHROME_CLASS\n              )}\n              disabled={!hasActiveCell || readOnly}\n              value={formulaFocused ? formulaDraft : selectedCellInputValue}\n              onBlur={() => {\n                commitFormula()\n                setFormulaFocused(false)\n              }}\n              onChange={(event) => setFormulaDraft(event.target.value)}\n              onFocus={() => {\n                formulaEditCellRef.current = activeCell\n                formulaInitialValueRef.current = selectedCellInputValue\n                setFormulaDraft(selectedCellInputValue)\n                setFormulaFocused(true)\n              }}\n              onKeyDown={(event) => {\n                if (event.key === \"Enter\") {\n                  event.preventDefault()\n                  commitFormula()\n                  setFormulaFocused(false)\n                }\n              }}\n              placeholder=\"Select a cell, then enter a value or formula\"\n            />\n          </Group>\n        </div>\n      </TooltipProvider>\n    </div>\n  )\n}\nexport function XlsxEditorSurface({\n  className,\n  isDark,\n  onIsDarkChange,\n  onUploadClick,\n  renderTableHeaderMenu,\n  showNightRenderToggle,\n  workbookIdentity,\n}: {\n  className?: string\n  isDark: boolean\n  onIsDarkChange: (checked: boolean) => void\n  onUploadClick: () => void\n  renderTableHeaderMenu: (\n    props: XlsxTableHeaderMenuRenderProps\n  ) => React.ReactNode\n  showNightRenderToggle: boolean\n  workbookIdentity: string\n}) {\n  const { error } = useXlsxViewer()\n  return (\n    <div\n      className={cn(\n        \"flex h-[640px] min-h-0 flex-col overflow-hidden bg-background\",\n        className\n      )}\n    >\n      <EditorToolbar\n        isDark={isDark}\n        onIsDarkChange={onIsDarkChange}\n        onUploadClick={onUploadClick}\n        showNightRenderToggle={showNightRenderToggle}\n        workbookIdentity={workbookIdentity}\n      />\n      <div className=\"flex min-h-0 flex-1 flex-col\">\n        <div className=\"min-h-0 flex-1 bg-muted/20\">\n          <XlsxViewer\n            allowResizeInReadOnly\n            className=\"h-full min-h-0 min-w-0\"\n            height=\"100%\"\n            isDark={isDark}\n            readOnly={false}\n            rounded={false}\n            showDefaultToolbar={false}\n            showImages\n            fileTooLargeState={\n              <div className=\"grid h-full w-full min-w-full place-items-center p-6\">\n                <div className=\"max-w-sm rounded-lg border bg-background p-4 text-sm\">\n                  <p className=\"font-medium\">File too large for editing</p>\n                  <p className=\"mt-1 text-muted-foreground\">\n                    This workbook exceeds the editor limit. Download it or open\n                    a smaller file to make changes.\n                  </p>\n                </div>\n              </div>\n            }\n            loadingState={<EditorLoadingSurface />}\n            renderScroller={renderXlsxScroller}\n            errorState={\n              <div className=\"grid h-full w-full min-w-full place-items-center p-6 text-sm text-destructive\">\n                {error?.message ?? \"Unable to edit workbook.\"}\n              </div>\n            }\n            renderTableHeaderMenu={renderTableHeaderMenu}\n          />\n        </div>\n        <WorkbookSheetTabs workbookIdentity={workbookIdentity} />\n      </div>\n    </div>\n  )\n}\nexport function XlsxEditorPreview({\n  className,\n  fileName,\n  isDark,\n  onIsDarkChange,\n  src,\n}: {\n  className?: string\n  fileName?: string\n  isDark: boolean\n  onIsDarkChange: (isDark: boolean) => void\n  src?: string\n}) {\n  return (\n    <XlsxEditorContent\n      key={src}\n      className={className}\n      effectiveIsDark={isDark}\n      fileName={fileName}\n      setNightRenderEnabled={onIsDarkChange}\n      shouldRenderNightMode\n      url={src}\n    />\n  )\n}\nfunction XlsxEditorContent({\n  className,\n  effectiveIsDark,\n  fileName,\n  setNightRenderEnabled,\n  shouldRenderNightMode,\n  url,\n}: {\n  className?: string\n  effectiveIsDark: boolean\n  fileName?: string\n  setNightRenderEnabled: (checked: boolean) => void\n  shouldRenderNightMode: boolean\n  url?: string\n}) {\n  const fileInputRef = React.useRef<HTMLInputElement>(null)\n  const [uploadedWorkbook, setUploadedWorkbook] =\n    React.useState<UploadedWorkbook | null>(null)\n  const [workbookBuffer, setWorkbookBuffer] =\n    React.useState<ArrayBuffer | null>(null)\n  const [loadError, setLoadError] = React.useState<string>()\n  const sourceFileName = React.useMemo(\n    () =>\n      url ? formatWorkbookName(fileName, url) : (fileName ?? \"workbook.xlsx\"),\n    [fileName, url]\n  )\n  const displayFileName = uploadedWorkbook?.fileName ?? sourceFileName\n  const workbookIdentity =\n    uploadedWorkbook?.identity ?? `${url ?? \"empty\"}::${displayFileName}`\n  const shouldShowLoadingSpinner = useDelayedLoadingIndicator(\n    !workbookBuffer && !loadError && !uploadedWorkbook,\n    XLSX_LOADING_INDICATOR_DELAY_MS\n  )\n  React.useEffect(() => {\n    let isCurrent = true\n    async function loadWorkbook(): Promise<void> {\n      if (!url) return\n      try {\n        const response = await fetch(url)\n        if (!response.ok) {\n          throw new Error(`Failed to fetch XLSX (${response.status})`)\n        }\n        const nextWorkbookBuffer = await response.arrayBuffer()\n        if (!isCurrent) return\n        setWorkbookBuffer(nextWorkbookBuffer)\n      } catch (error) {\n        if (!isCurrent) return\n        setLoadError(\n          error instanceof Error ? error.message : \"Unknown XLSX load error\"\n        )\n      }\n    }\n    void loadWorkbook()\n    return () => {\n      isCurrent = false\n    }\n  }, [url])\n  async function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {\n    const file = event.target.files?.[0]\n    event.target.value = \"\"\n    if (!file) return\n    const buffer = await file.arrayBuffer()\n    setWorkbookBuffer(null)\n    setLoadError(undefined)\n    setUploadedWorkbook({\n      buffer,\n      fileName: file.name,\n      identity: `${file.name}-${file.size}-${file.lastModified}`,\n    })\n  }\n  const activeBuffer = uploadedWorkbook?.buffer ?? workbookBuffer\n  if (!url && !uploadedWorkbook) {\n    return (\n      <div\n        className={cn(\n          \"flex h-[640px] min-h-0 flex-col overflow-hidden bg-background\",\n          className\n        )}\n      >\n        <input\n          ref={fileInputRef}\n          type=\"file\"\n          accept=\".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel\"\n          className=\"hidden\"\n          onChange={handleUpload}\n        />\n        <div className=\"grid min-h-0 flex-1 place-items-center bg-muted/30 p-4\">\n          <div className=\"max-w-md rounded-lg border bg-background p-4 text-center text-sm shadow-xs\">\n            <p className=\"font-medium\">Upload a workbook to edit</p>\n            <p className=\"mt-1 text-muted-foreground\">\n              Pass an XLSX URL with the <code>src</code> prop or upload a local\n              file.\n            </p>\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"mt-4\"\n              onClick={() => fileInputRef.current?.click()}\n            >\n              <IconPlaceholder\n                lucide=\"Upload\"\n                tabler=\"IconUpload\"\n                hugeicons=\"Upload01Icon\"\n                phosphor=\"UploadSimpleIcon\"\n                remixicon=\"RiUpload2Line\"\n                className=\"size-4\"\n              />\n              Upload XLSX\n            </Button>\n          </div>\n        </div>\n      </div>\n    )\n  }\n  if (loadError && !activeBuffer) {\n    return (\n      <div\n        className={cn(\n          \"flex h-[640px] min-h-0 flex-col overflow-hidden bg-background\",\n          className\n        )}\n      >\n        <input\n          ref={fileInputRef}\n          type=\"file\"\n          accept=\".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel\"\n          className=\"hidden\"\n          onChange={handleUpload}\n        />\n        <div className=\"grid min-h-0 flex-1 place-items-center bg-muted/30 p-4\">\n          <div className=\"max-w-md rounded-lg border bg-background p-4 text-sm\">\n            <p className=\"font-medium\">Unable to edit workbook</p>\n            <p className=\"mt-1 text-muted-foreground\">{loadError}</p>\n            <Button\n              type=\"button\"\n              variant=\"outline\"\n              size=\"sm\"\n              className=\"mt-4\"\n              onClick={() => fileInputRef.current?.click()}\n            >\n              <IconPlaceholder\n                lucide=\"Upload\"\n                tabler=\"IconUpload\"\n                hugeicons=\"Upload01Icon\"\n                phosphor=\"UploadSimpleIcon\"\n                remixicon=\"RiUpload2Line\"\n                className=\"size-4\"\n              />\n              Upload XLSX\n            </Button>\n          </div>\n        </div>\n      </div>\n    )\n  }\n  if (!activeBuffer) {\n    return (\n      <div\n        className={cn(\n          \"flex h-[640px] min-h-0 flex-col overflow-hidden bg-background\",\n          className\n        )}\n      >\n        <input\n          ref={fileInputRef}\n          type=\"file\"\n          accept=\".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel\"\n          className=\"hidden\"\n          onChange={handleUpload}\n        />\n        <EditorLoadingSurface showSpinner={shouldShowLoadingSpinner} />\n      </div>\n    )\n  }\n  return (\n    <div className={cn(\"overflow-hidden\", className)}>\n      <input\n        ref={fileInputRef}\n        type=\"file\"\n        accept=\".xlsx,.xls,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel\"\n        className=\"hidden\"\n        onChange={handleUpload}\n      />\n      <XlsxWorkbookLoadedEditor\n        fileName={displayFileName}\n        isDark={effectiveIsDark}\n        onIsDarkChange={setNightRenderEnabled}\n        onUploadClick={() => fileInputRef.current?.click()}\n        renderTableHeaderMenu={(props) => (\n          <WorkbookTableHeaderMenu {...props} />\n        )}\n        showNightRenderToggle={shouldRenderNightMode}\n        workbookBuffer={activeBuffer}\n        workbookIdentity={workbookIdentity}\n      />\n    </div>\n  )\n}\nfunction XlsxWorkbookLoadedEditor({\n  fileName,\n  isDark,\n  onIsDarkChange,\n  onUploadClick,\n  renderTableHeaderMenu,\n  showNightRenderToggle,\n  workbookBuffer,\n  workbookIdentity,\n}: {\n  fileName: string\n  isDark: boolean\n  onIsDarkChange: (checked: boolean) => void\n  onUploadClick: () => void\n  renderTableHeaderMenu: (\n    props: XlsxTableHeaderMenuRenderProps\n  ) => React.ReactNode\n  showNightRenderToggle: boolean\n  workbookBuffer: ArrayBuffer\n  workbookIdentity: string\n}) {\n  const controller = useXlsxViewerController(\n    React.useMemo(\n      () => ({\n        allowResizeInReadOnly: true,\n        file: workbookBuffer,\n        fileName,\n        readOnly: false,\n        readOnlyAboveBytes: XLSX_EDITOR_READ_ONLY_THRESHOLD_BYTES,\n        useWorker: true,\n      }),\n      [fileName, workbookBuffer]\n    )\n  )\n  return (\n    <XlsxViewerProvider controller={controller} isDark={isDark}>\n      <XlsxEditorSurface\n        isDark={isDark}\n        onIsDarkChange={onIsDarkChange}\n        onUploadClick={onUploadClick}\n        renderTableHeaderMenu={renderTableHeaderMenu}\n        showNightRenderToggle={showNightRenderToggle}\n        workbookIdentity={workbookIdentity}\n      />\n    </XlsxViewerProvider>\n  )\n}\ntype InlineRegistryIconProps = Omit<\n  React.ComponentProps<\"svg\">,\n  \"children\" | \"strokeWidth\"\n> & { strokeWidth?: number }\nfunction InlineSpinner({ className, ...props }: InlineRegistryIconProps) {\n  return (\n    <IconPlaceholder\n      lucide=\"LoaderCircle\"\n      tabler=\"IconLoader2\"\n      hugeicons=\"Loading03Icon\"\n      phosphor=\"CircleNotchIcon\"\n      remixicon=\"RiLoader4Line\"\n      role=\"status\"\n      aria-label=\"Loading\"\n      className={cn(\"size-4 animate-spin\", className)}\n      {...props}\n    />\n  )\n}\n",
      "type": "registry:ui",
      "target": "@components/extend/xlsx-editor.tsx"
    }
  ],
  "categories": [
    "documents"
  ],
  "type": "registry:ui"
}
