{"version":3,"file":"InputFileMultiplyAppendable.cjs","sources":["../../../src/components/InputFile/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n  type ChangeEvent,\n  type MouseEvent,\n  type ReactNode,\n  forwardRef,\n  memo,\n  useCallback,\n  useId,\n  useImperativeHandle,\n  useMemo,\n  useRef,\n  useState,\n} from 'react'\n\nimport { useDecorators } from '../../hooks/useDecorators'\nimport { useIntl } from '../../intl'\nimport { BaseColumn } from '../Base'\nimport { Button } from '../Button'\nimport { FaFolderOpenIcon, FaTrashCanIcon } from '../Icon'\nimport { Stack } from '../Layout'\n\nimport { classNameGenerator } from './style'\n\nimport type { DecoratorKeyTypes, Props } from './types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<HTMLInputElement, Omit<Props, 'multiple'>>(\n  (\n    {\n      className,\n      size,\n      label,\n      hasFileList = true,\n      onChange,\n      disabled = false,\n      error,\n      decorators,\n      ...rest\n    },\n    ref,\n  ) => {\n    const [files, setFiles] = useState<File[]>([])\n    const labelId = useId()\n    const { localize } = useIntl()\n\n    const decoratorDefaultTexts = useMemo(\n      () => ({\n        destroy: localize({\n          id: 'smarthr-ui/InputFile/destroy',\n          defaultText: '削除',\n        }),\n      }),\n      [localize],\n    )\n\n    const decorated = useDecorators<DecoratorKeyTypes>(decoratorDefaultTexts, decorators)\n\n    const classNames = useMemo(() => {\n      const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n      return {\n        wrapper: wrapper({ className }),\n        inputWrapper: inputWrapper({ size, disabled }),\n        fileList: fileList(),\n        fileItem: fileItem(),\n        input: input(),\n        prefix: prefix(),\n      }\n    }, [disabled, size, className])\n\n    // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n    const isUpdatingFilesDirectly = useRef(false)\n\n    const inputRef = useRef<HTMLInputElement>(null)\n    useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(\n      ref,\n      () => inputRef.current,\n    )\n\n    const updateInputFiles = useCallback(\n      (newFiles: File[]) => {\n        if (!inputRef.current) {\n          return\n        }\n        const buff = new DataTransfer()\n        newFiles.forEach((file) => {\n          buff.items.add(file)\n        })\n\n        isUpdatingFilesDirectly.current = true\n        inputRef.current.files = buff.files\n        isUpdatingFilesDirectly.current = false\n      },\n      [inputRef],\n    )\n\n    const updateFiles = useMemo(\n      () =>\n        onChange\n          ? (newFiles: File[]) => {\n              onChange(newFiles)\n              updateInputFiles(newFiles)\n              setFiles(newFiles)\n            }\n          : (newFiles: File[]) => {\n              setFiles(newFiles)\n              updateInputFiles(newFiles)\n            },\n      [onChange, updateInputFiles],\n    )\n\n    const handleChange = useCallback(\n      (e: ChangeEvent<HTMLInputElement>) => {\n        // Safari において、input.files への直接代入時はonChangeを発火させない\n        if (isUpdatingFilesDirectly.current) {\n          return\n        }\n\n        const newFiles = Array.from(e.target.files ?? [])\n\n        updateFiles([...files, ...newFiles])\n      },\n      [files, isUpdatingFilesDirectly, updateFiles],\n    )\n\n    const handleDelete = useCallback(\n      (e: MouseEvent<HTMLButtonElement>) => {\n        if (!inputRef.current) {\n          return\n        }\n\n        const index = parseInt(e.currentTarget.value, 10)\n        const newFiles = files.filter((_, i) => index !== i)\n\n        // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n        inputRef.current.value = ''\n\n        updateFiles(newFiles)\n      },\n      [files, updateFiles],\n    )\n\n    return (\n      <Stack align=\"flex-start\" className={classNames.wrapper}>\n        {!disabled && hasFileList && files.length > 0 && (\n          <BaseColumn as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n            {files.map((file, index) => (\n              <li key={index} className={classNames.fileItem}>\n                <span className=\"smarthr-ui-InputFile-fileName\">{file.name}</span>\n                <Button\n                  variant=\"text\"\n                  prefix={<FaTrashCanIcon />}\n                  value={index}\n                  onClick={handleDelete}\n                  className=\"smarthr-ui-InputFile-deleteButton\"\n                >\n                  {decorated.destroy}\n                </Button>\n              </li>\n            ))}\n          </BaseColumn>\n        )}\n        <span className={classNames.inputWrapper}>\n          <input\n            {...rest}\n            multiple\n            data-smarthr-ui-input=\"true\"\n            type=\"file\"\n            onChange={handleChange}\n            disabled={disabled}\n            ref={inputRef}\n            aria-invalid={error || undefined}\n            aria-labelledby={labelId}\n            className={classNames.input}\n          />\n          <StyledFaFolderOpenIcon className={classNames.prefix} />\n          <LabelRender id={labelId} label={label} />\n        </span>\n      </Stack>\n    )\n  },\n)\n\nconst StyledFaFolderOpenIcon = memo<{ className: string }>(({ className }) => (\n  <span className={className}>\n    <FaFolderOpenIcon />\n  </span>\n))\n\nconst LabelRender = memo<{ id: string; label: ReactNode }>(({ id, label }) => (\n  <span id={id} aria-hidden=\"true\">\n    {label}\n  </span>\n))\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA;AAEO;;AAgBH;AACA;AAEA;;AAGM;AACA;;AAEH;;AAMH;AACE;;AAGE;;;;;;;;;AAUJ;AAEA;;AAMA;AAEI;;;AAGA;AACA;AACE;AACF;AAEA;;AAEA;AACF;AAIF;AAGM;;;;;AAKA;;;AAGE;AAIR;;AAGI;;;AAIA;;;AAOJ;AAEI;;;AAIA;AACA;;AAGA;;AAGF;AAIF;AAsCF;AAGF;AAMA;;"}