All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.dist.index.js Maven / Gradle / Ivy

The newest version!
'use strict';

var anatomy$1 = require('@zag-js/anatomy');
var domQuery = require('@zag-js/dom-query');
var i18nUtils = require('@zag-js/i18n-utils');
var types = require('@zag-js/types');
var fileUtils = require('@zag-js/file-utils');
var core = require('@zag-js/core');
var utils = require('@zag-js/utils');

// src/file-upload.anatomy.ts
var anatomy = anatomy$1.createAnatomy("file-upload").parts(
  "root",
  "dropzone",
  "item",
  "itemDeleteTrigger",
  "itemGroup",
  "itemName",
  "itemPreview",
  "itemPreviewImage",
  "itemSizeText",
  "label",
  "trigger",
  "clearTrigger"
);
var parts = anatomy.build();
var dom = domQuery.createScope({
  getRootId: (ctx) => ctx.ids?.root ?? `file:${ctx.id}`,
  getDropzoneId: (ctx) => ctx.ids?.dropzone ?? `file:${ctx.id}:dropzone`,
  getHiddenInputId: (ctx) => ctx.ids?.hiddenInput ?? `file:${ctx.id}:input`,
  getTriggerId: (ctx) => ctx.ids?.trigger ?? `file:${ctx.id}:trigger`,
  getLabelId: (ctx) => ctx.ids?.label ?? `file:${ctx.id}:label`,
  getItemId: (ctx, id) => ctx.ids?.item?.(id) ?? `file:${ctx.id}:item:${id}`,
  getItemNameId: (ctx, id) => ctx.ids?.itemName?.(id) ?? `file:${ctx.id}:item-name:${id}`,
  getItemSizeTextId: (ctx, id) => ctx.ids?.itemSizeText?.(id) ?? `file:${ctx.id}:item-size:${id}`,
  getItemPreviewId: (ctx, id) => ctx.ids?.itemPreview?.(id) ?? `file:${ctx.id}:item-preview:${id}`,
  getHiddenInputEl: (ctx) => dom.getById(ctx, dom.getHiddenInputId(ctx)),
  getDropzoneEl: (ctx) => dom.getById(ctx, dom.getDropzoneId(ctx))
});
function isEventWithFiles(event) {
  const target = domQuery.getEventTarget(event);
  if (!event.dataTransfer) return !!target && "files" in target;
  return event.dataTransfer.types.some((type) => {
    return type === "Files" || type === "application/x-moz-file";
  });
}
function isFilesWithinRange(ctx, incomingCount) {
  if (!ctx.multiple && incomingCount > 1) return false;
  if (!ctx.multiple && incomingCount + ctx.acceptedFiles.length === 2) return true;
  if (incomingCount + ctx.acceptedFiles.length > ctx.maxFiles) return false;
  return true;
}
function getFilesFromEvent(ctx, files) {
  const acceptedFiles = [];
  const rejectedFiles = [];
  files.forEach((file) => {
    const [accepted, acceptError] = fileUtils.isValidFileType(file, ctx.acceptAttr);
    const [sizeMatch, sizeError] = fileUtils.isValidFileSize(file, ctx.minFileSize, ctx.maxFileSize);
    const validateErrors = ctx.validate?.(file);
    const valid = validateErrors ? validateErrors.length === 0 : true;
    if (accepted && sizeMatch && valid) {
      acceptedFiles.push(file);
    } else {
      const errors = [acceptError, sizeError];
      if (!valid) errors.push(...validateErrors ?? []);
      rejectedFiles.push({ file, errors: errors.filter(Boolean) });
    }
  });
  if (!isFilesWithinRange(ctx, acceptedFiles.length)) {
    acceptedFiles.forEach((file) => {
      rejectedFiles.push({ file, errors: ["TOO_MANY_FILES"] });
    });
    acceptedFiles.splice(0);
  }
  return {
    acceptedFiles,
    rejectedFiles
  };
}

