npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@uipath/ui-widgets-validation-station

v1.0.0

Published

A React component for UiPath Document Understanding Validation Station.

Readme

@uipath/ui-widgets-validation-station

A React wrapper for the UiPath Document Understanding Validation Station. It handles web component loading, bucket artifact fetching, and exposes a declarative props API for all Validation Station features.

Installation

npm install @uipath/ui-widgets-validation-station

Peer dependencies

react >= 19.2.0
react-dom >= 19.2.0
@uipath/uipath-typescript >= 1.4.2

Quick start

Note: Add either light or dark class to your HTML <body> element to enable proper theming.

import {
  ValidationStation,
  ValidationStationLanguage,
} from "@uipath/ui-widgets-validation-station";
import { UiPath } from "@uipath/uipath-typescript/core";
import type { DuFramework } from "@uipath/uipath-typescript/document-understanding";

const sdk = new UiPath({
  baseUrl: "https://cloud.uipath.com",
  orgName: "your-org",
  tenantName: "your-tenant",
  secret: "your-secret",
});

await sdk.initialize();

function App() {
  return (
    <ValidationStation
      sdk={sdk}
      data={selectedTask.data as DuFramework.ContentValidationData}
      folderId={12345}
    />
  );
}

theme defaults to "light" and language defaults to ValidationStationLanguage.English, so the minimal mount just needs sdk, data, and a folder.

See Static assets & runtime stylesheets below — you must copy the web component's du-assets/ folder and its stylesheets into your build output (and serve raw CSS in dev), or PDF rendering, translations, and icons will silently break with no build error.

Props

| Prop | Type | Required | Default | Description | | -------------------------------- | ---------------------------------------------- | -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | sdk | UiPath | Yes | — | UiPath SDK instance for authentication and API calls | | data | ContentValidationData | Yes | — | Document data containing bucket paths, document ID, and folder references | | folderId | number | No* | — | Storage bucket folder ID. Falls back to data.FolderId. One of the two must resolve to a value — otherwise the widget show an error. | | theme | 'light' \| 'dark' \| 'light-hc' \| 'dark-hc' | No | 'light' | Visual theme | | language | ValidationStationLanguage | No | English | UI language (see enum below) | | isReadonly | boolean | No | false | When true, renders in read-only mode | | options | IValidationStationOptions | No | — | Fine-grained UI feature flags | | save={{ validate: false }} | { validate: boolean } | No | — | Trigger save as draft. ⚠ Requires options.emitDtoStateChanges: true — otherwise the web component won't surface the latest in-memory extraction state and the save will be a no-op. | | save={{ validate: true }} | { validate: boolean } | No | — | Trigger submit — runs validation first, then saves. | | discardChanges | { value: boolean } | No | — | Trigger a discard-changes operation. Call setDiscardChanges({ value: true }) (or false — the boolean is ignored) every time you want it to fire. Each call creates a brand-new object even if the content looks identical, and that's what the widget watches for — so calling it repeatedly with the same { value: true } works just fine. | | setFieldValueByPath | SetFieldValueByPath | No | — | Set a field value addressed by a path of { fieldName, valueIndex } segments | | selectAndFocusFieldValueByPath | SelectAndFocusFieldValueByPath | No | — | Select and focus a field value addressed by a path; focuses the document reference if any | | deleteFieldValueByPath | DeleteFieldValueByPath | No | — | Delete a field value addressed by a path |

Three additional callback props (onSubmitComplete, onSaveAsDraftComplete, onReportExceptionComplete) are documented in the next section.

Reacting to save / draft / exception flows

The widget surfaces three user-initiated flows. Submit and draft are owned end-to-end by the widget; exception reporting is forwarded to the host so it can call the SDK directly.

