{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "edit-profile",
  "type": "registry:ui",
  "title": "Edit Profile Dialog",
  "description": "A responsive profile editing dialog with file upload and form inputs - drawer on mobile, modal on desktop.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "input",
    "label",
    "textarea",
    "https://revola.sameerjs.com/r/revola.json"
  ],
  "files": [
    {
      "path": "registry/revola/examples/origin-ui/forms/user-profile/edit-profile.tsx",
      "content": "\"use client\";\n\nimport { useId } from \"react\";\nimport Image from \"next/image\";\n\nimport { CheckIcon, ImagePlusIcon, XIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n  ResponsiveDialog,\n  ResponsiveDialogClose,\n  ResponsiveDialogContent,\n  ResponsiveDialogDescription,\n  ResponsiveDialogFooter,\n  ResponsiveDialogHeader,\n  ResponsiveDialogTitle,\n  ResponsiveDialogTrigger,\n} from \"@/components/ui/revola\";\nimport { Textarea } from \"@/components/ui/textarea\";\n\nimport { useCharacterLimit } from \"@/hooks/use-character-limit\";\nimport { useFileUpload } from \"@/hooks/use-file-upload\";\n\n// Pretend we have initial image files\nconst initialBgImage = [\n  {\n    name: \"extended-gradient-bg.png\",\n    size: 1528737,\n    type: \"image/png\",\n    url: \"/extended-gradient.png\",\n    id: \"profile-bg-123456789\",\n  },\n];\n\nconst initialAvatarImage = [\n  {\n    name: \"original-avatar.jpg\",\n    size: 1528737,\n    type: \"image/jpeg\",\n    url: \"/original-avatar.jpg\",\n    id: \"avatar-123456789\",\n  },\n];\n\nexport default function EditProfileDialog() {\n  const id = useId();\n\n  const maxLength = 180;\n  const {\n    value,\n    characterCount,\n    handleChange,\n    maxLength: limit,\n  } = useCharacterLimit({\n    maxLength,\n    initialValue: \"Hey, I am Charlet, a Product Manager who loves building amazing products that delight users!\",\n  });\n\n  return (\n    <ResponsiveDialog>\n      <ResponsiveDialogTrigger asChild>\n        <Button variant=\"outline\" className=\"h-12 rounded-full px-6 capitalize\">\n          Edit profile\n        </Button>\n      </ResponsiveDialogTrigger>\n      <ResponsiveDialogContent className=\"flex flex-col gap-0\">\n        <ResponsiveDialogHeader className=\"space-y-0 !p-0 text-left\">\n          <ResponsiveDialogTitle className=\"border-b px-6 py-4 text-base max-sm:pt-2\">\n            Edit profile\n          </ResponsiveDialogTitle>\n        </ResponsiveDialogHeader>\n        <ResponsiveDialogDescription className=\"sr-only\">\n          Make changes to your profile here. You can change your photo and set a username.\n        </ResponsiveDialogDescription>\n\n        <div className=\"overflow-y-auto\">\n          <ProfileBg />\n          <Avatar />\n          <div className=\"px-6 pb-6 pt-4\">\n            <form className=\"space-y-4\">\n              <div className=\"flex flex-col gap-4 sm:flex-row\">\n                <div className=\"flex-1 space-y-2\">\n                  <Label htmlFor={`${id}-first-name`}>First name</Label>\n                  <Input\n                    id={`${id}-first-name`}\n                    placeholder=\"Matt\"\n                    defaultValue=\"Charlet\"\n                    type=\"text\"\n                    required\n                    className=\"[&:-webkit-autofill]:shadow-[0_0_0_30px_theme(colors.input)_inset]\"\n                  />\n                </div>\n                <div className=\"flex-1 space-y-2\">\n                  <Label htmlFor={`${id}-last-name`}>Last name</Label>\n                  <Input\n                    id={`${id}-last-name`}\n                    placeholder=\"Welsh\"\n                    defaultValue=\"Anderson\"\n                    type=\"text\"\n                    required\n                    className=\"[&:-webkit-autofill]:shadow-[0_0_0_30px_theme(colors.input)_inset]\"\n                  />\n                </div>\n              </div>\n              <div className=\"space-y-2\">\n                <Label htmlFor={`${id}-username`}>Username</Label>\n                <div className=\"relative\">\n                  <Input\n                    id={`${id}-username`}\n                    className=\"peer pe-9 [&:-webkit-autofill]:shadow-[0_0_0_30px_theme(colors.input)_inset]\"\n                    placeholder=\"Username\"\n                    defaultValue=\"charlet-pm-69\"\n                    type=\"text\"\n                    required\n                  />\n                  <div className=\"pointer-events-none absolute inset-y-0 end-0 flex items-center justify-center pe-3 text-muted-foreground/80 peer-disabled:opacity-50\">\n                    <CheckIcon size={16} className=\"text-emerald-500\" aria-hidden=\"true\" />\n                  </div>\n                </div>\n              </div>\n              <div className=\"space-y-2\">\n                <Label htmlFor={`${id}-website`}>Website</Label>\n                <div className=\"shadow-xs flex rounded-md\">\n                  <span className=\"-z-10 inline-flex items-center rounded-s-md border border-input bg-background px-3 text-sm text-muted-foreground\">\n                    https://\n                  </span>\n                  <Input\n                    type=\"text\"\n                    id={`${id}-website`}\n                    placeholder=\"yourwebsite.com\"\n                    defaultValue=\"www.charletanderson.com\"\n                    className=\"-ms-px rounded-s-none shadow-none [&:-webkit-autofill]:shadow-[0_0_0_30px_theme(colors.input)_inset]\"\n                  />\n                </div>\n              </div>\n              <div className=\"space-y-2\">\n                <Label htmlFor={`${id}-bio`}>Biography</Label>\n                <Textarea\n                  id={`${id}-bio`}\n                  value={value}\n                  maxLength={maxLength}\n                  onChange={handleChange}\n                  placeholder=\"Write a few sentences about yourself\"\n                  className=\"[&:-webkit-autofill]:shadow-[0_0_0_30px_theme(colors.input)_inset]\"\n                  aria-describedby={`${id}-description`}\n                />\n                <p\n                  id={`${id}-description`}\n                  className=\"mt-2 text-right text-xs text-muted-foreground\"\n                  role=\"status\"\n                  aria-live=\"polite\"\n                >\n                  <span className=\"tabular-nums\">{limit - characterCount}</span> characters left\n                </p>\n              </div>\n            </form>\n          </div>\n        </div>\n        <ResponsiveDialogFooter className=\"flex-col-reverse border-t sm:px-6 sm:py-4\">\n          <ResponsiveDialogClose asChild>\n            <Button type=\"button\" variant=\"outline\">\n              Cancel\n            </Button>\n          </ResponsiveDialogClose>\n          <ResponsiveDialogClose asChild>\n            <Button type=\"button\">Save changes</Button>\n          </ResponsiveDialogClose>\n        </ResponsiveDialogFooter>\n      </ResponsiveDialogContent>\n    </ResponsiveDialog>\n  );\n}\n\nfunction ProfileBg() {\n  const [{ files }, { removeFile, openFileDialog, getInputProps }] = useFileUpload({\n    accept: \"image/*\",\n    initialFiles: initialBgImage,\n  });\n\n  const currentImage = files[0]?.preview || null;\n\n  return (\n    <div className=\"h-32\">\n      <div className=\"relative flex size-full items-center justify-center overflow-hidden bg-muted\">\n        {currentImage && (\n          <img\n            className=\"size-full object-cover\"\n            src={currentImage}\n            alt={files[0]?.preview ? \"Preview of uploaded image\" : \"Default profile background\"}\n            width={512}\n            height={96}\n          />\n        )}\n        <div className=\"absolute inset-0 flex items-center justify-center gap-2\">\n          <button\n            type=\"button\"\n            className=\"z-50 flex size-10 cursor-pointer items-center justify-center rounded-full bg-black/60 text-white outline-none transition-[color,box-shadow] hover:bg-black/80 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n            onClick={openFileDialog}\n            aria-label={currentImage ? \"Change image\" : \"Upload image\"}\n          >\n            <ImagePlusIcon size={16} aria-hidden=\"true\" />\n          </button>\n          {currentImage && (\n            <button\n              type=\"button\"\n              className=\"z-50 flex size-10 cursor-pointer items-center justify-center rounded-full bg-black/60 text-white outline-none transition-[color,box-shadow] hover:bg-black/80 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n              onClick={() => removeFile(files[0]?.id)}\n              aria-label=\"Remove image\"\n            >\n              <XIcon size={16} aria-hidden=\"true\" />\n            </button>\n          )}\n        </div>\n      </div>\n      <input {...getInputProps()} className=\"sr-only\" aria-label=\"Upload image file\" />\n    </div>\n  );\n}\n\nfunction Avatar() {\n  const [{ files }, { openFileDialog, getInputProps }] = useFileUpload({\n    accept: \"image/*\",\n    initialFiles: initialAvatarImage,\n  });\n\n  const currentImage = files[0]?.preview || null;\n\n  return (\n    <div className=\"-mt-10 px-6\">\n      <div className=\"shadow-xs group relative flex size-20 items-center justify-center overflow-hidden rounded-full border-4 border-background bg-muted shadow-black/10\">\n        {currentImage && (\n          <Image src={currentImage} className=\"size-full object-cover\" width={80} height={80} alt=\"Profile image\" />\n        )}\n        <button\n          type=\"button\"\n          className=\"pointer-events-none absolute flex size-8 cursor-pointer items-center justify-center rounded-full bg-black/60 text-white opacity-0 outline-none transition-[color,box-shadow,opacity] hover:bg-black/80 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 group-hover:pointer-events-auto group-hover:opacity-100\"\n          onClick={openFileDialog}\n          aria-label=\"Change profile picture\"\n        >\n          <ImagePlusIcon size={16} aria-hidden=\"true\" />\n        </button>\n        <input {...getInputProps()} className=\"sr-only\" aria-label=\"Upload profile picture\" />\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "hooks/use-character-limit.ts",
      "content": "\"use client\";\r\n\r\nimport { useState, type ChangeEvent } from \"react\";\r\n\r\ntype UseCharacterLimitProps = {\r\n  maxLength: number;\r\n  initialValue?: string;\r\n};\r\n\r\nexport function useCharacterLimit({ maxLength, initialValue = \"\" }: UseCharacterLimitProps) {\r\n  const [value, setValue] = useState(initialValue);\r\n  const [characterCount, setCharacterCount] = useState(initialValue.length);\r\n\r\n  const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {\r\n    const newValue = e.target.value;\r\n    if (newValue.length <= maxLength) {\r\n      setValue(newValue);\r\n      setCharacterCount(newValue.length);\r\n    }\r\n  };\r\n\r\n  return {\r\n    value,\r\n    characterCount,\r\n    handleChange,\r\n    maxLength,\r\n  };\r\n}\r\n",
      "type": "registry:hook"
    },
    {
      "path": "hooks/use-file-upload.ts",
      "content": "\"use client\";\r\n\r\nimport type React from \"react\";\r\nimport { useCallback, useRef, useState, type ChangeEvent, type DragEvent, type InputHTMLAttributes } from \"react\";\r\n\r\nexport type FileMetadata = {\r\n  name: string;\r\n  size: number;\r\n  type: string;\r\n  url: string;\r\n  id: string;\r\n};\r\n\r\nexport type FileWithPreview = {\r\n  file: File | FileMetadata;\r\n  id: string;\r\n  preview?: string;\r\n};\r\n\r\n/**\r\n * Hook by Origin UI https://github.com/origin-space/originui/blob/main/registry/default/hooks/use-file-upload.ts\r\n */\r\nexport type FileUploadOptions = {\r\n  maxFiles?: number; // Only used when multiple is true, defaults to Infinity\r\n  maxSize?: number; // in bytes\r\n  accept?: string;\r\n  multiple?: boolean; // Defaults to false\r\n  initialFiles?: FileMetadata[];\r\n  onFilesChange?: (files: FileWithPreview[]) => void; // Callback when files change\r\n  onFilesAdded?: (addedFiles: FileWithPreview[]) => void; // Callback when new files are added\r\n};\r\n\r\nexport type FileUploadState = {\r\n  files: FileWithPreview[];\r\n  isDragging: boolean;\r\n  errors: string[];\r\n};\r\n\r\nexport type FileUploadActions = {\r\n  addFiles: (files: FileList | File[]) => void;\r\n  removeFile: (id: string) => void;\r\n  clearFiles: () => void;\r\n  clearErrors: () => void;\r\n  handleDragEnter: (e: DragEvent<HTMLElement>) => void;\r\n  handleDragLeave: (e: DragEvent<HTMLElement>) => void;\r\n  handleDragOver: (e: DragEvent<HTMLElement>) => void;\r\n  handleDrop: (e: DragEvent<HTMLElement>) => void;\r\n  handleFileChange: (e: ChangeEvent<HTMLInputElement>) => void;\r\n  openFileDialog: () => void;\r\n  getInputProps: (props?: InputHTMLAttributes<HTMLInputElement>) => InputHTMLAttributes<HTMLInputElement> & {\r\n    ref: React.Ref<HTMLInputElement>;\r\n  };\r\n};\r\n\r\nexport const useFileUpload = (options: FileUploadOptions = {}): [FileUploadState, FileUploadActions] => {\r\n  const {\r\n    maxFiles = Infinity,\r\n    maxSize = Infinity,\r\n    accept = \"*\",\r\n    multiple = false,\r\n    initialFiles = [],\r\n    onFilesChange,\r\n    onFilesAdded,\r\n  } = options;\r\n\r\n  const [state, setState] = useState<FileUploadState>({\r\n    files: initialFiles.map((file) => ({\r\n      file,\r\n      id: file.id,\r\n      preview: file.url,\r\n    })),\r\n    isDragging: false,\r\n    errors: [],\r\n  });\r\n\r\n  const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n  const validateFile = useCallback(\r\n    (file: File | FileMetadata): string | null => {\r\n      if (file instanceof File) {\r\n        if (file.size > maxSize) {\r\n          return `File \"${file.name}\" exceeds the maximum size of ${formatBytes(maxSize)}.`;\r\n        }\r\n      }\r\n\r\n      if (accept !== \"*\") {\r\n        const acceptedTypes = accept.split(\",\").map((type) => type.trim());\r\n        const fileType = file instanceof File ? file.type || \"\" : file.type;\r\n        const fileExtension = `.${file instanceof File ? file.name.split(\".\").pop() : file.name.split(\".\").pop()}`;\r\n\r\n        const isAccepted = acceptedTypes.some((type) => {\r\n          if (type.startsWith(\".\")) {\r\n            return fileExtension.toLowerCase() === type.toLowerCase();\r\n          }\r\n          if (type.endsWith(\"/*\")) {\r\n            const baseType = type.split(\"/\")[0];\r\n            return fileType.startsWith(`${baseType}/`);\r\n          }\r\n          return fileType === type;\r\n        });\r\n\r\n        if (!isAccepted) {\r\n          return `File \"${file.name}\" is not an accepted file type.`;\r\n        }\r\n      }\r\n\r\n      return null;\r\n    },\r\n    [accept, maxSize]\r\n  );\r\n\r\n  const createPreview = useCallback((file: File | FileMetadata): string | undefined => {\r\n    if (file instanceof File) {\r\n      return URL.createObjectURL(file);\r\n    }\r\n    return file.url;\r\n  }, []);\r\n\r\n  const generateUniqueId = useCallback((file: File | FileMetadata): string => {\r\n    if (file instanceof File) {\r\n      return `${file.name}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\r\n    }\r\n    return file.id;\r\n  }, []);\r\n\r\n  const clearFiles = useCallback(() => {\r\n    setState((prev) => {\r\n      // Clean up object URLs\r\n      prev.files.forEach((file) => {\r\n        if (file.preview && file.file instanceof File && file.file.type.startsWith(\"image/\")) {\r\n          URL.revokeObjectURL(file.preview);\r\n        }\r\n      });\r\n\r\n      if (inputRef.current) {\r\n        inputRef.current.value = \"\";\r\n      }\r\n\r\n      const newState = {\r\n        ...prev,\r\n        files: [],\r\n        errors: [],\r\n      };\r\n\r\n      onFilesChange?.(newState.files);\r\n      return newState;\r\n    });\r\n  }, [onFilesChange]);\r\n\r\n  const addFiles = useCallback(\r\n    (newFiles: FileList | File[]) => {\r\n      if (!newFiles || newFiles.length === 0) return;\r\n\r\n      const newFilesArray = Array.from(newFiles);\r\n      const errors: string[] = [];\r\n\r\n      // Clear existing errors when new files are uploaded\r\n      setState((prev) => ({ ...prev, errors: [] }));\r\n\r\n      // In single file mode, clear existing files first\r\n      if (!multiple) {\r\n        clearFiles();\r\n      }\r\n\r\n      // Check if adding these files would exceed maxFiles (only in multiple mode)\r\n      if (multiple && maxFiles !== Infinity && state.files.length + newFilesArray.length > maxFiles) {\r\n        errors.push(`You can only upload a maximum of ${maxFiles} files.`);\r\n        setState((prev) => ({ ...prev, errors }));\r\n        return;\r\n      }\r\n\r\n      const validFiles: FileWithPreview[] = [];\r\n\r\n      newFilesArray.forEach((file) => {\r\n        // Only check for duplicates if multiple files are allowed\r\n        if (multiple) {\r\n          const isDuplicate = state.files.some(\r\n            (existingFile) => existingFile.file.name === file.name && existingFile.file.size === file.size\r\n          );\r\n\r\n          // Skip duplicate files silently\r\n          if (isDuplicate) {\r\n            return;\r\n          }\r\n        }\r\n\r\n        // Check file size\r\n        if (file.size > maxSize) {\r\n          errors.push(\r\n            multiple\r\n              ? `Some files exceed the maximum size of ${formatBytes(maxSize)}.`\r\n              : `File exceeds the maximum size of ${formatBytes(maxSize)}.`\r\n          );\r\n          return;\r\n        }\r\n\r\n        const error = validateFile(file);\r\n        if (error) {\r\n          errors.push(error);\r\n        } else {\r\n          validFiles.push({\r\n            file,\r\n            id: generateUniqueId(file),\r\n            preview: createPreview(file),\r\n          });\r\n        }\r\n      });\r\n\r\n      // Only update state if we have valid files to add\r\n      if (validFiles.length > 0) {\r\n        // Call the onFilesAdded callback with the newly added valid files\r\n        onFilesAdded?.(validFiles);\r\n\r\n        setState((prev) => {\r\n          const newFiles = !multiple ? validFiles : [...prev.files, ...validFiles];\r\n          onFilesChange?.(newFiles);\r\n          return {\r\n            ...prev,\r\n            files: newFiles,\r\n            errors,\r\n          };\r\n        });\r\n      } else if (errors.length > 0) {\r\n        setState((prev) => ({\r\n          ...prev,\r\n          errors,\r\n        }));\r\n      }\r\n\r\n      // Reset input value after handling files\r\n      if (inputRef.current) {\r\n        inputRef.current.value = \"\";\r\n      }\r\n    },\r\n    [\r\n      state.files,\r\n      maxFiles,\r\n      multiple,\r\n      maxSize,\r\n      validateFile,\r\n      createPreview,\r\n      generateUniqueId,\r\n      clearFiles,\r\n      onFilesChange,\r\n      onFilesAdded,\r\n    ]\r\n  );\r\n\r\n  const removeFile = useCallback(\r\n    (id: string) => {\r\n      setState((prev) => {\r\n        const fileToRemove = prev.files.find((file) => file.id === id);\r\n        if (\r\n          fileToRemove &&\r\n          fileToRemove.preview &&\r\n          fileToRemove.file instanceof File &&\r\n          fileToRemove.file.type.startsWith(\"image/\")\r\n        ) {\r\n          URL.revokeObjectURL(fileToRemove.preview);\r\n        }\r\n\r\n        const newFiles = prev.files.filter((file) => file.id !== id);\r\n        onFilesChange?.(newFiles);\r\n\r\n        return {\r\n          ...prev,\r\n          files: newFiles,\r\n          errors: [],\r\n        };\r\n      });\r\n    },\r\n    [onFilesChange]\r\n  );\r\n\r\n  const clearErrors = useCallback(() => {\r\n    setState((prev) => ({\r\n      ...prev,\r\n      errors: [],\r\n    }));\r\n  }, []);\r\n\r\n  const handleDragEnter = useCallback((e: DragEvent<HTMLElement>) => {\r\n    e.preventDefault();\r\n    e.stopPropagation();\r\n    setState((prev) => ({ ...prev, isDragging: true }));\r\n  }, []);\r\n\r\n  const handleDragLeave = useCallback((e: DragEvent<HTMLElement>) => {\r\n    e.preventDefault();\r\n    e.stopPropagation();\r\n\r\n    if (e.currentTarget.contains(e.relatedTarget as Node)) {\r\n      return;\r\n    }\r\n\r\n    setState((prev) => ({ ...prev, isDragging: false }));\r\n  }, []);\r\n\r\n  const handleDragOver = useCallback((e: DragEvent<HTMLElement>) => {\r\n    e.preventDefault();\r\n    e.stopPropagation();\r\n  }, []);\r\n\r\n  const handleDrop = useCallback(\r\n    (e: DragEvent<HTMLElement>) => {\r\n      e.preventDefault();\r\n      e.stopPropagation();\r\n      setState((prev) => ({ ...prev, isDragging: false }));\r\n\r\n      // Don't process files if the input is disabled\r\n      if (inputRef.current?.disabled) {\r\n        return;\r\n      }\r\n\r\n      if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {\r\n        // In single file mode, only use the first file\r\n        if (!multiple) {\r\n          const file = e.dataTransfer.files[0];\r\n          addFiles([file]);\r\n        } else {\r\n          addFiles(e.dataTransfer.files);\r\n        }\r\n      }\r\n    },\r\n    [addFiles, multiple]\r\n  );\r\n\r\n  const handleFileChange = useCallback(\r\n    (e: ChangeEvent<HTMLInputElement>) => {\r\n      if (e.target.files && e.target.files.length > 0) {\r\n        addFiles(e.target.files);\r\n      }\r\n    },\r\n    [addFiles]\r\n  );\r\n\r\n  const openFileDialog = useCallback(() => {\r\n    if (inputRef.current) {\r\n      inputRef.current.click();\r\n    }\r\n  }, []);\r\n\r\n  const getInputProps = useCallback(\r\n    (props: InputHTMLAttributes<HTMLInputElement> = {}) => {\r\n      return {\r\n        ...props,\r\n        type: \"file\" as const,\r\n        onChange: handleFileChange,\r\n        accept: props.accept || accept,\r\n        multiple: props.multiple !== undefined ? props.multiple : multiple,\r\n        ref: inputRef,\r\n      };\r\n    },\r\n    [accept, multiple, handleFileChange]\r\n  );\r\n\r\n  return [\r\n    state,\r\n    {\r\n      addFiles,\r\n      removeFile,\r\n      clearFiles,\r\n      clearErrors,\r\n      handleDragEnter,\r\n      handleDragLeave,\r\n      handleDragOver,\r\n      handleDrop,\r\n      handleFileChange,\r\n      openFileDialog,\r\n      getInputProps,\r\n    },\r\n  ];\r\n};\r\n\r\n// Helper function to format bytes to human-readable format\r\nexport const formatBytes = (bytes: number, decimals = 2): string => {\r\n  if (bytes === 0) return \"0 Bytes\";\r\n\r\n  const k = 1024;\r\n  const dm = decimals < 0 ? 0 : decimals;\r\n  const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\", \"EB\", \"ZB\", \"YB\"];\r\n\r\n  const i = Math.floor(Math.log(bytes) / Math.log(k));\r\n\r\n  return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + sizes[i];\r\n};\r\n",
      "type": "registry:hook"
    }
  ]
}