// src/file-upload.connect.ts
function connect(state, send, normalize) {
  const disabled = state.context.disabled;
  const allowDrop = state.context.allowDrop;
  const translations = state.context.translations;
  const dragging = state.matches("dragging");
  const focused = state.matches("focused") && !disabled;
  return {
    dragging,
    focused,
    openFilePicker() {
      send("OPEN");
    },
    deleteFile(file) {
      send({ type: "FILE.DELETE", file });
    },
    acceptedFiles: state.context.acceptedFiles,
    rejectedFiles: state.context.rejectedFiles,
    setFiles(files) {
      const count = files.length;
      send({ type: "FILES.SET", files, count });
    },
    clearRejectedFiles() {
      send({ type: "REJECTED_FILES.CLEAR" });
    },
    clearFiles() {
      send({ type: "FILES.CLEAR" });
    },
    getFileSize(file) {
      return i18nUtils.formatBytes(file.size, state.context.locale);
    },
    createFileUrl(file, cb) {
      const win = dom.getWin(state.context);
      const url = win.URL.createObjectURL(file);
      cb(url);
      return () => win.URL.revokeObjectURL(url);
    },
    getRootProps() {
      return normalize.element({
        ...parts.root.attrs,
        dir: state.context.dir,
        id: dom.getRootId(state.context),
        "data-disabled": domQuery.dataAttr(disabled),
        "data-dragging": domQuery.dataAttr(dragging)
      });
    },
    getDropzoneProps() {
      return normalize.element({
        ...parts.dropzone.attrs,
        dir: state.context.dir,
        id: dom.getDropzoneId(state.context),
        tabIndex: disabled ? void 0 : 0,
        role: "button",
        "aria-label": translations.dropzone,
        "aria-disabled": disabled,
        "data-invalid": domQuery.dataAttr(state.context.invalid),
        "data-disabled": domQuery.dataAttr(disabled),
        "data-dragging": domQuery.dataAttr(dragging),
        onKeyDown(event) {
          if (event.defaultPrevented) return;
          if (!domQuery.isSelfTarget(event)) return;
          if (event.key !== "Enter" && event.key !== " ") return;
          send({ type: "DROPZONE.CLICK", src: "keydown" });
        },
        onClick(event) {
          const isLabel = event.currentTarget.localName === "label";
          if (isLabel) event.preventDefault();
          send("DROPZONE.CLICK");
        },
        onDragOver(event) {
          if (!allowDrop) return;
          event.preventDefault();
          event.stopPropagation();
          try {
            event.dataTransfer.dropEffect = "copy";
          } catch {
          }
          const hasFiles = isEventWithFiles(event);
          if (!hasFiles) return;
          const count = event.dataTransfer.items.length;
          send({ type: "DROPZONE.DRAG_OVER", count });
        },
        onDragLeave(event) {
          if (!allowDrop || disabled) return;
          if (domQuery.contains(event.currentTarget, event.relatedTarget)) return;
          send({ type: "DROPZONE.DRAG_LEAVE" });
        },
        onDrop(event) {
          if (allowDrop) {
            event.preventDefault();
            event.stopPropagation();
          }
          const hasFiles = isEventWithFiles(event);
          if (disabled || !hasFiles) return;
          send({ type: "DROPZONE.DROP", files: Array.from(event.dataTransfer.files) });
        },
        onFocus() {
          send("DROPZONE.FOCUS");
        },
        onBlur() {
          send("DROPZONE.BLUR");
        }
      });
    },
    getTriggerProps() {
      return normalize.button({
        ...parts.trigger.attrs,
        dir: state.context.dir,
        id: dom.getTriggerId(state.context),
        disabled,
        "data-disabled": domQuery.dataAttr(disabled),
        "data-invalid": domQuery.dataAttr(state.context.invalid),
        type: "button",
        onClick(event) {
          if (disabled) return;
          if (domQuery.contains(dom.getDropzoneEl(state.context), event.currentTarget)) {
            event.stopPropagation();
          }
          send("OPEN");
        }
      });
    },
    getHiddenInputProps() {
      return normalize.input({
        id: dom.getHiddenInputId(state.context),
        tabIndex: -1,
        disabled,
        type: "file",
        required: state.context.required,
        capture: state.context.capture,
        name: state.context.name,
        accept: state.context.acceptAttr,
        webkitdirectory: state.context.directory ? "" : void 0,
        multiple: state.context.multiple || state.context.maxFiles > 1,
        onClick(event) {
          event.stopPropagation();
          event.currentTarget.value = "";
        },
        onChange(event) {
          if (disabled) return;
          const { files } = event.currentTarget;
          send({ type: "FILES.SET", files: files ? Array.from(files) : [] });
        },
        style: domQuery.visuallyHiddenStyle
      });
    },
    getItemGroupProps() {
      return normalize.element({
        ...parts.itemGroup.attrs,
        dir: state.context.dir,
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getItemProps(props2) {
      const { file } = props2;
      return normalize.element({
        ...parts.item.attrs,
        dir: state.context.dir,
        id: dom.getItemId(state.context, file.name),
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getItemNameProps(props2) {
      const { file } = props2;
      return normalize.element({
        ...parts.itemName.attrs,
        dir: state.context.dir,
        id: dom.getItemNameId(state.context, file.name),
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getItemSizeTextProps(props2) {
      const { file } = props2;
      return normalize.element({
        ...parts.itemSizeText.attrs,
        dir: state.context.dir,
        id: dom.getItemSizeTextId(state.context, file.name),
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getItemPreviewProps(props2) {
      const { file } = props2;
      return normalize.element({
        ...parts.itemPreview.attrs,
        dir: state.context.dir,
        id: dom.getItemPreviewId(state.context, file.name),
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getItemPreviewImageProps(props2) {
      const { file, url } = props2;
      const isImage = file.type.startsWith("image/");
      if (!isImage) {
        throw new Error("Preview Image is only supported for image files");
      }
      return normalize.img({
        ...parts.itemPreviewImage.attrs,
        alt: translations.itemPreview?.(file),
        src: url,
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getItemDeleteTriggerProps(props2) {
      const { file } = props2;
      return normalize.button({
        ...parts.itemDeleteTrigger.attrs,
        dir: state.context.dir,
        type: "button",
        disabled,
        "data-disabled": domQuery.dataAttr(disabled),
        "aria-label": translations.deleteFile?.(file),
        onClick() {
          if (disabled) return;
          send({ type: "FILE.DELETE", file });
        }
      });
    },
    getLabelProps() {
      return normalize.label({
        ...parts.label.attrs,
        dir: state.context.dir,
        id: dom.getLabelId(state.context),
        htmlFor: dom.getHiddenInputId(state.context),
        "data-disabled": domQuery.dataAttr(disabled)
      });
    },
    getClearTriggerProps() {
      return normalize.button({
        ...parts.clearTrigger.attrs,
        dir: state.context.dir,
        type: "button",
        disabled,
        hidden: state.context.acceptedFiles.length === 0,
        "data-disabled": domQuery.dataAttr(disabled),
        onClick(event) {
          if (event.defaultPrevented) return;
          if (disabled) return;
          send({ type: "FILES.CLEAR" });
        }
      });
    }
  };
}
function machine(userContext) {
  const ctx = utils.compact(userContext);
  return core.createMachine(
    {
      id: "fileupload",
      initial: "idle",
      context: {
        minFileSize: 0,
        maxFileSize: Infinity,
        maxFiles: 1,
        allowDrop: true,
        ...ctx,
        acceptedFiles: core.ref([]),
        rejectedFiles: core.ref([]),
        translations: {
          dropzone: "dropzone",
          itemPreview: (file) => `preview of ${file.name}`,
          deleteFile: (file) => `delete file ${file.name}`,
          ...ctx.translations
        }
      },
      computed: {
        acceptAttr: (ctx2) => fileUtils.getAcceptAttrString(ctx2.accept),
        multiple: (ctx2) => ctx2.maxFiles > 1
      },
      on: {
        "FILES.SET": {
          actions: ["setFilesFromEvent"]
        },
        "FILE.DELETE": {
          actions: ["removeFile"]
        },
        "FILES.CLEAR": {
          actions: ["clearFiles"]
        },
        "REJECTED_FILES.CLEAR": {
          actions: ["clearRejectedFiles"]
        }
      },
      states: {
        idle: {
          on: {
            OPEN: {
              actions: ["openFilePicker"]
            },
            "DROPZONE.CLICK": {
              actions: ["openFilePicker"]
            },
            "DROPZONE.FOCUS": "focused",
            "DROPZONE.DRAG_OVER": {
              target: "dragging"
            }
          }
        },
        focused: {
          on: {
            "DROPZONE.BLUR": "idle",
            OPEN: {
              actions: ["openFilePicker"]
            },
            "DROPZONE.CLICK": {
              actions: ["openFilePicker"]
            },
            "DROPZONE.DRAG_OVER": {
              target: "dragging"
            }
          }
        },
        dragging: {
          on: {
            "DROPZONE.DROP": {
              target: "idle",
              actions: ["setFilesFromEvent", "syncInputElement"]
            },
            "DROPZONE.DRAG_LEAVE": {
              target: "idle"
            }
          }
        }
      }
    },
    {
      actions: {
        syncInputElement(ctx2) {
          const inputEl = dom.getHiddenInputEl(ctx2);
          if (!inputEl) return;
          const win = dom.getWin(ctx2);
          const dataTransfer = new win.DataTransfer();
          ctx2.acceptedFiles.forEach((v) => {
            dataTransfer.items.add(v);
          });
          inputEl.files = dataTransfer.files;
        },
        openFilePicker(ctx2) {
          domQuery.raf(() => {
            dom.getHiddenInputEl(ctx2)?.click();
          });
        },
        setFilesFromEvent(ctx2, evt) {
          const result = getFilesFromEvent(ctx2, evt.files);
          const { acceptedFiles, rejectedFiles } = result;
          if (ctx2.multiple) {
            const files = core.ref([...ctx2.acceptedFiles, ...acceptedFiles]);
            set.files(ctx2, files, rejectedFiles);
            return;
          }
          if (acceptedFiles.length) {
            const files = core.ref([acceptedFiles[0]]);
            set.files(ctx2, files, rejectedFiles);
          } else if (rejectedFiles.length) {
            set.files(ctx2, ctx2.acceptedFiles, rejectedFiles);
          }
        },
        removeFile(ctx2, evt) {
          const nextFiles = ctx2.acceptedFiles.filter((file) => file !== evt.file);
          ctx2.acceptedFiles = core.ref(nextFiles);
          invoke.change(ctx2);
        },
        clearRejectedFiles(ctx2) {
          ctx2.rejectedFiles = core.ref([]);
          invoke.change(ctx2);
        },
        clearFiles(ctx2) {
          ctx2.acceptedFiles = core.ref([]);
          ctx2.rejectedFiles = core.ref([]);
          invoke.change(ctx2);
        }
      },
      compareFns: {
        acceptedFiles: (a, b) => a.length === b.length && a.every((file, i) => fileUtils.isFileEqual(file, b[i]))
      }
    }
  );
}
var invoke = {
  change: (ctx) => {
    ctx.onFileChange?.({
      acceptedFiles: ctx.acceptedFiles,
      rejectedFiles: ctx.rejectedFiles
    });
  },
  accept: (ctx) => {
    ctx.onFileAccept?.({ files: ctx.acceptedFiles });
  },
  reject: (ctx) => {
    ctx.onFileReject?.({ files: ctx.rejectedFiles });
  }
};
var set = {
  files: (ctx, acceptedFiles, rejectedFiles) => {
    ctx.acceptedFiles = core.ref(acceptedFiles);
    invoke.accept(ctx);
    if (rejectedFiles) {
      ctx.rejectedFiles = core.ref(rejectedFiles);
      invoke.reject(ctx);
    }
    invoke.change(ctx);
  }
};
var props = types.createProps()([
  "accept",
  "allowDrop",
  "capture",
  "dir",
  "directory",
  "disabled",
  "getRootNode",
  "id",
  "ids",
  "locale",
  "maxFiles",
  "maxFileSize",
  "minFileSize",
  "name",
  "invalid",
  "onFileAccept",
  "onFileReject",
  "onFileChange",
  "required",
  "translations",
  "validate"
]);
var splitProps = utils.createSplitProps(props);
var itemProps = types.createProps()(["file"]);
var splitItemProps = utils.createSplitProps(itemProps);

exports.anatomy = anatomy;
exports.connect = connect;
exports.itemProps = itemProps;
exports.machine = machine;
exports.props = props;
exports.splitItemProps = splitItemProps;
exports.splitProps = splitProps;




© 2015 - 2025 Weber Informatics LLC | Privacy Policy