| Callback | User action | Signature | What the widget does | What the host does | | --------------------------- | ----------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | | onSubmitComplete | Submit | (result: SaveValidatedDataResult) => void | Calls OrchestratorDuModule.processExtractedData(...), then uploads the merged result to ValidatedExtractionResultsPath. Fires the callback with the persistence result. | (optional) react to success/failure (complete the task, retry, log, etc.). | | onSaveAsDraftComplete | Save as draft | (result: SaveValidatedDataResult) => void | Uploads the in-progress validatedData straight to ValidatedExtractionResultsPath (no processExtractedData call). Fires the callback with the persistence result. | (optional) react to success/failure. | | onReportExceptionComplete | Report as exception | (documentId: string, reason: string) => void | Extracts documentId and reason from the web component's exception DTO and hands them to the host. No API call. | Required if you want the report persisted — call OrchestratorDuModule.submitExceptionReport(...) yourself. |

Submit/draft hand you a SaveValidatedDataResult ({ success, error? }) — the host owns all UI feedback (toast, retry, etc.); the widget does not surface failures itself. The exception callback hands you documentId and reason strings ready to forward to the SDK.

import {
  ValidationStation,
  type SaveValidatedDataResult,
} from "@uipath/ui-widgets-validation-station";
import { OrchestratorDuModule } from "@uipath/uipath-typescript/orchestrator-du-module";

function App({ sdk, data, task }) {
  const handleSubmitComplete = async (result: SaveValidatedDataResult) => {
    if (!result.success) {
      console.warn("Submit failed:", result.error);
      return;
    }
    await task.complete({ action: "Completed", type: "DocumentValidation" });
  };

  const handleDraftComplete = (result: SaveValidatedDataResult) => {
    if (!result.success) console.warn("Draft save failed:", result.error);
  };

  const handleReportException = async (documentId: string, reason: string) => {
    const response = await new OrchestratorDuModule(sdk).submitExceptionReport(
      task.id,
      documentId,
      reason || "Reported via Validation Station",
      { folderId: task.folderId },
    );
    if (!response.IsSuccessful) {
      console.error("submitExceptionReport failed:", response.ErrorMessage);
    }
  };

  return (
    <ValidationStation
      sdk={sdk}
      data={data}
      folderId={task.folderId}
      onSubmitComplete={handleSubmitComplete}
      onSaveAsDraftComplete={handleDraftComplete}
      onReportExceptionComplete={handleReportException}
    />
  );
}

Submit and draft callbacks are optional, but failures are silent if you skip them — the widget does not surface errors on its own. The exception callback is the only place the report goes; without it the user's "Report as exception" click is a no-op.

Language enum

ValidationStationLanguage provides all supported locales:

import { ValidationStationLanguage } from "@uipath/ui-widgets-validation-station";

ValidationStationLanguage.English; // "en"
ValidationStationLanguage.German; // "de"
ValidationStationLanguage.Spanish; // "es"
ValidationStationLanguage.SpanishMexico; // "es-MX"
ValidationStationLanguage.French; // "fr"
ValidationStationLanguage.Japanese; // "ja"
ValidationStationLanguage.Korean; // "ko"
ValidationStationLanguage.Portuguese; // "pt"
ValidationStationLanguage.PortugueseBrazil; // "pt-BR"
ValidationStationLanguage.Romanian; // "ro"
ValidationStationLanguage.Russian; // "ru"
ValidationStationLanguage.Turkish; // "tr"
ValidationStationLanguage.ChineseSimplified; // "zh-CN"
ValidationStationLanguage.ChineseTraditional; // "zh-TW"

Exported types

All parameter types are re-exported from the package for convenience:

import { ValidationStationLanguage } from "@uipath/ui-widgets-validation-station";
import type {
  ValidationStationProps,
  IValidationStationOptions,
  SaveValidatedDataResult,
  SetFieldValueByPath,
  SelectAndFocusFieldValueByPath,
  DeleteFieldValueByPath,
} from "@uipath/ui-widgets-validation-station";

Examples

Setting a field value by path

Address fields by path when you have nested groups or table rows. Each segment is { fieldName, valueIndex }.

import { useState } from "react";
import {
  ValidationStation,
  type SetFieldValueByPath,
} from "@uipath/ui-widgets-validation-station";

