function pushWithTags(remote, tags, opts) {

keep keep keep keep keep replace replace keep keep

function pushWithTags(remote, opts) {
log.silly("pushWithTags", remote);

"use strict";

const os = require("os");
const chalk = require("chalk");
const dedent = require("dedent");
const minimatch = require("minimatch");
const path = require("path");
const pFinally = require("p-finally");
const pMap = require("p-map");
const pReduce = require("p-reduce");
const pWaterfall = require("p-waterfall");
const semver = require("semver");

const Command = require("@lerna/command");
const ConventionalCommitUtilities = require("@lerna/conventional-commits");
const GitUtilities = require("@lerna/git-utils");
const PromptUtilities = require("@lerna/prompt");
const output = require("@lerna/output");
const collectUpdates = require("@lerna/collect-updates");
const npmConf = require("@lerna/npm-conf");
const npmDistTag = require("@lerna/npm-dist-tag");
const npmPublish = require("@lerna/npm-publish");
const runLifecycle = require("@lerna/run-lifecycle");
const batchPackages = require("@lerna/batch-packages");
const runParallelBatches = require("@lerna/run-parallel-batches");
const ValidationError = require("@lerna/validation-error");

module.exports = factory;

function factory(argv) {
  return new PublishCommand(argv);
}

class PublishCommand extends Command {
  get defaultOptions() {
    return Object.assign({}, super.defaultOptions, {
      conventionalCommits: false,
      exact: false,
      ignoreChanges: [],
      skipGit: false,
      skipNpm: false,
      tempTag: false,
      yes: false,
      allowBranch: false,
    });
  }

  initialize() {
    this.gitRemote = this.options.gitRemote || "origin";
    this.gitEnabled = !(this.options.canary || this.options.skipGit);

    // https://docs.npmjs.com/misc/config#save-prefix
    this.savePrefix = this.options.exact ? "" : "^";

    this.npmConfig = {
      npmClient: this.options.npmClient || "npm",
      registry: this.options.registry,
    };

    if (this.options.canary) {
      this.logger.info("canary", "enabled");
    }

    if (!this.project.isIndependent()) {
      this.logger.info("current version", this.project.version);
    }

    // git validation, if enabled, should happen before updates are calculated and versions picked
    if (this.gitEnabled) {
      if (GitUtilities.isDetachedHead(this.execOpts)) {
        throw new ValidationError(
          "ENOGIT",
          "Detached git HEAD, please checkout a branch to publish changes."
        );
      }

      const currentBranch = GitUtilities.getCurrentBranch(this.execOpts);

      if (
        this.options.allowBranch &&
        ![].concat(this.options.allowBranch).some(x => minimatch(currentBranch, x))
      ) {
        throw new ValidationError(
          "ENOTALLOWED",
          dedent`
            Branch '${currentBranch}' is restricted from publishing due to allowBranch config.
            Please consider the reasons for this restriction before overriding the option.
          `
        );
      }

      if (GitUtilities.isBehindUpstream(this.gitRemote, this.execOpts)) {
        const message = `Local branch '${currentBranch}' is behind remote upstream ${
          this.gitRemote
        }/${currentBranch}`;

        if (!this.options.ci) {
          // interrupt interactive publish
          throw new ValidationError(
            "EBEHIND",
            dedent`
              ${message}
              Please merge remote changes into '${currentBranch}' with 'git pull'
            `
          );
        }

        // CI publish should not error, but warn & exit
        this.logger.warn("EBEHIND", `${message}, exiting`);

        // still exits zero, aka "ok"
        return false;
      }
    }

    this.conf = npmConf(this.options);
    this.updates = collectUpdates(this);

    if (!this.updates.length) {
      this.logger.success("No updated packages to publish");

      // still exits zero, aka "ok"
      return false;
    }

    this.packagesToPublish = this.updates.map(({ pkg }) => pkg).filter(pkg => !pkg.private);

    this.batchedPackages = this.toposort
      ? batchPackages(
          this.packagesToPublish,
          this.options.rejectCycles,
          // Don't sort based on devDependencies because that would increase the chance of dependency cycles
          // causing less-than-ideal a publishing order.
          "dependencies"
        )
      : [this.packagesToPublish];

    const tasks = [
      () => this.getVersionsForUpdates(),
      versions => {
        this.updatesVersions = versions;
      },
      () => this.confirmVersions(),
    ];

    return pWaterfall(tasks);
  }

  execute() {
    this.enableProgressBar();

    const tasks = [];

    if (!this.project.isIndependent() && !this.options.canary) {
      tasks.push(() => this.updateVersionInLernaJson());
    }

    tasks.push(() => this.updateUpdatedPackages());

    if (this.gitEnabled) {
      tasks.push(() => this.commitAndTagUpdates());
    } else {
      this.logger.info("execute", "Skipping git commit/push");
    }

    if (this.options.skipNpm) {
      this.logger.info("execute", "Skipping publish to registry");
    } else {
      tasks.push(() => this.publishPackagesToNpm());
    }

    if (this.gitEnabled) {
      tasks.push(() => this.pushToRemote());
    }

    return pWaterfall(tasks).then(() => {
      this.logger.success("publish", "finished");
    });
  }

  pushToRemote() {
    this.logger.info("git", "Pushing tags...");

    return GitUtilities.pushWithTags(this.gitRemote, this.tags, this.execOpts);
  }

  resolveLocalDependencyLinks() {
    // resolve relative file: links to their actual version range
    const updatesWithLocalLinks = this.updates.filter(
      ({ pkg, localDependencies }) =>
        !pkg.private &&
        localDependencies.size &&
        Array.from(localDependencies.values()).some(({ type }) => type === "directory")
    );

    return pMap(updatesWithLocalLinks, ({ pkg, localDependencies }) => {
      for (const [depName, resolved] of localDependencies) {
        // regardless of where the version comes from, we can't publish "file:../sibling-pkg" specs
        const depVersion = this.updatesVersions.get(depName) || this.packageGraph.get(depName).pkg.version;

        // it no longer matters if we mutate the shared Package instance
        pkg.updateLocalDependency(resolved, depVersion, this.savePrefix);
      }

      // writing changes to disk handled in annotateGitHead()
    });
  }

  annotateGitHead() {
    const gitHead = GitUtilities.getCurrentSHA(this.execOpts);

    return pMap(this.updates, ({ pkg }) => {
      if (!pkg.private) {
        // provide gitHead property that is normally added during npm publish
        pkg.set("gitHead", gitHead);

        return pkg.serialize();
      }
    });
  }

  resetManifestChanges() {
    // the package.json files are changed (by gitHead if not --canary)
    // and we should always leave the working tree clean
    return pReduce(this.project.packageConfigs, (_, pkgGlob) =>
      GitUtilities.checkoutChanges(`${pkgGlob}/package.json`, this.execOpts)
    );
  }

  publishPackagesToNpm() {
    this.logger.info("publish", "Publishing packages to npm...");

    let chain = Promise.resolve();

    chain = chain.then(() => this.resolveLocalDependencyLinks());
    chain = chain.then(() => this.annotateGitHead());
    chain = chain.then(() => this.npmPublish());
    chain = chain.then(() => this.resetManifestChanges());

    if (this.options.tempTag) {
      chain = chain.then(() => this.npmUpdateAsLatest());
    }

    return chain.then(() => {
      const message = this.packagesToPublish.map(pkg => ` - ${pkg.name}@${pkg.version}`);

      output("Successfully published:");
      output(message.join(os.EOL));
    });
  }

  getVersionsForUpdates() {
    const { canary, cdVersion, conventionalCommits, preid, repoVersion } = this.options;

    const makeGlobalVersionPredicate = nextVersion => {
      this.globalVersion = nextVersion;

      return () => nextVersion;
    };

    // decide the predicate in the conditionals below
    let predicate;

    if (repoVersion) {
      predicate = makeGlobalVersionPredicate(repoVersion);
    } else if (canary) {
      const release = cdVersion || "minor";
      // FIXME: this complicated defaulting should be done in yargs option.coerce()
      const keyword = typeof canary !== "string" ? preid || "alpha" : canary;
      const shortHash = GitUtilities.getShortSHA(this.execOpts);

      predicate = ({ version }) => `${semver.inc(version, release)}-${keyword}.${shortHash}`;
    } else if (cdVersion) {
      predicate = ({ version }) => semver.inc(version, cdVersion, preid);
    } else if (conventionalCommits) {
      // it's a bit weird to have a return here, true
      return this.recommendVersions();
    } else {
      predicate = this.promptVersion;
    }

    if (!this.project.isIndependent()) {
      predicate = Promise.resolve(predicate({ version: this.project.version })).then(
        makeGlobalVersionPredicate
      );
    }

    return Promise.resolve(predicate).then(getVersion => this.reduceVersions(getVersion));
  }

  reduceVersions(getVersion) {
    const iterator = (versionMap, { pkg }) =>
      Promise.resolve(getVersion(pkg)).then(version => versionMap.set(pkg.name, version));

    return pReduce(this.updates, iterator, new Map());
  }

  recommendVersions() {
    const independentVersions = this.project.isIndependent();
    const { changelogPreset } = this.options;
    const rootPath = this.project.manifest.location;
    const type = independentVersions ? "independent" : "fixed";

    let chain = Promise.resolve();

    if (type === "fixed") {
      chain = chain.then(() => {
        const globalVersion = this.project.version;

        for (const { pkg } of this.updates) {
          if (semver.lt(pkg.version, globalVersion)) {
            this.logger.verbose(
              "publish",
              `Overriding version of ${pkg.name} from ${pkg.version} to ${globalVersion}`
            );

            pkg.version = globalVersion;
          }
        }
      });
    }

    chain = chain.then(() =>
      this.reduceVersions(pkg =>
        ConventionalCommitUtilities.recommendVersion(pkg, type, {
          changelogPreset,
          rootPath,
        })
      )
    );

    if (type === "fixed") {
      chain = chain.then(versions => {
        let highestVersion = this.project.version;

        versions.forEach(bump => {
          if (semver.gt(bump, highestVersion)) {
            highestVersion = bump;
          }
        });

        this.globalVersion = highestVersion;

        versions.forEach((_, name) => versions.set(name, highestVersion));

        return versions;
      });
    }

    return chain;
  }

  promptVersion({ version: currentVersion, name: pkgName }) {
    const patch = semver.inc(currentVersion, "patch");
    const minor = semver.inc(currentVersion, "minor");
    const major = semver.inc(currentVersion, "major");
    const prepatch = semver.inc(currentVersion, "prepatch");
    const preminor = semver.inc(currentVersion, "preminor");
    const premajor = semver.inc(currentVersion, "premajor");

    const message = `Select a new version ${pkgName ? `for ${pkgName} ` : ""}(currently ${currentVersion})`;

    return PromptUtilities.select(message, {
      choices: [
        { value: patch, name: `Patch (${patch})` },
        { value: minor, name: `Minor (${minor})` },
        { value: major, name: `Major (${major})` },
        { value: prepatch, name: `Prepatch (${prepatch})` },
        { value: preminor, name: `Preminor (${preminor})` },
        { value: premajor, name: `Premajor (${premajor})` },
        { value: "PRERELEASE", name: "Prerelease" },
        { value: "CUSTOM", name: "Custom" },
      ],
    }).then(choice => {
      if (choice === "CUSTOM") {
        return PromptUtilities.input("Enter a custom version", {
          filter: semver.valid,
          validate: v => v !== null || "Must be a valid semver version",
        });
      }

      if (choice === "PRERELEASE") {
        const [existingId] = semver.prerelease(currentVersion) || [];
        const defaultVersion = semver.inc(currentVersion, "prerelease", existingId);
        const prompt = `(default: ${existingId ? `"${existingId}"` : "none"}, yielding ${defaultVersion})`;

        // TODO: allow specifying prerelease identifier as CLI option to skip the prompt
        return PromptUtilities.input(`Enter a prerelease identifier ${prompt}`, {
          filter: v => {
            const preid = v || existingId;
            return semver.inc(currentVersion, "prerelease", preid);
          },
        });
      }

      return choice;
    });
  }

  confirmVersions() {
    const changes = this.updates.map(({ pkg }) => {
      let line = ` - ${pkg.name}: ${pkg.version} => ${this.updatesVersions.get(pkg.name)}`;
      if (pkg.private) {
        line += ` (${chalk.red("private")})`;
      }
      return line;
    });

    output("");
    output("Changes:");
    output(changes.join(os.EOL));
    output("");

    if (this.options.yes) {
      this.logger.info("auto-confirmed");
      return true;
    }

    return PromptUtilities.confirm("Are you sure you want to publish the above changes?");
  }

  updateVersionInLernaJson() {
    this.project.version = this.globalVersion;

    return this.project.serializeConfig().then(lernaConfigLocation => {
      if (!this.options.skipGit) {
        return GitUtilities.addFiles([lernaConfigLocation], this.execOpts);
      }
    });
  }

  runPackageLifecycle(pkg, stage) {
    if (pkg.scripts[stage]) {
      return runLifecycle(pkg, stage, this.conf).catch(err => {
        this.logger.error("lifecycle", `error running ${stage} in ${pkg.name}\n`, err.stack || err);
      });
    }
  }

  updateUpdatedPackages() {
    const { conventionalCommits, changelogPreset } = this.options;
    const independentVersions = this.project.isIndependent();
    const rootPkg = this.project.manifest;
    const rootPath = rootPkg.location;
    const changedFiles = new Set();

    // my kingdom for async await :(
    let chain = Promise.resolve();

    // exec preversion lifecycle in root (before all updates)
    chain = chain.then(() => this.runPackageLifecycle(rootPkg, "preversion"));

    chain = chain.then(() =>
      pMap(
        this.updates,
        ({ pkg, localDependencies }) =>
          // start the chain
          Promise.resolve()

            // exec preversion script
            .then(() => this.runPackageLifecycle(pkg, "preversion"))

            // write new package
            .then(() => {
              // set new version
              pkg.version = this.updatesVersions.get(pkg.name);

              // update pkg dependencies
              for (const [depName, resolved] of localDependencies) {
                const depVersion = this.updatesVersions.get(depName);

                if (depVersion && resolved.type !== "directory") {
                  // don't overwrite local file: specifiers (yet)
                  pkg.updateLocalDependency(resolved, depVersion, this.savePrefix);
                }
              }

              return pkg.serialize().then(() => {
                // commit the updated manifest
                changedFiles.add(pkg.manifestLocation);
              });
            })

            // exec version script
            .then(() => this.runPackageLifecycle(pkg, "version"))

            .then(() => {
              if (conventionalCommits) {
                // we can now generate the Changelog, based on the
                // the updated version that we're about to release.
                const type = independentVersions ? "independent" : "fixed";

                return ConventionalCommitUtilities.updateChangelog(pkg, type, {
                  changelogPreset,
                  rootPath,
                }).then(changelogLocation => {
                  // commit the updated changelog
                  changedFiles.add(changelogLocation);
                });
              }
            }),
        // TODO: tune the concurrency
        { concurrency: 100 }
      )
    );

    if (conventionalCommits && !independentVersions) {
      chain = chain.then(() =>
        ConventionalCommitUtilities.updateChangelog(rootPkg, "root", {
          changelogPreset,
          rootPath,
          version: this.globalVersion,
        }).then(changelogLocation => {
          // commit the updated changelog
          changedFiles.add(changelogLocation);
        })
      );
    }

    // exec version lifecycle in root (after all updates)
    chain = chain.then(() => this.runPackageLifecycle(rootPkg, "version"));

    if (this.gitEnabled) {
      chain = chain.then(() => GitUtilities.addFiles(Array.from(changedFiles), this.execOpts));
    }

    return chain;
  }

  commitAndTagUpdates() {
    let chain = Promise.resolve();

    if (this.project.isIndependent()) {
      chain = chain.then(() => this.gitCommitAndTagVersionForUpdates());
    } else {
      chain = chain.then(() => this.gitCommitAndTagVersion());
    }

    chain = chain.then(tags => {
      this.tags = tags;
    });

    // run the postversion script for each update
    chain = chain.then(() => pMap(this.updates, ({ pkg }) => this.runPackageLifecycle(pkg, "postversion")));

    // run postversion, if set, in the root directory
    chain = chain.then(() => this.runPackageLifecycle(this.project.manifest, "postversion"));

    return chain;
  }

  gitCommitAndTagVersionForUpdates() {
    const tags = this.updates.map(({ pkg }) => `${pkg.name}@${this.updatesVersions.get(pkg.name)}`);
    const subject = this.options.message || "Publish";
    const message = tags.reduce((msg, tag) => `${msg}${os.EOL} - ${tag}`, `${subject}${os.EOL}`);

    return Promise.resolve()
      .then(() => GitUtilities.commit(message, this.execOpts))
      .then(() => Promise.all(tags.map(tag => GitUtilities.addTag(tag, this.execOpts))))
      .then(() => tags);
  }

  gitCommitAndTagVersion() {
    const version = this.globalVersion;
    const tag = `v${version}`;
    const message = this.options.message
      ? this.options.message.replace(/%s/g, tag).replace(/%v/g, version)
      : tag;

    return Promise.resolve()
      .then(() => GitUtilities.commit(message, this.execOpts))
      .then(() => GitUtilities.addTag(tag, this.execOpts))
      .then(() => [tag]);
  }

  execScript(pkg, script) {
    const scriptLocation = path.join(pkg.location, "scripts", script);

    try {
      // eslint-disable-next-line import/no-dynamic-require, global-require
      require(scriptLocation);
    } catch (ex) {
      this.logger.silly("execScript", `No ${script} script found at ${scriptLocation}`);
    }
  }

  runPrepublishScripts(pkg) {
    return Promise.resolve()
      .then(() => this.runPackageLifecycle(pkg, "prepare"))
      .then(() => this.runPackageLifecycle(pkg, "prepublishOnly"));
  }

  npmPublish() {
    const tracker = this.logger.newItem("npmPublish");
    // if we skip temp tags we should tag with the proper value immediately
    const distTag = this.options.tempTag ? "lerna-temp" : this.getDistTag();
    const rootPkg = this.project.manifest;

    let chain = Promise.resolve();

    chain = chain.then(() => this.runPrepublishScripts(rootPkg));
    chain = chain.then(() =>
      pMap(this.updates, ({ pkg }) => {
        this.execScript(pkg, "prepublish");

        return this.runPrepublishScripts(pkg);
      })
    );

    tracker.addWork(this.packagesToPublish.length);

    const mapPackage = pkg => {
      tracker.verbose("publishing", pkg.name);

      return npmPublish(pkg, distTag, this.npmConfig).then(() => {
        tracker.info("published", pkg.name);
        tracker.completeWork(1);

        this.execScript(pkg, "postpublish");

        return this.runPackageLifecycle(pkg, "postpublish");
      });
    };

    chain = chain.then(() => runParallelBatches(this.batchedPackages, this.concurrency, mapPackage));
    chain = chain.then(() => this.runPackageLifecycle(rootPkg, "postpublish"));

    return pFinally(chain, () => tracker.finish());
  }

  npmUpdateAsLatest() {
    const tracker = this.logger.newItem("npmUpdateAsLatest");

    tracker.addWork(this.packagesToPublish.length);

    const mapPackage = pkg =>
      this.updateTag(pkg).then(() => {
        tracker.info("latest", pkg.name);
        tracker.completeWork(1);
      });

    return pFinally(runParallelBatches(this.batchedPackages, this.concurrency, mapPackage), () =>
      tracker.finish()
    );
  }

  removeTempTag(pkg) {
    return Promise.resolve()
      .then(() => npmDistTag.check(pkg, "lerna-temp", this.npmConfig.registry))
      .then(exists => {
        if (exists) {
          return npmDistTag.remove(pkg, "lerna-temp", this.npmConfig.registry);
        }
      });
  }

  updateTag(pkg) {
    const distTag = this.getDistTag() || "latest";
    const version = this.options.canary ? pkg.version : this.updatesVersions.get(pkg.name);

    return this.removeTempTag(pkg).then(() => npmDistTag.add(pkg, version, distTag, this.npmConfig.registry));
  }

  getDistTag() {
    if (this.options.npmTag) {
      return this.options.npmTag;
    }

    if (this.options.canary) {
      return "canary";
    }

    // undefined defaults to "latest" OR whatever is in pkg.publishConfig.tag
  }
}

module.exports.PublishCommand = PublishCommand;


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`lerna clean global: stderr 1`] = `
lerna info version __TEST_VERSION__
lerna info clean removing __TEST_ROOTDIR__/packages/package-1/node_modules
lerna info clean removing __TEST_ROOTDIR__/packages/package-2/node_modules
lerna info clean removing __TEST_ROOTDIR__/packages/package-3/node_modules
lerna success clean finished
`;


ChildProcessUtilities.exec("git", ["push", "--no-verify", remote, branch].concat(tags), opts)

ChildProcessUtilities.exec("git", ["push", "--follow-tags", "--no-verify", remote, branch], opts)

"use strict";

const execa = require("execa");
const fs = require("fs-extra");
const { EOL } = require("os");
const path = require("path");
const slash = require("slash");
const tempy = require("tempy");

// helpers
const initFixture = require("@lerna-test/init-fixture")(__dirname);
const cloneFixture = require("@lerna-test/clone-fixture")(__dirname);

// file under test
const GitUtilities = require("..");

describe("GitUtilities", () => {
  describe(".isDetachedHead()", () => {
    it("returns true when branchName is HEAD", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.isDetachedHead({ cwd })).toBe(false);

      const sha = await execa.stdout("git", ["rev-parse", "HEAD"], { cwd });
      await execa("git", ["checkout", sha], { cwd }); // detach head

      expect(GitUtilities.isDetachedHead({ cwd })).toBe(true);
    });
  });

  describe(".isInitialized()", () => {
    it("returns true when git command succeeds", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.isInitialized({ cwd })).toBe(true);

      await fs.remove(path.join(cwd, ".git"));
      expect(GitUtilities.isInitialized({ cwd })).toBe(false);
    });
  });

  describe(".addFiles()", () => {
    it("calls git add with files argument", async () => {
      const cwd = await initFixture("basic");
      const file = path.join("packages", "pkg-1", "index.js");

      await fs.outputFile(path.join(cwd, file), "hello");
      await GitUtilities.addFiles([file], { cwd });

      const list = await execa.stdout("git", ["diff", "--cached", "--name-only"], { cwd });
      expect(slash(list)).toBe("packages/pkg-1/index.js");
    });

    it("works with absolute path for files", async () => {
      const cwd = await initFixture("basic");
      const file = path.join(cwd, "packages", "pkg-2", "index.js");

      await fs.outputFile(file, "hello");
      await GitUtilities.addFiles([file], { cwd });

      const list = await execa.stdout("git", ["diff", "--cached", "--name-only"], { cwd });
      expect(slash(list)).toBe("packages/pkg-2/index.js");
    });
  });

  describe(".commit()", () => {
    it("calls git commit with message", async () => {
      const cwd = await initFixture("basic");

      await fs.outputFile(path.join(cwd, "packages", "pkg-3", "index.js"), "hello");
      await execa("git", ["add", "."], { cwd });
      await GitUtilities.commit("foo", { cwd });

      const message = await execa.stdout("git", ["log", "-1", "--pretty=format:%B"], { cwd });
      expect(message).toBe("foo");
    });

    it("allows multiline message", async () => {
      const cwd = await initFixture("basic");

      await fs.outputFile(path.join(cwd, "packages", "pkg-4", "index.js"), "hello");
      await execa("git", ["add", "."], { cwd });
      await GitUtilities.commit(`foo${EOL}${EOL}bar`, { cwd });

      const subject = await execa.stdout("git", ["log", "-1", "--pretty=format:%s"], { cwd });
      const body = await execa.stdout("git", ["log", "-1", "--pretty=format:%b"], { cwd });

      expect(subject).toBe("foo");
      expect(body).toBe("bar");
    });
  });

  describe(".addTag()", () => {
    it("creates annotated git tag", async () => {
      const cwd = await initFixture("basic");

      await GitUtilities.addTag("v1.2.3", { cwd });

      const list = await execa.stdout("git", ["tag", "--list"], { cwd });
      expect(list).toMatch("v1.2.3");
    });
  });

  describe(".hasTags()", () => {
    it("returns true when one or more git tags exist", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.hasTags({ cwd })).toBe(false);

      await execa("git", ["tag", "v1.2.3", "-m", "v1.2.3"], { cwd });
      expect(GitUtilities.hasTags({ cwd })).toBe(true);
    });
  });

  describe(".getLastTaggedCommit()", () => {
    it("returns SHA of closest git tag", async () => {
      const cwd = await initFixture("basic");

      await execa("git", ["tag", "v1.2.3", "-m", "v1.2.3"], { cwd });

      expect(GitUtilities.getLastTaggedCommit({ cwd })).toMatch(/^[0-9a-f]{40}$/);
    });
  });

  describe(".getFirstCommit()", () => {
    it("returns SHA of first commit", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.getFirstCommit({ cwd })).toMatch(/^[0-9a-f]{40}$/);
    });
  });

  describe(".pushWithTags()", () => {
    it("pushes current branch and specified tag(s) to origin", async () => {
      const { cwd } = await cloneFixture("basic");

      await execa("git", ["commit", "--allow-empty", "-m", "change"], { cwd });
      await execa("git", ["tag", "v1.2.3", "-m", "v1.2.3"], { cwd });

      await GitUtilities.pushWithTags("origin", ["v1.2.3"], { cwd });

      const list = await execa.stdout("git", ["ls-remote", "--tags", "--refs", "--quiet"], { cwd });
      expect(list).toMatch("v1.2.3");
    });
  });

  describe(".getLastTag()", () => {
    it("returns the closest tag", async () => {
      const cwd = await initFixture("basic");

      await execa("git", ["tag", "v1.2.3", "-m", "v1.2.3"], { cwd });

      expect(GitUtilities.getLastTag({ cwd })).toBe("v1.2.3");
    });
  });

  describe(".diffSinceIn()", () => {
    it("omits location filter when location is current working directory", async () => {
      const cwd = await initFixture("basic");
      const file = path.join(cwd, "packages", "pkg-5", "index.js");

      await execa("git", ["tag", "v1.2.3", "-m", "v1.2.3"], { cwd });

      await fs.outputFile(file, "hello");
      await fs.writeJSON(path.join(cwd, "package.json"), { foo: "bar" });

      await execa("git", ["add", "."], { cwd });
      await execa("git", ["commit", "-m", "change"], { cwd });

      const rootDiff = GitUtilities.diffSinceIn("v1.2.3", cwd, { cwd });
      const fileDiff = GitUtilities.diffSinceIn("v1.2.3", path.dirname(file), { cwd });

      expect(rootDiff).toMatch("package.json");
      expect(fileDiff).toBe("packages/pkg-5/index.js");
    });
  });

  describe(".getWorkspaceRoot()", () => {
    it("calls `git rev-parse --show-toplevel`", async () => {
      const topLevel = await initFixture("basic");
      const cwd = path.join(topLevel, "foo");

      await fs.mkdirp(cwd);
      expect(GitUtilities.getWorkspaceRoot({ cwd })).toBe(slash(topLevel));
    });
  });

  describe(".getCurrentBranch()", () => {
    it("calls `git rev-parse --abbrev-ref HEAD`", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.getCurrentBranch({ cwd })).toBe("master");
    });
  });

  describe(".getCurrentSHA()", () => {
    it("returns full SHA of current ref", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.getCurrentSHA({ cwd })).toMatch(/^[0-9a-f]{40}$/);
    });
  });

  describe(".getShortSHA()", () => {
    it("returns short SHA of current ref", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.getShortSHA({ cwd })).toMatch(/^[0-9a-f]{7,8}$/);
    });
  });

  describe(".checkoutChanges()", () => {
    it("calls git checkout with specified arg", async () => {
      const cwd = await initFixture("basic");

      await fs.writeJSON(path.join(cwd, "package.json"), { foo: "bar" });
      await GitUtilities.checkoutChanges("package.json", { cwd });

      const modified = await execa.stdout("git", ["ls-files", "--modified"], { cwd });
      expect(modified).toBe("");
    });
  });

  describe(".init()", () => {
    it("calls git init", async () => {
      const cwd = tempy.directory();

      GitUtilities.init({ cwd });

      const { code } = await execa("git", ["status"], { cwd });
      expect(code).toBe(0);
    });
  });

  describe(".hasCommit()", () => {
    it("returns true when git command succeeds", async () => {
      const cwd = await initFixture("basic");

      expect(GitUtilities.hasCommit({ cwd })).toBe(true);

      await fs.remove(path.join(cwd, ".git"));
      expect(GitUtilities.hasCommit({ cwd })).toBe(false);
    });
  });

  describe(".isBehindUpstream()", () => {
    it("returns true when behind upstream", async () => {
      const { cwd } = await cloneFixture("basic");

      expect(GitUtilities.isBehindUpstream("origin", { cwd })).toBe(false);

      await execa("git", ["commit", "--allow-empty", "-m", "change"], { cwd });
      await execa("git", ["push", "origin", "master"], { cwd });
      await execa("git", ["reset", "--hard", "HEAD^"], { cwd });

      expect(GitUtilities.isBehindUpstream("origin", { cwd })).toBe(true);
    });
  });
});