function App({ sdk, data }) {
  const [fieldValueByPath, setFieldValueByPath] = useState<
    SetFieldValueByPath | undefined
  >(undefined);

  return (
    <>
      <button
        onClick={() =>
          setFieldValueByPath({
            path: [
              { fieldName: "Invoice", valueIndex: 0 }, // parent field name
              { fieldName: "Amount", valueIndex: 0 }, // child field name
            ],
            update: { Value: "100.00", OperatorConfirmed: true },
          })
        }
      >
        Set Amount by path
      </button>
      <ValidationStation
        sdk={sdk}
        data={data}
        folderId={67}
        setFieldValueByPath={fieldValueByPath}
      />
    </>
  );
}

Focusing a field by path

import { useState } from "react";
import {
  ValidationStation,
  type SelectAndFocusFieldValueByPath,
} from "@uipath/ui-widgets-validation-station";

function App({ sdk, data }) {
  const [focus, setFocus] = useState<
    SelectAndFocusFieldValueByPath | undefined
  >(undefined);

  return (
    <>
      <button
        onClick={() =>
          setFocus({
            path: [
              { fieldName: "Invoice", valueIndex: 0 }, // parent field name
              { fieldName: "Amount", valueIndex: 0 }, // child field name
            ],
          })
        }
      >
        Focus Amount by path
      </button>
      <ValidationStation
        sdk={sdk}
        data={data}
        folderId={67}
        selectAndFocusFieldValueByPath={focus}
      />
    </>
  );
}

Triggering save

const [save, setSave] = useState<{ validate: boolean } | undefined>(undefined);

<button onClick={() => setSave({ validate: true })}>Save</button>
<ValidationStation sdk={sdk} data={data} folderId={1} save={save} />

Static assets & runtime stylesheets

The underlying web component resolves several files at runtime, relative to where its main bundle is served (via import.meta.url):

  • du-assets/ — PDF.js worker, cmaps, wasm, and i18n translations.
  • styles.css — fetched as raw CSS text and adopted into the component's shadow root. This is what styles the icons (<mat-icon>) and everything else inside the shadow boundary.
  • fonts.css + media/ — the Apollo / Material Icons @font-face declarations and the font files they reference.

The React wrapper already imports styles.css and fonts.css as ES modules for you, so the light DOM concerns (@font-face registration, plus CDK overlays like menus/tooltips that portal to document.body) are handled automatically — you do not add those imports yourself. What you still have to handle is making the same files reachable by the web component's runtime fetch, covered below.

These files must be deployed at the same path level as your output bundle. If they're missing there's no build error — they silently 404 at runtime: PDFs fail to render, and because the shadow root never receives styles.css, icons fall back to a system font and render as empty boxes or raw text.

There are two things to get right:

  1. Build — copy du-assets/, styles.css, fonts.css, and media/ next to your emitted JS chunks.
  2. Dev server — if your dev server rewrites .css requests into JS modules (Vite does this), the web component's fetch("styles.css") receives JavaScript instead of CSS, CSSStyleSheet.replaceSync() parses nothing, and the shadow-root styles never load (→ broken icons). You must serve the raw CSS for that fetch. Bundlers that serve copied files verbatim in dev (e.g. webpack-dev-server) don't have this problem — copying alone is enough.

Vite

Two plugins: one copies the runtime files after a build, one serves raw CSS to the web component's fetch during dev. optimizeDeps.exclude is also required — Vite's pre-bundler rewrites import.meta.url, which breaks the web component's runtime resolution.

// vite.config.ts
import react from "@vitejs/plugin-react";
import { cp, readFile } from "node:fs/promises";
import { createRequire } from "node:module";
import { dirname, resolve } from "node:path";
import { defineConfig, type Plugin } from "vite";

const require = createRequire(import.meta.url);

const WC_ROOT = dirname(
  require.resolve("@uipath/du-validation-station-wc/package.json"),
);

// Stylesheets the web component fetches (as raw CSS) at runtime to adopt into its
// shadow root.
const WC_RUNTIME_CSS = ["styles.css", "fonts.css"];