# `@lerna/npm-dist-tag`

> description TODO

## Usage

TODO


# `lerna`

> A tool for managing JavaScript projects with multiple packages.

## Usage

TODO


"use strict";

module.exports = {
  transform: (commit, context) => {
    /* eslint-disable no-param-reassign */
    let discard = true;
    const issues = [];

    commit.notes.forEach(note => {
      note.title = `BREAKING CHANGES`;
      discard = false;
    });

    if (commit.type === `feat`) {
      commit.type = `Features`;
    } else if (commit.type === `fix`) {
      commit.type = `Bug Fixes`;
    } else if (commit.type === `perf`) {
      commit.type = `Performance Improvements`;
    } else if (commit.type === `revert`) {
      commit.type = `Reverts`;
    } else if (discard) {
      return;
    } else if (commit.type === `docs`) {
      commit.type = `Documentation`;
    } else if (commit.type === `style`) {
      commit.type = `Styles`;
    } else if (commit.type === `refactor`) {
      commit.type = `Code Refactoring`;
    } else if (commit.type === `test`) {
      commit.type = `Tests`;
    } else if (commit.type === `build`) {
      commit.type = `Build System`;
    } else if (commit.type === `ci`) {
      commit.type = `Continuous Integration`;
    }

    if (commit.scope === `*`) {
      commit.scope = ``;
    }

    if (typeof commit.hash === `string`) {
      commit.hash = commit.hash.substring(0, 7);
    }

    if (typeof commit.subject === `string`) {
      let url = context.repository
        ? `${context.host}/${context.owner}/${context.repository}`
        : context.repoUrl;
      if (url) {
        url = `${url}/issues/`;
        // Issue URLs.
        commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {
          issues.push(issue);
          return `[#${issue}](${url}${issue})`;
        });
      }
      if (context.host) {
        // User URLs.
        commit.subject = commit.subject.replace(
          /\B@([a-z0-9](?:-?[a-z0-9]){0,38})/g,
          `[@$1](${context.host}/$1)`
        );
      }
    }

    // remove references that already appear in the subject
    commit.references = commit.references.filter(reference => {
      if (issues.indexOf(reference.issue) === -1) {
        return true;
      }

      return false;
    });

    return commit;
    /* eslint-enable no-param-reassign */
  },
  groupBy: `type`,
  commitGroupsSort: `title`,
  commitsSort: [`scope`, `subject`],
  noteGroupsSort: `title`,
  // notesSort: compareFunc,
  mainTemplate: [
    "{{> header}}",
    "",
    "{{#each commitGroups}}",
    "{{#each commits}}",
    "{{> commit root=@root}}",
    "{{/each}}",
    "{{/each}}",
    "",
    "{{> footer}}",
  ].join("\n"),
  headerPartial: [
    '',
    "## {{#if isPatch~}} ",
    "{{~/if~}} {{version}}",
    '{{~#if title}} "{{title}}"',
    "{{~/if~}}",
    "{{~#if date}} ({{date}})",
    "{{~/if~}}",
    "{{~#if isPatch~}} ",
    "{{~/if}}",
    "",
  ].join("\n"),
  commitPartial: "* {{header}}",
  footerPartial: [
    "{{#if noteGroups}}",
    "{{#each noteGroups}}",
    "",
    "### {{title}}",
    "",
    "{{#each notes}}",
    "* {{text}}",
    "{{/each}}",
    "{{/each}}",
    "{{/if}}",
  ].join("\n"),
};