// BUILD: place the web component's runtime files next to the emitted JS chunks, where
// `import.meta.url` will resolve them.
function copyDuValidationStationAssets(): Plugin {
  let assetsDir = "";
  return {
    name: "copy-du-validation-station-assets",
    apply: "build",
    configResolved(config) {
      assetsDir = resolve(
        config.root,
        config.build.outDir,
        config.build.assetsDir,
      );
    },
    async closeBundle() {
      await cp(resolve(WC_ROOT, "du-assets"), resolve(assetsDir, "du-assets"), {
        recursive: true,
      });
      await cp(resolve(WC_ROOT, "media"), resolve(assetsDir, "media"), {
        recursive: true,
      });
      for (const css of WC_RUNTIME_CSS) {
        await cp(resolve(WC_ROOT, css), resolve(assetsDir, css));
      }
    },
  };
}

// DEV: Vite serves any `.css` request as a JS module. Return the real CSS to
// the web component's raw `fetch` (identified by `Sec-Fetch-Dest: empty`), while letting
// genuine ES-module imports (`Sec-Fetch-Dest: script`) pass through to Vite.
function serveDuValidationStationRawCss(): Plugin {
  const pattern = new RegExp(
    `/@uipath/du-validation-station-wc/(${WC_RUNTIME_CSS.join("|")})$`,
  );
  return {
    name: "serve-du-validation-station-raw-css",
    apply: "serve",
    configureServer(server) {
      server.middlewares.use((req, res, next) => {
        if (req.headers["sec-fetch-dest"] !== "empty") return next();
        const match = pattern.exec((req.url ?? "").split("?")[0]);
        if (!match) return next();
        readFile(resolve(WC_ROOT, match[1]), "utf8").then((css) => {
          res.setHeader("Content-Type", "text/css");
          res.end(css);
        }, next);
      });
    },
  };
}

export default defineConfig({
  plugins: [
    react(),
    copyDuValidationStationAssets(),
    serveDuValidationStationRawCss(),
  ],
  optimizeDeps: {
    exclude: ["@uipath/du-validation-station-wc"],
  },
});

webpack

Use copy-webpack-plugin to copy du-assets/, the stylesheets, and media/ next to your bundle, and opt the web component bundle out of webpack's new URL(..., import.meta.url) parsing — the web component uses that pattern for its runtime resolution, and webpack will otherwise try to bundle the directory and fail with Module not found: Error: Can't resolve './du-assets/'. webpack-dev-server serves the copied files verbatim, so no separate raw-CSS handling is needed.

// webpack.config.js
const CopyPlugin = require("copy-webpack-plugin");
const path = require("path");

const wcRoot = path.dirname(
  require.resolve("@uipath/du-validation-station-wc/package.json"),
);

module.exports = {
  module: {
    rules: [
      // Don't parse runtime URL / dynamic-require expressions inside the web component —
      // its assets and stylesheets are resolved at runtime from `import.meta.url`.
      {
        test: /node_modules[\\/]@uipath[\\/]du-validation-station-wc[\\/].*\.js$/,
        parser: {
          url: false,
          exprContextCritical: false,
          unknownContextCritical: false,
        },
      },
    ],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: `${wcRoot}/du-assets`, to: "assets/du-assets" },
        { from: `${wcRoot}/media`, to: "assets/media" },
        { from: `${wcRoot}/styles.css`, to: "assets/styles.css" },
        { from: `${wcRoot}/fonts.css`, to: "assets/fonts.css" },
      ],
    }),
  ],
};

Other bundlers

Any asset-copy mechanism works — Angular's assets array, rollup-plugin-copy, a postbuild npm script with cp -r, etc. The requirement is the same: the final deployed layout must have du-assets/, styles.css, fonts.css, and media/ sitting next to the JS chunks that import the web component. If your dev server transforms .css into JS modules, also make sure the web component's runtime fetch for styles.css/fonts.css receives raw CSS.

Development

# Build
npm run build

# Run tests
npm run test

# Run tests in watch mode
npm run test:watch