const right = this.length - left + 1 + even

if (this.value > left && this.value < right) {

const end = this.value + left - 2 - even

const firstItem = 1
const lastItem = this.length

// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VPagination.ts emits an event when next or previous is clicked 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        4
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        5
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should only render end of range if value is equals "right" 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        98
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
              aria-current="true"
      >
        99
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should only render middle of range if length is big and value is somewhere in the middle 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        41
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        42
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        43
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        44
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        45
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        46
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        47
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        48
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        49
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
              aria-current="true"
      >
        50
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        51
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        52
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        53
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        54
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        55
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        56
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        57
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        58
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should only render start and end of range if length is big 1`] = `

      <button disabled="disabled"
              type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation v-pagination__navigation--disabled"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        4
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        5
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        6
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        7
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        8
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        9
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        10
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        11
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        91
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        92
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        93
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        94
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        95
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        96
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        97
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        98
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        99
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should only render start of range if value is equals "left" 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
              aria-current="true"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should render component in RTL mode and match snapshot 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        2
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        3
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        4
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        5
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;

exports[`VPagination.ts should render disabled buttons with length equals to 0 1`] = `

      <button disabled="disabled"
              type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation v-pagination__navigation--disabled"
      >

      </button>

      <button disabled="disabled"
              type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation v-pagination__navigation--disabled"
      >

      </button>

`;

exports[`VPagination.ts should use totalVisible prop if defined 1`] = `

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.previous"
              class="v-pagination__navigation"
      >

      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        1
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        47
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        48
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        49
      </button>

      <button type="button"
              aria-current="true"
              aria-label="$vuetify.pagination.ariaLabel.currentPage"
              class="v-pagination__item v-pagination__item--active primary"
      >
        50
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        51
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        52
      </button>

        ...

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.page"
              class="v-pagination__item"
      >
        100
      </button>

      <button type="button"
              aria-label="$vuetify.pagination.ariaLabel.next"
              class="v-pagination__navigation"
      >

      </button>

`;


Sets the transition mode (does not apply to transition-group). You can find more information on the Vue documentation [for transition modes](https://vuejs.org/v2/api/#transition).

Sets the transition origin on the element. You can find more information on the MDN documentation [for transition origin](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin).

Creates a `transition-group` component. [vue docs](https://vuejs.org/v2/api/#transition-group)

Hides the leaving element (no exit animation)

Absolutely positions the leaving element (useful for [FLIP](https://aerotwist.com/blog/flip-your-animations/))


const fs = require('fs')

function getLocaleData () {
  const localeData = {}
  const localeFolders = fs.readdirSync('./src/locale')
  localeFolders.forEach(folder => {
    const locale = {}
    const localeFiles = fs.readdirSync(`./src/locale/${folder}`)
    localeFiles.forEach(file => {
      const compLocale = fs.readFileSync(`./src/locale/${folder}/${file}`, 'utf8')
      locale[file.split('.')[0]] = JSON.parse(compLocale)
    })
    localeData[folder] = locale
  })
  return localeData
}

module.exports = getLocaleData()


import { format, parseISO } from 'date-fns'

export function formatDate (date) {
  return format(parseISO(date.toISOString().substr(0, 10)), 'MMMM d')
}


const secondItem = start - 1 === firstItem + 1 ? 2 : '...'

const beforeLastItem = end + 1 === lastItem - 1 ? end + 1 : '...'


---
meta:
  title: Display helpers
  description: Display helper classes allow you to control when elements should display based upon viewport.
  keywords: display helper classes, display classes, vuetify display
related:
  - /styles/text-and-typography/
  - /directives/resize/
  - /features/breakpoints/
---

# Display helpers

The display helpers allow you to control the display of content. This includes being conditionally visible based upon the current viewport, or the actual element display type.

<entry-ad />

<breakpoints-table />

## Display

Specify the elements `display` property. These classes can be applied to all breakpoints from `xs` to `xl`. When using a base class,`.d-{value}`, it is inferred to be `.d-${value}-xs`.

- `.d-{value}` for `xs`
- `.d-{breakpoint}-{value}` for `sm`, `md`, `lg` and `xl`

The _value_ property is one of:

- `none`
- `inline`
- `inline-block`
- `block`
- `table`
- `table-cell`
- `table-row`
- `flex`
- `inline-flex`

When setting a specific breakpoint for a display helper class, it will apply to all screen widths from the designation and up. For example, `d-lg-flex` will apply to `lg` and `xl` size screens.

<example file="display/display-inline" />

<example file="display/display-block" />

## Visibility

Conditionally display an element based upon the current **viewport**. Breakpoint utility classes always apply from the bottom up. That means if you have `.d-none`, it will apply to all breakpoints. However, `.d-md-none` will apply to only `md` and up.

| Screen size | Class |
| ------- | ------ |
| Hidden on all | `.d-none` |
| Hidden only on xs | `.d-none .d-sm-flex` |
| Hidden only on sm | `.d-sm-none .d-md-flex` |
| Hidden only on md | `.d-md-none .d-lg-flex` |
| Hidden only on lg | `.d-lg-none .d-xl-flex` |
| Hidden only on xl | `.d-xl-none` |
| Visible on all | `.d-flex` |
| Visible only on xs |`.d-flex .d-sm-none` |
| Visible only on sm |`.d-none .d-sm-flex .d-md-none` |
| Visible only on md |`.d-none .d-md-flex .d-lg-none` |
| Visible only on lg |`.d-none .d-lg-flex .d-xl-none` |
| Visible only on xl |`.d-none .d-xl-flex` |

<example file="display/visibility" />

Additionally you can also display an element based upon the current **viewport** using lateral display helper classes. These classes can be applied using the following format `hidden-{breakpoint}-{condition}`

The _condition_ applies the class base on:

- `only` - hide the element only on `xs` through `xl` breakpoints
- `and-down` - hide the element on the specified breakpoint and down `sm` through `lg` breakpoints
- `and-up` - hide the element on the specified breakpoint and up `sm` through `lg` breakpoints

Additionally, **media types** can be targeted using the `only` condition. Both `hidden-screen-only` and `hidden-print-only` are currently supported.

## Display in print

You can also change the display property when printing.

- `.d-print-none`
- `.d-print-inline`
- `.d-print-inline-block`
- `.d-print-block`
- `.d-print-table`
- `.d-print-table-row`
- `.d-print-table-cell`
- `.d-print-flex`
- `.d-print-inline-flex`

Print utility classes can also be combined with none print display utilities.

<example file="display/print" />

## Accessibility

### Screen readers

Use the `d-sr` utility classes to conditionally hide content on all devices *except* screen readers.

- `d-sr-only` visually hides elements but will still announce to **screen readers**.
- `d-sr-only-focusable` visually hides an element until it is focused. This is useful when implementing *skip links*.
<backmatter />


// Styles
import './VSystemBar.sass'

// Mixins
import Applicationable from '../../mixins/applicationable'
import Colorable from '../../mixins/colorable'
import Themeable from '../../mixins/themeable'

// Utilities
import mixins from '../../util/mixins'
import { convertToUnit, getSlot } from '../../util/helpers'

// Types
import { VNode } from 'vue/types'

export default mixins(
  Applicationable('bar', [
    'height',
    'window',
  ]),
  Colorable,
  Themeable
/* @vue/component */
).extend({
  name: 'v-system-bar',

  props: {
    height: [Number, String],
    lightsOut: Boolean,
    window: Boolean,
  },

  computed: {
    classes (): object {
      return {
        'v-system-bar--lights-out': this.lightsOut,
        'v-system-bar--absolute': this.absolute,
        'v-system-bar--fixed': !this.absolute && (this.app || this.fixed),
        'v-system-bar--window': this.window,
        ...this.themeClasses,
      }
    },
    computedHeight (): number | string {
      if (this.height) {
        return isNaN(parseInt(this.height)) ? this.height : parseInt(this.height)
      }

      return this.window ? 32 : 24
    },
    styles (): object {
      return {
        height: convertToUnit(this.computedHeight),
      }
    },
  },

  methods: {
    updateApplication () {
      return this.$el
        ? this.$el.clientHeight
        : this.computedHeight
    },
  },

  render (h): VNode {
    const data = {
      staticClass: 'v-system-bar',
      class: this.classes,
      style: this.styles,
      on: this.$listeners,
    }

    return h('div', this.setBackgroundColor(this.color, data), getSlot(this))
  },
})


<template>
  <v-row dense class="mb-12">
    <v-col
      v-for="(item, i) in items"
      :key="i"
      cols="12"
      md="6"
      xl="4"
      class="d-flex"
    >
      <app-sheet class="grow">
        <v-list-item :to="item.to">
          <v-list-item-icon>
            <v-icon
              color="primary"
              large
              v-text="item.icon"
            />
          </v-list-item-icon>

          <v-list-item-content>
            <v-list-item-title
              class="font-weight-bold"
              v-text="item.title"
            />

            <v-list-item-subtitle
              v-text="item.subtitle"
            />
          </v-list-item-content>
        </v-list-item>
      </app-sheet>
    </v-col>
  </v-row>
</template>

<script>
  export default {
    name: 'BgOverview',

    data: () => ({
      items: [
        {
          icon: 'mdi-speedometer',
          title: 'Installation',
          subtitle: 'Install Vuetify.',
          to: {
            name: 'Documentation',
            params: {
              category: 'getting-started',
              page: 'installation',
            },
          },
        },
        {
          icon: 'mdi-image-edit-outline',
          title: 'Customization',
          subtitle: 'Configuring your App.',
        },
        {
          icon: 'mdi-palette-outline',
          title: 'Styling',
          subtitle: 'Styling Vuetify.',
        },
        {
          icon: 'mdi-teach',
          title: 'Resources',
          subtitle: 'Available tooling and more.',
        },
        {
          icon: 'mdi-bullhorn-outline',
          title: `What's new`,
          subtitle: 'Browse latest releases.',
        },
        {
          icon: 'mdi-console',
          title: 'Support',
          subtitle: 'Professional services.',
        },
      ],
    }),
  }
</script>


return [1, '...', ...this.range(start, end), '...', this.length]

const end = this.value + left - 1 - even

return [...this.range(1, end), '...', this.length]

return [1, secondItem, ...this.range(start, end), beforeLastItem, this.length]


import VContent from './VContent'

export { VContent }

export default VContent


// Components
import VSparkline from '../VSparkline'

// Utilities
import {
  mount,
  Wrapper,
} from '@vue/test-utils'

describe('VSparkline.ts', () => {
  type Instance = InstanceType<typeof VSparkline>
  let mountFunction: (options?: object) => Wrapper<Instance>

  beforeEach(() => {
    mountFunction = (options = {}) => {
      return mount(VSparkline, {
        ...options,
      })
    }
  })

  it('should render component and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with padding and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        padding: 20,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render smooth component and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        smooth: 20,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with line width and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        lineWidth: 42,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with gradient and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        gradient: ['#000', 'red', 'rgba(80, 160, 240, 0.5)'],
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with string labels and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        showLabels: true,
        value: [1, 7, 42],
      },
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      value: [
        {
          value: 2,
        },
        {
          value: 8,
        },
        {
          value: 43,
        },
      ],
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      labels: ['foo', 'bar', 'baz'],
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and negative and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [-1, 1, 7, 42],
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and gradient and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        gradient: ['#000', 'red', 'rgba(80, 160, 240, 0.5)'],
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and labels and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        labels: ['Value 1', 'Value 2', 'Value 3'],
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and auto-line-width and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        autoLineWidth: true,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and line width and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        lineWidth: 8,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and custom padding and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        padding: 12,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and auto-line-width with custom padding and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        type: 'bar',
        autoLineWidth: true,
        padding: 12,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and custom label size and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        labels: ['Value 1', 'Value 2', 'Value 3'],
        labelSize: 15,
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with trend and equal values and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 1, 1],
        type: 'trend',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with label size and match a snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        showLabels: true,
        labelSize: 14,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should position labels correctly', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 7, 42],
        showLabels: true,
        lineWidth: 20,
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component with bars and correct bar lengths', async () => {
    const wrapper = mountFunction({
      propsData: {
        value: [1, 2],
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()

    wrapper.setProps({
      value: [-1, -2],
    })
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render bar component with all values 0', () => {
    const wrapper = mountFunction({
      propsData: {
        value: [0, 0, 0],
        type: 'bar',
      },
    })

    expect(wrapper.html()).toMatchSnapshot()
  })
})


<template>
  <v-row justify="center">
    <v-date-picker
      v-model="picker"
      type="month"
    ></v-date-picker>
  </v-row>
</template>

<script>
  export default {
    data () {
      return {
        picker: new Date().toISOString().substr(0, 10),
      }
    },
  }
</script>


const maxLength = wrapper.vm.totalVisible

keep keep keep replace replace keep keep keep keep

const maxLength = Number(wrapper.vm.totalVisible)


// Mixins
import { factory as GroupableFactory } from '../../mixins/groupable'

// Utilities
import mixins from '../../util/mixins'
import { consoleWarn } from '../../util/console'

// Types
import Vue from 'vue'
import { VNode, ScopedSlotChildren } from 'vue/types/vnode'

/* @vue/component */
export const BaseItem = Vue.extend({
  props: {
    activeClass: String,
    value: {
      required: false,
    },
  },

  data: () => ({
    isActive: false,
  }),

  methods: {
    toggle () {
      this.isActive = !this.isActive
    },
  },

  render (): VNode {
    if (!this.$scopedSlots.default) {
      consoleWarn('v-item is missing a default scopedSlot', this)

      return null as any
    }

    let element: VNode | ScopedSlotChildren

    /* istanbul ignore else */
    if (this.$scopedSlots.default) {
      element = this.$scopedSlots.default({
        active: this.isActive,
        toggle: this.toggle,
      })
    }

    if (Array.isArray(element) && element.length === 1) {
      element = element[0]
    }

    if (!element || Array.isArray(element) || !element.tag) {
      consoleWarn('v-item should only contain a single element', this)

      return element as any
    }

    element.data = this._b(element.data || {}, element.tag!, {
      class: { [this.activeClass]: this.isActive },
    })

    return element
  },
})

export default mixins(
  BaseItem,
  GroupableFactory('itemGroup', 'v-item', 'v-item-group')
).extend({
  name: 'v-item',
})


// Styles
import './VInput.sass'

// Components
import VIcon from '../VIcon'
import VLabel from '../VLabel'
import VMessages from '../VMessages'

// Mixins
import BindsAttrs from '../../mixins/binds-attrs'
import Validatable from '../../mixins/validatable'

// Utilities
import {
  convertToUnit,
  getSlot,
  kebabCase,
} from '../../util/helpers'
import mergeData from '../../util/mergeData'

// Types
import { VNode, VNodeData, PropType } from 'vue'
import mixins from '../../util/mixins'
import { InputValidationRule } from 'vuetify/types'

const baseMixins = mixins(
  BindsAttrs,
  Validatable,
)

interface options extends InstanceType<typeof baseMixins> {
  /* eslint-disable-next-line camelcase */
  $_modelEvent: string
}

/* @vue/component */
export default baseMixins.extend<options>().extend({
  name: 'v-input',

  inheritAttrs: false,

  props: {
    appendIcon: String,
    backgroundColor: {
      type: String,
      default: '',
    },
    dense: Boolean,
    height: [Number, String],
    hideDetails: [Boolean, String] as PropType<boolean | 'auto'>,
    hint: String,
    id: String,
    label: String,
    loading: Boolean,
    persistentHint: Boolean,
    prependIcon: String,
    value: null as any as PropType<any>,
  },

  data () {
    return {
      lazyValue: this.value,
      hasMouseDown: false,
    }
  },

  computed: {
    classes (): object {
      return {
        'v-input--has-state': this.hasState,
        'v-input--hide-details': !this.showDetails,
        'v-input--is-label-active': this.isLabelActive,
        'v-input--is-dirty': this.isDirty,
        'v-input--is-disabled': this.isDisabled,
        'v-input--is-focused': this.isFocused,
        // <v-switch loading>.loading === '' so we can't just cast to boolean
        'v-input--is-loading': this.loading !== false && this.loading != null,
        'v-input--is-readonly': this.isReadonly,
        'v-input--dense': this.dense,
        ...this.themeClasses,
      }
    },
    computedId (): string {
      return this.id || `input-${this._uid}`
    },
    hasDetails (): boolean {
      return this.messagesToDisplay.length > 0
    },
    hasHint (): boolean {
      return !this.hasMessages &&
        !!this.hint &&
        (this.persistentHint || this.isFocused)
    },
    hasLabel (): boolean {
      return !!(this.$slots.label || this.label)
    },
    // Proxy for `lazyValue`
    // This allows an input
    // to function without
    // a provided model
    internalValue: {
      get (): any {
        return this.lazyValue
      },
      set (val: any) {
        this.lazyValue = val
        this.$emit(this.$_modelEvent, val)
      },
    },
    isDirty (): boolean {
      return !!this.lazyValue
    },
    isLabelActive (): boolean {
      return this.isDirty
    },
    messagesToDisplay (): string[] {
      if (this.hasHint) return [this.hint]

      if (!this.hasMessages) return []

      return this.validations.map((validation: string | InputValidationRule) => {
        if (typeof validation === 'string') return validation

        const validationResult = validation(this.internalValue)

        return typeof validationResult === 'string' ? validationResult : ''
      }).filter(message => message !== '')
    },
    showDetails (): boolean {
      return this.hideDetails === false || (this.hideDetails === 'auto' && this.hasDetails)
    },
  },

  watch: {
    value (val) {
      this.lazyValue = val
    },
  },

  beforeCreate () {
    // v-radio-group needs to emit a different event
    // https://github.com/vuetifyjs/vuetify/issues/4752
    this.$_modelEvent = (this.$options.model && this.$options.model.event) || 'input'
  },

  methods: {
    genContent () {
      return [
        this.genPrependSlot(),
        this.genControl(),
        this.genAppendSlot(),
      ]
    },
    genControl () {
      return this.$createElement('div', {
        staticClass: 'v-input__control',
        attrs: { title: this.attrs$.title },
      }, [
        this.genInputSlot(),
        this.genMessages(),
      ])
    },
    genDefaultSlot () {
      return [
        this.genLabel(),
        this.$slots.default,
      ]
    },
    genIcon (
      type: string,
      cb?: (e: Event) => void,
      extraData: VNodeData = {}
    ) {
      const icon = (this as any)[`${type}Icon`]
      const eventName = `click:${kebabCase(type)}`
      const hasListener = !!(this.listeners$[eventName] || cb)

      const data = mergeData({
        attrs: {
          'aria-label': hasListener ? kebabCase(type).split('-')[0] + ' icon' : undefined,
          color: this.validationState,
          dark: this.dark,
          disabled: this.isDisabled,
          light: this.light,
        },
        on: !hasListener
          ? undefined
          : {
            click: (e: Event) => {
              e.preventDefault()
              e.stopPropagation()

              this.$emit(eventName, e)
              cb && cb(e)
            },
            // Container has g event that will
            // trigger menu open if enclosed
            mouseup: (e: Event) => {
              e.preventDefault()
              e.stopPropagation()
            },
          },
      }, extraData)

      return this.$createElement('div', {
        staticClass: `v-input__icon`,
        class: type ? `v-input__icon--${kebabCase(type)}` : undefined,
      }, [
        this.$createElement(
          VIcon,
          data,
          icon
        ),
      ])
    },
    genInputSlot () {
      return this.$createElement('div', this.setBackgroundColor(this.backgroundColor, {
        staticClass: 'v-input__slot',
        style: { height: convertToUnit(this.height) },
        on: {
          click: this.onClick,
          mousedown: this.onMouseDown,
          mouseup: this.onMouseUp,
        },
        ref: 'input-slot',
      }), [this.genDefaultSlot()])
    },
    genLabel () {
      if (!this.hasLabel) return null

      return this.$createElement(VLabel, {
        props: {
          color: this.validationState,
          dark: this.dark,
          disabled: this.isDisabled,
          focused: this.hasState,
          for: this.computedId,
          light: this.light,
        },
      }, this.$slots.label || this.label)
    },
    genMessages () {
      if (!this.showDetails) return null

      return this.$createElement(VMessages, {
        props: {
          color: this.hasHint ? '' : this.validationState,
          dark: this.dark,
          light: this.light,
          value: this.messagesToDisplay,
        },
        attrs: {
          role: this.hasMessages ? 'alert' : null,
        },
        scopedSlots: {
          default: props => getSlot(this, 'message', props),
        },
      })
    },
    genSlot (
      type: string,
      location: string,
      slot: (VNode | VNode[])[]
    ) {
      if (!slot.length) return null

      const ref = `${type}-${location}`

      return this.$createElement('div', {
        staticClass: `v-input__${ref}`,
        ref,
      }, slot)
    },
    genPrependSlot () {
      const slot = []

      if (this.$slots.prepend) {
        slot.push(this.$slots.prepend)
      } else if (this.prependIcon) {
        slot.push(this.genIcon('prepend'))
      }

      return this.genSlot('prepend', 'outer', slot)
    },
    genAppendSlot () {
      const slot = []

      // Append icon for text field was really
      // an appended inner icon, v-text-field
      // will overwrite this method in order to obtain
      // backwards compat
      if (this.$slots.append) {
        slot.push(this.$slots.append)
      } else if (this.appendIcon) {
        slot.push(this.genIcon('append'))
      }

      return this.genSlot('append', 'outer', slot)
    },
    onClick (e: Event) {
      this.$emit('click', e)
    },
    onMouseDown (e: Event) {
      this.hasMouseDown = true
      this.$emit('mousedown', e)
    },
    onMouseUp (e: Event) {
      this.hasMouseDown = false
      this.$emit('mouseup', e)
    },
  },

  render (h): VNode {
    return h('div', this.setTextColor(this.validationState, {
      staticClass: 'v-input',
      class: this.classes,
    }), this.genContent())
  },
})


import VSubheader from './VSubheader'

export { VSubheader }
export default VSubheader


const even = maxLength % 2 === 0 ? 1 : 0


module.exports = {
  'v-alert': {
    slots: [
      {
        name: 'default',
        props: undefined,
      },
      {
        name: 'append',
        props: undefined,
      },
      {
        name: 'close',
        props: {
          toggle: 'Function',
        },
      },
      {
        name: 'prepend',
        props: undefined,
      },
    ],
    events: [
      {
        name: 'input',
        value: 'boolean',
      },
    ],
    functions: [
      {
        name: 'toggle',
        signature: '(): void',
      },
    ],
  },
}


---
meta:
  title: File input component
  description: The file input component is a specialized input that provides a clean interface for selecting files, showing detailed selection information and upload progress.
  keywords: file input, file upload, file field
related:
  - /components/text-fields/
  - /components/forms/
  - /components/icons/
---

# File inputs

The `v-file-input` component is a specialized input that provides a clean interface for selecting files, showing detailed selection information and upload progress. It is meant to be a direct replacement for a standard file input.

<entry-ad />

## Usage

At its core, the `v-file-input` component is a basic container that extends [v-text-field](/components/text-fields).

<usage name="v-file-input" />

## API

- [v-file-input](/api/v-file-input)

## Examples

### Props

#### Accept

`v-file-input` component can accept only specific media formats/file types if you want. For more information, checkout the documentation on the [accept attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept).

<example file="v-file-input/prop-accept" />

#### Chips

A selected file can be displayed as a [chip](/components/chips). When using the **chips** and **multiple** props, each chip will be displayed (as opposed to the file count).

<example file="v-file-input/prop-chips" />

#### Counter

When using the **show-size** property along with **counter**, the total number of files and size will be displayed under the input.

<example file="v-file-input/prop-counter" />

#### Dense

You can reduces the file input height with `dense` prop.

<example file="v-file-input/prop-dense" />

#### Multiple

The `v-file-input` can contain multiple files at the same time when using the **multiple** prop.

<example file="v-file-input/prop-multiple" />

#### Prepend icon

The `v-file-input` has a default `prepend-icon` that can be set on the component or adjusted globally. More information on changing global components can be found on the [customizing icons page](/features/icon-fonts).

<example file="v-file-input/prop-prepend-icon" />

#### Show size

The displayed size of the selected file(s) can be configured with the **show-size** property. Display sizes can be either _1024_ (the default used when providing **true**) or _1000_.

<example file="v-file-input/prop-show-size" />

#### Validation

Similar to other inputs, you can use the **rules** prop to create your own custom validation parameters.

<example file="v-file-input/prop-validation" />

### Slots

#### Selection

Using the `selection` slot, you can customize the appearance of your input selections. This is typically done with [chips](/components/chips), however any component or markup can be used.

<example file="v-file-input/slot-selection" />

### Misc

#### Complex selection slot

The flexibility of the selection slot allows you accommodate complex use-cases. In this example we show the first 2 selections as chips while adding a number indicator for the remaining amount.

<example file="v-file-input/misc-complex-selection" />

<backmatter />


<template>
  <div>
    <v-card
      class="d-flex align-content-space-between flex-wrap"
      color="grey lighten-2"
      flat
      tile
      min-height="200"
    >
      <v-card
        v-for="n in 20"
        :key="n"
        class="pa-2"
        outlined
        tile
      >
        Flex item
      </v-card>
    </v-card>
  </div>
</template>


const right = wrapper.vm.length - left + 1 + even

import './VPagination.sass'

import VIcon from '../VIcon'

// Directives
import Resize from '../../directives/resize'

// Mixins
import Colorable from '../../mixins/colorable'
import Intersectable from '../../mixins/intersectable'
import Themeable from '../../mixins/themeable'

// Utilities
import mixins from '../../util/mixins'

// Types
import { VNode, CreateElement, VNodeChildrenArrayContents } from 'vue'

/* @vue/component */
export default mixins(
  Colorable,
  Intersectable({ onVisible: ['init'] }),
  Themeable
).extend({
  name: 'v-pagination',

  directives: { Resize },

  props: {
    circle: Boolean,
    disabled: Boolean,
    length: {
      type: Number,
      default: 0,
      validator: (val: number) => val % 1 === 0,
    },
    nextIcon: {
      type: String,
      default: '$next',
    },
    prevIcon: {
      type: String,
      default: '$prev',
    },
    totalVisible: [Number, String],
    value: {
      type: Number,
      default: 0,
    },
    pageAriaLabel: {
      type: String,
      default: '$vuetify.pagination.ariaLabel.page',
    },
    currentPageAriaLabel: {
      type: String,
      default: '$vuetify.pagination.ariaLabel.currentPage',
    },
    previousAriaLabel: {
      type: String,
      default: '$vuetify.pagination.ariaLabel.previous',
    },
    nextAriaLabel: {
      type: String,
      default: '$vuetify.pagination.ariaLabel.next',
    },
    wrapperAriaLabel: {
      type: String,
      default: '$vuetify.pagination.ariaLabel.wrapper',
    },
  },

  data () {
    return {
      maxButtons: 0,
      selected: null as number | null,
    }
  },

  computed: {
    classes (): object {
      return {
        'v-pagination': true,
        'v-pagination--circle': this.circle,
        'v-pagination--disabled': this.disabled,
        ...this.themeClasses,
      }
    },

    items (): (string | number)[] {
      const totalVisible = parseInt(this.totalVisible, 10)

      if (totalVisible === 0) {
        return []
      }

      const maxLength = Math.min(
        Math.max(0, totalVisible) || this.length,
        Math.max(0, this.maxButtons) || this.length,
        this.length
      )

      if (this.length <= maxLength) {
        return this.range(1, this.length)
      }

      const even = maxLength % 2 === 0 ? 1 : 0
      const left = Math.floor(maxLength / 2)
      const right = this.length - left + 1 + even

      if (this.value > left && this.value < right) {
        const start = this.value - left + 2
        const end = this.value + left - 2 - even

        return [1, '...', ...this.range(start, end), '...', this.length]
      } else if (this.value === left) {
        const end = this.value + left - 1 - even
        return [...this.range(1, end), '...', this.length]
      } else if (this.value === right) {
        const start = this.value - left + 1
        return [1, '...', ...this.range(start, this.length)]
      } else {
        return [
          ...this.range(1, left),
          '...',
          ...this.range(right, this.length),
        ]
      }
    },
  },

  watch: {
    value () {
      this.init()
    },
  },

  mounted () {
    this.init()
  },

  methods: {
    init () {
      this.selected = null

      this.$nextTick(this.onResize)
      // TODO: Change this (f75dee3a, cbdf7caa)
      setTimeout(() => (this.selected = this.value), 100)
    },
    onResize () {
      const width = this.$el && this.$el.parentElement
        ? this.$el.parentElement.clientWidth
        : window.innerWidth

      this.maxButtons = Math.floor((width - 96) / 42)
    },
    next (e: Event) {
      e.preventDefault()
      this.$emit('input', this.value + 1)
      this.$emit('next')
    },
    previous (e: Event) {
      e.preventDefault()
      this.$emit('input', this.value - 1)
      this.$emit('previous')
    },
    range (from: number, to: number) {
      const range = []

      from = from > 0 ? from : 1

      for (let i = from; i <= to; i++) {
        range.push(i)
      }

      return range
    },
    genIcon (h: CreateElement, icon: string, disabled: boolean, fn: EventListener, label: String): VNode {
      return h('li', [
        h('button', {
          staticClass: 'v-pagination__navigation',
          class: {
            'v-pagination__navigation--disabled': disabled,
          },
          attrs: {
            disabled,
            type: 'button',
            'aria-label': label,
          },
          on: disabled ? {} : { click: fn },
        }, [h(VIcon, [icon])]),
      ])
    },
    genItem (h: CreateElement, i: string | number): VNode {
      const color: string | false = (i === this.value) && (this.color || 'primary')
      const isCurrentPage = i === this.value
      const ariaLabel = isCurrentPage ? this.currentPageAriaLabel : this.pageAriaLabel

      return h('button', this.setBackgroundColor(color, {
        staticClass: 'v-pagination__item',
        class: {
          'v-pagination__item--active': i === this.value,
        },
        attrs: {
          type: 'button',
          'aria-current': isCurrentPage,
          'aria-label': this.$vuetify.lang.t(ariaLabel, i),
        },
        on: {
          click: () => this.$emit('input', i),
        },
      }), [i.toString()])
    },
    genItems (h: CreateElement): VNode[] {
      return this.items.map((i, index) => {
        return h('li', { key: index }, [
          isNaN(Number(i)) ? h('span', { class: 'v-pagination__more' }, [i.toString()]) : this.genItem(h, i),
        ])
      })
    },
    genList (h: CreateElement, children: VNodeChildrenArrayContents): VNode {
      return h('ul', {
        directives: [{
          modifiers: { quiet: true },
          name: 'resize',
          value: this.onResize,
        }],
        class: this.classes,
      }, children)
    },
  },

  render (h): VNode {
    const children = [
      this.genIcon(h,
        this.$vuetify.rtl ? this.nextIcon : this.prevIcon,
        this.value <= 1,
        this.previous,
        this.$vuetify.lang.t(this.previousAriaLabel)),
      this.genItems(h),
      this.genIcon(h,
        this.$vuetify.rtl ? this.prevIcon : this.nextIcon,
        this.value >= this.length,
        this.next,
        this.$vuetify.lang.t(this.nextAriaLabel)),
    ]

    return h('nav', {
      attrs: {
        role: 'navigation',
        'aria-label': this.$vuetify.lang.t(this.wrapperAriaLabel),
      },
    }, [this.genList(h, children)])
  },
})


<template>
  <v-alert
    border="left"
    class="v-alert--doc"
    text
    v-bind="$attrs"
    v-on="$listeners"
  >
    <slot />
  </v-alert>
</template>

<script>
  export default {
    name: 'Alert',
  }
</script>

<style lang="sass">
  .v-alert--doc
    .v-alert__content p:last-child
      margin-bottom: 0 !important
</style>


<template>
  <v-card>
    <v-toolbar flat>
      <v-app-bar-nav-icon></v-app-bar-nav-icon>

      <v-toolbar-title>Contact Database</v-toolbar-title>

      <v-spacer></v-spacer>

      <v-btn icon>
        <v-icon>mdi-magnify</v-icon>
      </v-btn>

      <v-btn icon>
        <v-icon>mdi-dots-vertical</v-icon>
      </v-btn>

      <template v-slot:extension>
        <v-tabs
          v-model="tabs"
          fixed-tabs
        >
          <v-tabs-slider></v-tabs-slider>
          <v-tab
            href="#mobile-tabs-5-1"
            class="primary--text"
          >
            <v-icon>mdi-phone</v-icon>
          </v-tab>

          <v-tab
            href="#mobile-tabs-5-2"
            class="primary--text"
          >
            <v-icon>mdi-heart</v-icon>
          </v-tab>

          <v-tab
            href="#mobile-tabs-5-3"
            class="primary--text"
          >
            <v-icon>mdi-account-box</v-icon>
          </v-tab>
        </v-tabs>
      </template>
    </v-toolbar>

    <v-tabs-items v-model="tabs">
      <v-tab-item
        v-for="i in 3"
        :key="i"
        :value="'mobile-tabs-5-' + i"
      >
        <v-card flat>
          <v-card-text v-text="text"></v-card-text>
        </v-card>
      </v-tab-item>
    </v-tabs-items>
  </v-card>
</template>

<script>
  export default {
    data () {
      return {
        tabs: null,
        text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
      }
    },
  }
</script>


---
meta:
  title: Button group component
  description: The button group component allows you to combine a series of selectable buttons together in a single-line.
  keywords: button groups, vuetify button group component, vue button group component
related:
  - /components/buttons/
  - /components/icons/
  - /components/selection-controls/
---

# Button groups

The `v-btn-toggle` component is a simple wrapper for `v-item-group` built specifically to work with `v-btn`.

<entry-ad />

## Usage

Toggle buttons allow you to create a styled group of buttons that can be selected or toggled under a single **v-model**.

<example file="v-btn-toggle/usage" />

## API

- [v-btn-toggle](/api/v-btn-toggle)
- [v-btn](/api/v-btn)

## Examples

### Props

#### Mandatory

A `v-btn-toggle` with the **mandatory** prop will always have a value.

<example file="v-btn-toggle/prop-mandatory" />

#### Multiple

A `v-btn-toggle` with the **multiple** prop will allow a user to select multiple return values as an array.

<example file="v-btn-toggle/prop-multiple" />

#### Rounded

You can make `v-btn-toggle` rounded using the **rounded** prop.

<example file="v-btn-toggle/prop-rounded" />

### Misc

#### Toolbar

Easily integrate customized button solutions with a `v-toolbar`

<example file="v-btn-toggle/misc-toolbar" />

#### WYSIWYG

Group similar actions and design your own WYSIWYG component.

<example file="v-btn-toggle/misc-wysiwyg" />

<backmatter />


keep keep keep keep keep replace replace keep keep keep keep keep


import VRangeSlider from './VRangeSlider'

export { VRangeSlider }
export default VRangeSlider


import { VNodeDirective } from 'vue/types/vnode'

type ObserveHandler = (
  entries: IntersectionObserverEntry[],
  observer: IntersectionObserver,
  isIntersecting: boolean,
) => void

interface ObserveVNodeDirective extends Omit<VNodeDirective, 'modifiers'> {
  value?: ObserveHandler | { handler: ObserveHandler, options?: IntersectionObserverInit }
  modifiers?: {
    once?: boolean
    quiet?: boolean
  }
}

function inserted (el: HTMLElement, binding: ObserveVNodeDirective) {
  if (typeof window === 'undefined' || !('IntersectionObserver' in window)) return

  const modifiers = binding.modifiers || {}
  const value = binding.value
  const { handler, options } = typeof value === 'object'
    ? value
    : { handler: value, options: {} }
  const observer = new IntersectionObserver((
    entries: IntersectionObserverEntry[] = [],
    observer: IntersectionObserver
  ) => {
    /* istanbul ignore if */
    if (!el._observe) return // Just in case, should never fire

    const isIntersecting = entries.some(entry => entry.isIntersecting)

    // If is not quiet or has already been
    // initted, invoke the user callback
    if (
      handler && (
        !modifiers.quiet ||
        el._observe.init
      ) && (
        !modifiers.once ||
        isIntersecting ||
        !el._observe.init
      )
    ) {
      handler(entries, observer, isIntersecting)
    }

    if (isIntersecting && modifiers.once) unbind(el)
    else el._observe.init = true
  }, options)

  el._observe = { init: false, observer }

  observer.observe(el)
}

function unbind (el: HTMLElement) {
  /* istanbul ignore if */
  if (!el._observe) return

  el._observe.observer.unobserve(el)
  delete el._observe
}

export const Intersect = {
  inserted,
  unbind,
}

export default Intersect


'use strict'
const spawn = require('child_process').spawn

const args = process.argv.slice(2)

let child

if (process.platform === 'win32') {
  // yarn test -i
  child = spawn('yarn.cmd', ['test:win32', ...args], { stdio: 'inherit' })
} else {
  // yarn test
  child = spawn('yarn', ['test:unix', ...args], { stdio: 'inherit' })
}

child.on('exit', process.exit)


keep keep keep keep replace replace replace keep keep keep keep keep

import VPagination from '../VPagination'
import {
  mount,
  MountOptions,
  Wrapper,
} from '@vue/test-utils'
import Vue from 'vue'

Vue.prototype.$vuetify = {
  rtl: false,
  icons: {
    values: {
      next: 'mdi-chevron-right',
      prev: 'mdi-chevron-left',
    },
  },
  lang: {
    t: str => str,
  },
}

describe('VPagination.ts', () => {
  type Instance = InstanceType<typeof VPagination>
  let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
  beforeEach(() => {
    jest.useFakeTimers()

    mountFunction = (options?: MountOptions<Instance>) => {
      return mount(VPagination, {
        mocks: {
          $vuetify: {
            lang: {
              t: str => str,
            },
          },
        },
        ...options,
      })
    }
  })

  it('emits an event when next or previous is clicked', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 5,
        value: 2,
      },
    })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    const previous = jest.fn()
    const next = jest.fn()

    wrapper.vm.$on('previous', previous)
    wrapper.vm.$on('next', next)

    const navigation = wrapper.findAll('.v-pagination__navigation').wrappers
    navigation[0].trigger('click')
    navigation[1].trigger('click')

    expect(next).toHaveBeenCalled()
    expect(previous).toHaveBeenCalled()
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should render component in RTL mode and match snapshot', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 5,
        value: 2,
      },
    })
    wrapper.vm.$vuetify.rtl = true
    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    wrapper.vm.$vuetify.rtl = undefined
  })

  it('emits an event when pagination item is clicked', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 5,
        value: 2,
      },
    })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    const cb = jest.fn()

    wrapper.vm.$on('input', cb)

    const navigation = wrapper.findAll('.v-pagination__item').wrappers
    navigation[1].trigger('click')

    expect(cb).toHaveBeenCalledWith(2)
  })

  it('should render disabled buttons with length equals to 0', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 0,
        value: 1,
      },
    })
    jest.runAllTimers()

    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should watch the value', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 5,
        value: 1,
      },
    })

    jest.runAllTimers()
    expect(wrapper.vm.selected).toBe(1)

    wrapper.setProps({ value: 2 })
    jest.runAllTimers()
    expect(wrapper.vm.selected).toBe(2)
  })

  it('should only render start and end of range if length is big', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 100,
      },
    })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.findAll('.v-pagination__more').wrappers).toHaveLength(1)
  })

  it('should only render middle of range if length is big and value is somewhere in the middle', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 100,
        value: 50,
      },
    })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.findAll('.v-pagination__more').wrappers).toHaveLength(2)
  })

  it('should only render start of range if value is equals "left"', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 100,
        totalVisible: 5,
      },
    })
    const maxLength = wrapper.vm.totalVisible
    const left = Math.floor(maxLength / 2)
    wrapper.setProps({ value: left })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.findAll('.v-pagination__more').wrappers).toHaveLength(1)
  })

  it('should only render end of range if value is equals "right"', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 100,
        totalVisible: 5,
      },
    })
    const maxLength = wrapper.vm.totalVisible
    const even = maxLength % 2 === 0 ? 1 : 0
    const left = Math.floor(maxLength / 2)
    const right = wrapper.vm.length - left + 1 + even
    wrapper.setProps({ value: right })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.findAll('.v-pagination__more').wrappers).toHaveLength(1)
  })

  it('should use totalVisible prop if defined', async () => {
    const wrapper = mountFunction({
      propsData: {
        length: 100,
        value: 50,
        totalVisible: 10,
      },
    })
    jest.runAllTimers()

    await wrapper.vm.$nextTick()

    expect(wrapper.html()).toMatchSnapshot()
    expect(wrapper.findAll('.v-pagination__more').wrappers).toHaveLength(2)
    expect(wrapper.findAll('.v-pagination__item').wrappers).toHaveLength(8)
  })

  it('should set from to 1 if <= 0', () => {
    const wrapper = mountFunction()

    expect(wrapper.vm.range(1, 2)).toEqual([1, 2])
    expect(wrapper.vm.range(0, 2)).toEqual([1, 2])
  })

  // Since we have no DOM access, test the expected outcome
  // even if it's not real world, so that we can detect changes
  it('should use parents width for on resize calculation', () => {
    const wrapper = mount({
      functional: true,
      render: h => h('div', [h(VPagination)]),
    })

    const pagination = wrapper.find(VPagination.options)

    expect(pagination.vm.maxButtons).toBe(0)

    pagination.vm.onResize()

    expect(pagination.vm.maxButtons).toBe(-3)
  })

  // https://github.com/vuetifyjs/vuetify/issues/7947
  it('should never show more than the max number of allowed buttons', () => {
    const wrapper = mountFunction({
      data: () => ({
        maxButtons: 4,
      }),

      propsData: {
        length: 40,
        totalVisible: 10,
      },
    })

    expect(wrapper.vm.items).toHaveLength(4)

    wrapper.setData({ maxButtons: 12 })

    expect(wrapper.vm.items).toHaveLength(10)
  })

  it('should never show more than the number of total visible buttons', () => {
    const wrapper = mountFunction({
      data: () => ({
        maxButtons: 0,
      }),

      propsData: {
        length: 5,
        totalVisible: undefined,
      },
    })

    expect(wrapper.vm.items).toHaveLength(5)

    wrapper.setProps({ length: 40 })

    wrapper.setData({ maxButtons: 0 })
    wrapper.setProps({ totalVisible: 10 })
    expect(wrapper.vm.items).toHaveLength(10)

    wrapper.setData({ maxButtons: 11 })
    wrapper.setProps({ totalVisible: undefined })
    expect(wrapper.vm.items).toHaveLength(11)

    wrapper.setData({ maxButtons: 12 })
    wrapper.setProps({ totalVisible: 13 })
    expect(wrapper.vm.items).toHaveLength(12)
  })

  it('should return length when maxButtons is less than 1', () => {
    const wrapper = mountFunction({
      data: () => ({ maxButtons: -3 }),
      propsData: { length: 4 },
    })

    expect(wrapper.vm.items).toEqual([1, 2, 3, 4])
  })
})


import { Component } from 'vue'
// Services
import {
  Breakpoint,
  BreakpointOptions,
} from 'vuetify/types/services/breakpoint'
import {
  Icons,
  IconsOptions,
} from 'vuetify/types/services/icons'
import {
  Lang,
  LangOptions,
} from 'vuetify/types/services/lang'
import {
  Theme,
  ThemeOptions,
} from 'vuetify/types/services/theme'

export interface VuetifyPreset {
  breakpoint: {
    mobileBreakpoint: Breakpoint['mobileBreakpoint']
    scrollBarWidth: Breakpoint['scrollBarWidth']
    thresholds: Breakpoint['thresholds']
  }
  icons: {
    component?: Icons['component']
    iconfont: Icons['iconfont']
    // TODO: Remove partial for v3
    values: Partial<Icons['values']>
  }
  lang: {
    current: Lang['current']
    locales: Lang['locales']
    t: Lang['t']
  }
  theme: {
    dark: Theme['dark']
    default: Theme['default']
    disable: Theme['disable']
    options: Theme['options']
    themes: Theme['themes']
  }

  [name: string]: any
}

export interface UserVuetifyPreset extends GlobalVuetifyPreset {
  preset?: GlobalVuetifyPreset

  [name: string]: any
}

export interface GlobalVuetifyPreset {
  breakpoint?: BreakpointOptions
  icons?: IconsOptions
  lang?: LangOptions
  theme?: ThemeOptions

  [name: string]: any
}

export interface Presets {
  preset: UserVuetifyPreset
}