diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..3bc38448
Binary files /dev/null and b/.DS_Store differ
diff --git a/.gitignore b/.gitignore
index 4f7d1bfd..2ab1accd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,5 @@ node_modules
.build
.idea
web_modules
-dist
-dist-commonjs
-types
-coverage
\ No newline at end of file
+coverage
+.claude
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 00000000..a0c96954
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,5 @@
+# @storybook/react@6 and some other dev tools declare React ^16/17 peer deps
+# but work fine with React 18/19. Legacy mode skips strict peer dep checking
+# for the development environment only. Runtime dependencies are fully React 19
+# compatible.
+legacy-peer-deps=true
diff --git a/.storybook/main.ts b/.storybook/main.ts
index 3c23cbc7..3d9b3f98 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -1,27 +1,12 @@
-const path = require("path")
+import type { StorybookConfig } from "@storybook/react-vite"
-const toPath = (_path: string) => path.join(process.cwd(), _path)
-
-module.exports = {
+const config: StorybookConfig = {
stories: ["../src/**/stories/*.stories.tsx"],
- webpackFinal: async (config: any) => {
- config.module.rules.push({
- test: /\.mjs$/,
- include: /node_modules/,
- type: "javascript/auto",
- })
- return {
- ...config,
- devtool: "inline-source-map",
- resolve: {
- ...config.resolve,
- alias: {
- ...config.resolve.alias,
- "@emotion/core": toPath("node_modules/@emotion/react"),
- "emotion-theming": toPath("node_modules/@emotion/react"),
- },
- },
- }
+ framework: {
+ name: "@storybook/react-vite",
+ options: {},
},
staticDirs: ["../src/stories/static"],
}
+
+export default config
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index ab42a110..00000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2022 UGNIS,
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index f6401be1..0c0b708a 100644
--- a/README.md
+++ b/README.md
@@ -1,337 +1,79 @@
-
RSI react-spreadsheet-import โก๏ธ
+# @icon/react-spreadsheet-importer
-
-
-
- [](https://www.npmjs.com/package/react-spreadsheet-import)
-
-
-
+Drop-in spreadsheet (`.xlsx` / `.xls` / `.csv`) importer modal for React 19 + Bootstrap 5 + Yup.
-A component used for importing XLS / XLSX / CSV documents built with [**Chakra UI**](https://chakra-ui.com). Import flow combines:
+A focused reimplementation of [`react-spreadsheet-import`](https://github.com/UgnisSoftware/react-spreadsheet-import):
-- ๐ฅ Uploader
-- โ๏ธ Parser
-- ๐ File preview
-- ๐งช UI for column mapping
-- โ UI for validating and editing data
+- React 19 compatible
+- Bootstrap 5 (`react-bootstrap`) instead of Chakra UI
+- Yup schema as the validation source of truth
+- `exceljs` for parsing
+- `react-data-grid` for the editable validation step
+- `fuse.js` for auto-mapping spreadsheet headers to template fields
-โจ [**Demo**](https://ugnissoftware.github.io/react-spreadsheet-import/iframe.html?id=react-spreadsheet-import--basic&args=&viewMode=story) โจ
-
+## Install
-## Features
-
-- Custom styles - edit Chakra UI theme to match your project's styles ๐จ
-- Custom validation rules - make sure valid data is being imported, easily spot and correct errors
-- Hooks - alter raw data after upload or make adjustments on data changes
-- Auto-mapping columns - automatically map most likely value to your template values, e.g. `name` -> `firstName`
-
-
-
-
-## Figma
-
-We provide full figma designs. You can copy the designs
-[here](https://www.figma.com/community/file/1080776795891439629)
-
-## Getting started
-
-```sh
-npm i react-spreadsheet-import
+```bash
+npm install @icon/react-spreadsheet-importer
```
-Using the component: (it's up to you when the flow is open and what you do on submit with the imported data)
-
-```tsx
-import { ReactSpreadsheetImport } from "react-spreadsheet-import";
-
-
-```
+Peer deps: `react`, `react-dom`, `react-bootstrap`, `bootstrap`, `yup`.
-## Required Props
+## Usage
```tsx
- // Determines if modal is visible.
- isOpen: Boolean
- // Called when flow is closed without reaching submit.
- onClose: () => void
- // Called after user completes the flow. Provides data array, where data keys matches your field keys.
- onSubmit: (data) => void
-```
+import "bootstrap/dist/css/bootstrap.min.css";
+import "@icon/react-spreadsheet-importer/styles.css";
-### Fields
+import { ReactSpreadsheetImport } from "@icon/react-spreadsheet-importer";
+import * as yup from "yup";
-Fields describe what data you are trying to collect.
+const schema = yup.object({
+ firstName: yup.string().required().max(20),
+ lastName: yup.string().required().max(20),
+ email: yup.string().email().required(),
+});
-```tsx
const fields = [
- {
- // Visible in table header and when matching columns.
- label: "Name",
- // This is the key used for this field when we call onSubmit.
- key: "name",
- // Allows for better automatic column matching. Optional.
- alternateMatches: ["first name", "first"],
- // Used when editing and validating information.
- fieldType: {
- // There are 3 types - "input" / "checkbox" / "select".
- type: "input",
- },
- // Used in the first step to provide an example of what data is expected in this field. Optional.
- example: "Stephanie",
- // Can have multiple validations that are visible in Validation Step table.
- validations: [
- {
- // Can be "required" / "unique" / "regex"
- rule: "required",
- errorMessage: "Name is required",
- // There can be "info" / "warning" / "error" levels. Optional. Default "error".
- level: "error",
- },
- ],
- },
-] as const
-```
-
-## Optional Props
-
-### Hooks
-
-You can transform and validate data with custom hooks. There are hooks after each step:
-
-- **uploadStepHook** - runs only once after uploading the file.
-- **selectHeaderStepHook** - runs only once after selecting the header row in spreadsheet.
-- **matchColumnsStepHook** - runs only once after column matching. Operations on data that are expensive should be done here.
-
-The last step - validation step has 2 unique hooks that run only in that step with different performance tradeoffs:
+ { key: "firstName", label: "First Name", required: true, alternateMatches: ["first"], example: "John" },
+ { key: "lastName", label: "Last Name", required: true, alternateMatches: ["last"], example: "Doe" },
+ { key: "email", label: "Email", required: true, unique: true, example: "user@company.com" },
+] as const;
-- **tableHook** - runs at the start and on any change. Runs on all rows. Very expensive, but can change rows that depend on other rows.
-- **rowHook** - runs at the start and on any row change. Runs only on the rows changed. Fastest, most validations and transformations should be done here.
-
-Example:
-
-```tsx
{
- // Validation
- if (data.name === "John") {
- addError("name", { message: "No Johns allowed", level: "info" })
- }
- // Transformation
- return { ...data, name: "Not John" }
- // Sorry John
+ isOpen={open}
+ onClose={() => setOpen(false)}
+ fields={fields}
+ schema={schema}
+ allowInvalidSubmit={false}
+ maxRecords={1000}
+ onSubmit={(result, file) => {
+ console.log(result.validData, result.invalidData);
}}
/>
```
-### Initial state
-
-In rare case when you need to skip the beginning of the flow, you can start the flow from any of the steps.
-
-- **initialStepState** - initial state of component that will be rendered on load.
-
-```tsx
- initialStepState?: StepState
-
- type StepState =
- | {
- type: StepType.upload
- }
- | {
- type: StepType.selectSheet
- workbook: XLSX.WorkBook
- }
- | {
- type: StepType.selectHeader
- data: RawData[]
- }
- | {
- type: StepType.matchColumns
- data: RawData[]
- headerValues: RawData
- }
- | {
- type: StepType.validateData
- data: any[]
- }
-
- type RawData = Array
-
- // XLSX.workbook type is native to SheetJS and can be viewed here: https://github.com/SheetJS/sheetjs/blob/83ddb4c1203f6bac052d8c1608b32fead02ea32f/types/index.d.ts#L269
-```
-
-Example:
-
-```tsx
-import { ReactSpreadsheetImport, StepType } from "react-spreadsheet-import";
-
-
-```
-
-### Dates and time
-
-Excel stores dates and times as numbers - offsets from an epoch. When reading xlsx files SheetJS provides date formatting helpers.
-**Default date import format** is `yyyy-mm-dd`. Date parsing with SheetJS sometimes yields unexpected results, therefore thorough date validations are recommended.
-
-- **dateFormat** - sets SheetJS `dateNF` option. Can be used to format dates when importing sheet data.
-- **parseRaw** - sets SheetJS `raw` option. If `true`, date formatting will be applied to XLSX date fields only. Default is `true`
-
-Common date-time formats can be viewed [here](https://docs.sheetjs.com/docs/csf/features/dates/#date-and-time-number-formats).
-
-### Other optional props
-
-```tsx
- // Allows submitting with errors. Default: true
- allowInvalidSubmit?: boolean
- // Translations for each text. See customisation bellow
- translations?: object
- // Theme configuration passed to underlying Chakra-UI. See customisation bellow
- customTheme?: object
- // Specifies maximum number of rows for a single import
- maxRecords?: number
- // Maximum upload filesize (in bytes)
- maxFileSize?: number
- // Automatically map imported headers to specified fields if possible. Default: true
- autoMapHeaders?: boolean
- // Headers matching accuracy: 1 for strict and up for more flexible matching. Default: 2
- autoMapDistance?: number
-```
-
-## Customisation
-
-### Customising styles (colors, fonts)
-
-You can see default theme we use [here](https://github.com/UgnisSoftware/react-spreadsheet-import/blob/master/src/theme.ts). Your override should match this object's structure.
-
-There are 3 ways you can style the component:
-
-1.) Change theme colors globally
-
-```jsx
-
-```
-
-
-
-2.) Change all components of the same type, like all Buttons, at the same time
-
-```jsx
-
-```
-
-
-
-3.) Change components specifically in each Step.
-```jsx
-
-```
-
-
-Underneath we use Chakra-UI, you can send in a custom theme for us to apply. Read more about themes [here](https://chakra-ui.com/docs/styled-system/theming/theme)
-
-### Changing text (translations)
-
-You can change any text in the flow:
-
-```tsx
-
-```
-
-You can see all the translation keys [here](https://github.com/UgnisSoftware/react-spreadsheet-import/blob/master/src/translationsRSIProps.ts)
-
-## VS other libraries
+## Field configuration
-Flatfile vs react-spreadsheet-import and Dromo vs react-spreadsheet-import:
+| Property | Description |
+| ------------------- | --------------------------------------------------------------------------------------------- |
+| `key` | Object key produced for this column. |
+| `label` | Human label shown in the match step and grid header. |
+| `required` | Marks the field as required to advance past column matching. The Yup schema enforces values. |
+| `unique` | Cross-row uniqueness check (Yup can't express this). |
+| `alternateMatches` | Strings considered when fuzzy-matching incoming spreadsheet headers. |
+| `example` | Sample value shown in the upload-step preview. |
-| | RSI | Flatfile | Dromo |
-| ------------------------------ | -------------- | ----------- | ----------- |
-| Licence | MIT | Proprietary | Proprietary |
-| Price | Free | Paid | Paid |
-| Support | Github Issues | Enterprise | Enterprise |
-| Self-host | Yes | Paid | Paid |
-| Hosted solution | In development | Yes | No |
-| On-prem deployment | N/A | Yes | Yes |
-| Hooks | Yes | Yes | Yes |
-| Automatic header matching | Yes | Yes | Yes |
-| Data validation | Yes | Yes | Yes |
-| Custom styling | Yes | Yes | Yes |
-| Translations | Yes | Yes | No |
-| Trademarked words `Data Hooks` | No | Yes | No |
+## Validation
-React-spreadsheet-import can be used as a free and open-source alternative to Flatfile and Dromo.
+Pass a Yup `ObjectSchema` whose keys match `fields[].key`. The validator runs per row with `abortEarly: false`, so all field errors surface at once. Cross-row constraints (`unique`) are handled by the importer outside Yup. An optional `rowHook` can mutate values or add extra errors after Yup runs.
-## Contributing
+## Step hooks
-Feel free to open issues if you have any questions or notice bugs. If you want different component behaviour, consider forking the project.
+`uploadStepHook`, `selectHeaderStepHook`, `matchColumnsStepHook` mirror the original API and let host apps observe / transform data between steps (e.g. to drive an external progress indicator).
-## Credits
+## What's different from the original
-Created by Ugnis. [Julita Kriauciunaite](https://github.com/JulitorK) and [Karolis Masiulis](https://github.com/masiulis). You can contact us at `info@ugnis.com`
+- **No Chakra `customTheme` prop.** Theme via Bootstrap CSS variables / SCSS overrides; `.rsi-*` class hooks are exposed in `styles.css`.
+- **No per-field `validations: [...]` array.** Use a Yup schema; use `unique: true` on the field for cross-row checks.
+- **Fewer translation keys.** Trimmed to the surface this lib actually renders.
diff --git a/dist/index.cjs b/dist/index.cjs
new file mode 100644
index 00000000..5390f24b
--- /dev/null
+++ b/dist/index.cjs
@@ -0,0 +1,3521 @@
+"use strict";
+var __create = Object.create;
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __getProtoOf = Object.getPrototypeOf;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __export = (target, all) => {
+ for (var name in all)
+ __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+ if (from && typeof from === "object" || typeof from === "function") {
+ for (let key of __getOwnPropNames(from))
+ if (!__hasOwnProp.call(to, key) && key !== except)
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+ }
+ return to;
+};
+var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
+ // If the importer is in node compatibility mode or this is not an ESM
+ // file that has been converted to a CommonJS file using a Babel-
+ // compatible transform (i.e. "__esModule" has not been set), then set
+ // "default" to the CommonJS "module.exports" for node compatibility.
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
+ mod
+));
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+
+// src/index.ts
+var index_exports = {};
+__export(index_exports, {
+ ReactSpreadsheetImport: () => ReactSpreadsheetImport,
+ autoMatchColumns: () => autoMatchColumns,
+ defaultTranslations: () => defaultTranslations,
+ rowHasErrors: () => rowHasErrors,
+ validateRows: () => validateRows
+});
+module.exports = __toCommonJS(index_exports);
+
+// #style-inject:#style-inject
+function styleInject(css, { insertAt } = {}) {
+ if (!css || typeof document === "undefined") return;
+ const head = document.head || document.getElementsByTagName("head")[0];
+ const style = document.createElement("style");
+ style.type = "text/css";
+ if (insertAt === "top") {
+ if (head.firstChild) {
+ head.insertBefore(style, head.firstChild);
+ } else {
+ head.appendChild(style);
+ }
+ } else {
+ head.appendChild(style);
+ }
+ if (style.styleSheet) {
+ style.styleSheet.cssText = css;
+ } else {
+ style.appendChild(document.createTextNode(css));
+ }
+}
+
+// src/styles.css
+styleInject('@layer rdg {\n @layer Defaults, FocusSink, CheckboxInput, CheckboxIcon, CheckboxLabel, Cell, HeaderCell, SummaryCell, EditCell, Row, HeaderRow, SummaryRow, GroupedRow, Root;\n}\n.rdg-7-0-0-beta-58-fa71d63e {\n @layer rdg.MeasuringCell {\n contain: strict;\n grid-row: 1;\n visibility: hidden;\n }\n}\n.rdg-7-0-0-beta-58-85c48527 {\n @layer rdg.Cell {\n position: relative;\n padding-block: 0;\n padding-inline: 8px;\n border-inline-end: var(--rdg-border-width) solid var(--rdg-border-color);\n border-block-end: var(--rdg-border-width) solid var(--rdg-border-color);\n grid-row-start: var(--rdg-grid-row-start);\n align-content: center;\n background-color: inherit;\n white-space: nowrap;\n overflow: clip;\n text-overflow: ellipsis;\n outline: none;\n &[aria-selected=true] {\n outline: var(--rdg-selection-width) solid var(--rdg-selection-color);\n outline-offset: calc(var(--rdg-selection-width) * -1);\n }\n }\n}\n.rdg-7-0-0-beta-58-17a9a6d4 {\n @layer rdg.Cell {\n position: sticky;\n z-index: 1;\n &:nth-last-child(1 of &) {\n box-shadow: var(--rdg-cell-frozen-box-shadow);\n }\n }\n}\n.rdg-7-0-0-beta-58-bfba19bc {\n @layer rdg.DragHandle {\n --rdg-drag-handle-size: 8px;\n z-index: 0;\n cursor: move;\n inline-size: var(--rdg-drag-handle-size);\n block-size: var(--rdg-drag-handle-size);\n background-color: var(--rdg-selection-color);\n place-self: end;\n &:hover {\n --rdg-drag-handle-size: 16px;\n border: 2px solid var(--rdg-selection-color);\n background-color: var(--rdg-background-color);\n }\n }\n}\n.rdg-7-0-0-beta-58-7abddb3e {\n @layer rdg.DragHandle {\n z-index: 1;\n position: sticky;\n }\n}\n.rdg-7-0-0-beta-58-3b807ead {\n @layer rdg.CheckboxInput {\n display: block;\n margin: auto;\n inline-size: 20px;\n block-size: 20px;\n &:focus-visible {\n outline: 2px solid var(--rdg-checkbox-focus-color);\n outline-offset: -3px;\n }\n &:enabled {\n cursor: pointer;\n }\n }\n}\n.rdg-7-0-0-beta-58-07919382 {\n @layer rdg.GroupCellContent {\n outline: none;\n }\n}\n.rdg-7-0-0-beta-58-02a50147 {\n @layer rdg.GroupCellCaret {\n margin-inline-start: 4px;\n stroke: currentColor;\n stroke-width: 1.5px;\n fill: transparent;\n vertical-align: middle;\n > path {\n transition: d 0.1s;\n }\n }\n}\n.rdg-7-0-0-beta-58-56a248e4 {\n @layer rdg.SortableHeaderCell {\n display: flex;\n }\n}\n.rdg-7-0-0-beta-58-7fad8c83 {\n @layer rdg.SortableHeaderCellName {\n flex-grow: 1;\n overflow: clip;\n text-overflow: ellipsis;\n }\n}\n.rdg-7-0-0-beta-58-35ccb4c8 {\n @layer rdg.Cell {\n background-color: #ccccff;\n }\n}\n.rdg-7-0-0-beta-58-46f9ea88 {\n @layer rdg.EditCell {\n padding: 0;\n }\n}\n.rdg-7-0-0-beta-58-0dbd5994 {\n @layer rdg.HeaderRow {\n display: contents;\n background-color: var(--rdg-header-background-color);\n font-weight: bold;\n & > .rdg-7-0-0-beta-58-85c48527 {\n z-index: 2;\n position: sticky;\n }\n & > .rdg-7-0-0-beta-58-17a9a6d4 {\n z-index: 3;\n }\n }\n}\n.rdg-7-0-0-beta-58-2a7e240d {\n @layer rdg.HeaderCell {\n cursor: pointer;\n }\n}\n.rdg-7-0-0-beta-58-1893dc0f {\n @layer rdg.HeaderCell {\n touch-action: none;\n }\n}\n.rdg-7-0-0-beta-58-4e60db91 {\n @layer rdg.HeaderCell {\n cursor: col-resize;\n position: absolute;\n inset-block-start: 0;\n inset-inline-end: 0;\n inset-block-end: 0;\n inline-size: 10px;\n }\n}\n.rdg-7-0-0-beta-58-3e1a4ad4 {\n @layer rdg.HeaderCell {\n background-color: var(--rdg-header-draggable-background-color);\n }\n}\n.rdg-7-0-0-beta-58-51abd8b8 {\n @layer rdg.HeaderCell {\n background-color: var(--rdg-header-draggable-background-color);\n }\n}\n.rdg-7-0-0-beta-58-c8d7aa64 {\n @layer rdg.HeaderCell {\n border-radius: 4px;\n width: fit-content;\n outline: 2px solid hsl(207, 100%, 50%);\n outline-offset: -2px;\n }\n}\n.rdg-7-0-0-beta-58-3c083f1b {\n @layer rdg.Row {\n display: contents;\n background-color: var(--rdg-background-color);\n &:hover {\n background-color: var(--rdg-row-hover-background-color);\n }\n &[aria-selected=true] {\n background-color: var(--rdg-row-selected-background-color);\n &:hover {\n background-color: var(--rdg-row-selected-hover-background-color);\n }\n }\n }\n}\n.rdg-7-0-0-beta-58-3fe773c3 {\n @layer rdg.FocusSink {\n outline: 2px solid var(--rdg-selection-color);\n outline-offset: -2px;\n }\n}\n.rdg-7-0-0-beta-58-97ce3fde {\n @layer rdg.FocusSink {\n &::before {\n content: "";\n display: inline-block;\n block-size: 100%;\n position: sticky;\n inset-inline-start: 0;\n border-inline-start: 2px solid var(--rdg-selection-color);\n }\n }\n}\n.rdg-7-0-0-beta-58-3d5115f3 {\n @layer rdg.SortIcon {\n fill: currentColor;\n > path {\n transition: d 0.1s;\n }\n }\n}\n.rdg-7-0-0-beta-58-ccd2e5d9 {\n @layer rdg.Defaults {\n *,\n *::before,\n *::after {\n box-sizing: inherit;\n }\n }\n @layer rdg.Root {\n --rdg-selection-width: 2px;\n --rdg-selection-color: hsl(207, 75%, 66%);\n --rdg-font-size: 14px;\n --rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);\n --rdg-border-width: 1px;\n --rdg-summary-border-width: calc(var(--rdg-border-width) * 2);\n --rdg-color: light-dark(#000, #ddd);\n --rdg-border-color: light-dark(#ddd, #444);\n --rdg-summary-border-color: light-dark(#aaa, #555);\n --rdg-background-color: light-dark(hsl(0deg 0% 100%), hsl(0deg 0% 13%));\n --rdg-header-background-color: light-dark(hsl(0deg 0% 97.5%), hsl(0deg 0% 10.5%));\n --rdg-header-draggable-background-color: light-dark(hsl(0deg 0% 90.5%), hsl(0deg 0% 17.5%));\n --rdg-row-hover-background-color: light-dark(hsl(0deg 0% 96%), hsl(0deg 0% 9%));\n --rdg-row-selected-background-color: light-dark(hsl(207deg 76% 92%), hsl(207deg 76% 42%));\n --rdg-row-selected-hover-background-color: light-dark(hsl(207deg 76% 88%), hsl(207deg 76% 38%));\n --rdg-checkbox-focus-color: hsl(207deg 100% 69%);\n &.rdg-dark {\n --rdg-color-scheme: dark;\n }\n &.rdg-light {\n --rdg-color-scheme: light;\n }\n color-scheme: var(--rdg-color-scheme, light dark);\n &:dir(rtl) {\n --rdg-cell-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);\n }\n display: grid;\n accent-color: light-dark(hsl(207deg 100% 29%), hsl(207deg 100% 79%));\n contain: content;\n content-visibility: auto;\n block-size: 350px;\n border: 1px solid var(--rdg-border-color);\n box-sizing: border-box;\n overflow: auto;\n background-color: var(--rdg-background-color);\n color: var(--rdg-color);\n font-size: var(--rdg-font-size);\n &::before {\n content: "";\n grid-column: 1/-1;\n grid-row: 1/-1;\n }\n > :nth-last-child(1 of .rdg-top-summary-row) {\n > .rdg-7-0-0-beta-58-85c48527 {\n border-block-end: var(--rdg-summary-border-width) solid var(--rdg-summary-border-color);\n }\n }\n > :nth-child(1 of .rdg-bottom-summary-row) {\n > .rdg-7-0-0-beta-58-85c48527 {\n border-block-start: var(--rdg-summary-border-width) solid var(--rdg-summary-border-color);\n }\n }\n }\n}\n.rdg-7-0-0-beta-58-e9b0e1c9 {\n @layer rdg.Root {\n user-select: none;\n & .rdg-7-0-0-beta-58-3c083f1b {\n cursor: move;\n }\n }\n}\n.rdg-7-0-0-beta-58-dbb8b3c5 {\n @layer rdg.FocusSink {\n grid-column: 1/-1;\n pointer-events: none;\n z-index: 1;\n }\n}\n.rdg-7-0-0-beta-58-e9f55541 {\n @layer rdg.FocusSink {\n z-index: 3;\n }\n}\n.rdg-7-0-0-beta-58-0b90c82c {\n @layer rdg.SummaryRow {\n > .rdg-7-0-0-beta-58-85c48527 {\n position: sticky;\n }\n }\n}\n.rdg-7-0-0-beta-58-d0520eab {\n @layer rdg.SummaryRow {\n > .rdg-7-0-0-beta-58-85c48527 {\n z-index: 2;\n }\n > .rdg-7-0-0-beta-58-17a9a6d4 {\n z-index: 3;\n }\n }\n}\n.rdg-7-0-0-beta-58-d907aa87 {\n @layer rdg.SummaryCell {\n inset-block-start: var(--rdg-summary-row-top);\n inset-block-end: var(--rdg-summary-row-bottom);\n }\n}\n.rdg-7-0-0-beta-58-e74a2be3 {\n @layer rdg.GroupedRow {\n &:not([aria-selected=true]) {\n background-color: var(--rdg-header-background-color);\n }\n > .rdg-7-0-0-beta-58-85c48527:not(:last-child, .rdg-7-0-0-beta-58-17a9a6d4),\n > :nth-last-child(n+2 of .rdg-7-0-0-beta-58-17a9a6d4) {\n border-inline-end: none;\n }\n }\n}\n.rdg-7-0-0-beta-58-2f8db206 {\n @layer rdg.TextEditor {\n appearance: none;\n box-sizing: border-box;\n inline-size: 100%;\n block-size: 100%;\n padding-block: 0;\n padding-inline: 6px;\n border: 2px solid #ccc;\n vertical-align: top;\n color: var(--rdg-color);\n background-color: var(--rdg-background-color);\n font-family: inherit;\n font-size: var(--rdg-font-size);\n &:focus {\n border-color: var(--rdg-selection-color);\n outline: none;\n }\n &::placeholder {\n color: #999;\n opacity: 1;\n }\n }\n}\n.rsi-modal-content {\n --rsi-error: var(--bs-danger);\n --rsi-warning: var(--bs-warning);\n --rsi-info: var(--bs-info);\n}\n.rsi-dropzone {\n border-style: dashed !important;\n transition: background-color 120ms ease, border-color 120ms ease;\n}\n.rsi-grid-wrapper .rdg {\n block-size: 100%;\n border: 1px solid var(--bs-border-color);\n border-radius: var(--bs-border-radius);\n --rdg-border-color: var(--bs-border-color-translucent);\n --rdg-color: var(--bs-body-color);\n --rdg-background-color: var(--bs-body-bg);\n --rdg-header-background-color: var(--bs-tertiary-bg);\n --rdg-row-hover-background-color: var(--bs-secondary-bg);\n --rdg-row-selected-background-color: var(--bs-primary-bg-subtle);\n --rdg-row-selected-hover-background-color: var(--bs-primary-bg-subtle);\n --rdg-selection-color: var(--bs-primary);\n font-family: inherit;\n font-size: 0.875rem;\n}\n.rsi-cell-error {\n background-color: var(--bs-danger-bg-subtle);\n color: var(--bs-danger-text-emphasis);\n padding: 0 4px;\n border-radius: 2px;\n cursor: default;\n}\n.rsi-tooltip-fixed {\n pointer-events: none;\n}\n.rsi-tooltip-inner {\n background-color: #0a2540;\n color: #ffffff;\n font-size: 0.8125rem;\n max-width: 320px;\n text-align: left;\n padding: 6px 10px;\n border-radius: 4px;\n}\n.rsi-tooltip-arrow {\n width: 0;\n height: 0;\n border-left: 6px solid transparent;\n border-right: 6px solid transparent;\n border-top: 6px solid #0a2540;\n margin: 0 auto;\n}\n.rsi-cell-warning {\n background-color: var(--bs-warning-bg-subtle);\n color: var(--bs-warning-text-emphasis);\n padding: 0 4px;\n border-radius: 2px;\n}\n.rsi-cell-info {\n background-color: var(--bs-info-bg-subtle);\n color: var(--bs-info-text-emphasis);\n padding: 0 4px;\n border-radius: 2px;\n}\n.rsi-cell-edit {\n height: 100%;\n border-radius: 0;\n}\n.rsi-stepper-row {\n gap: 1px;\n background-color: transparent;\n}\n.rsi-stepper-item {\n min-width: 0;\n padding-right: 1rem;\n}\n.rsi-stepper-item:last-child {\n padding-right: 0;\n}\n.rsi-stepper-bar {\n height: 4px;\n background-color: var(--bs-border-color);\n border-radius: 2px;\n margin-bottom: 0.5rem;\n}\n.rsi-stepper-done .rsi-stepper-bar,\n.rsi-stepper-active .rsi-stepper-bar {\n background-color: var(--bs-primary);\n}\n.rsi-stepper-label {\n color: var(--bs-secondary-color);\n font-weight: 500;\n}\n.rsi-stepper-active .rsi-stepper-label {\n color: var(--bs-body-color);\n font-weight: 600;\n}\n.rsi-stepper-num {\n color: inherit;\n}\n.rsi-stepper-active .rsi-stepper-num {\n color: #1b9aa9;\n}\n.rsi-inline .btn-primary,\n.rsi-modal-content .btn-primary {\n color: #1b9aa9;\n background-color: #ffffff;\n border-color: #dfeff3;\n}\n.rsi-inline .btn-primary:hover,\n.rsi-modal-content .btn-primary:hover,\n.rsi-inline .btn-primary:focus,\n.rsi-modal-content .btn-primary:focus {\n color: #2696a6;\n background-color: #f2fafb;\n border-color: #d1ebee;\n}\n.rsi-inline .btn-primary:active,\n.rsi-modal-content .btn-primary:active,\n.rsi-inline .btn-primary:disabled,\n.rsi-modal-content .btn-primary:disabled {\n color: #1b9aa9;\n background-color: #f2fafb;\n border-color: #d1ebee;\n}\n.rsi-inline .btn-primary:focus-visible,\n.rsi-modal-content .btn-primary:focus-visible {\n box-shadow: 0 0 0 0.25rem rgba(27, 154, 169, 0.25);\n}\n.rsi-inline .btn-outline-secondary,\n.rsi-modal-content .btn-outline-secondary {\n color: #858c9c;\n background-color: #ffffff;\n border-color: #e7e7ec;\n}\n.rsi-inline .btn-outline-secondary:hover,\n.rsi-modal-content .btn-outline-secondary:hover,\n.rsi-inline .btn-outline-secondary:focus,\n.rsi-modal-content .btn-outline-secondary:focus {\n color: #0a2540;\n background-color: #f8f8f8;\n border-color: #cfcfd7;\n}\n.rsi-inline .btn-outline-secondary:active,\n.rsi-modal-content .btn-outline-secondary:active,\n.rsi-inline .btn-outline-secondary:disabled,\n.rsi-modal-content .btn-outline-secondary:disabled {\n color: #858c9c;\n background-color: #f8f8f8;\n border-color: #cfcfd7;\n}\n.rsi-inline .btn-outline-secondary:focus-visible,\n.rsi-modal-content .btn-outline-secondary:focus-visible {\n box-shadow: 0 0 0 0.25rem rgba(133, 140, 156, 0.25);\n}\n.rsi-match-grid > * {\n border-bottom: 1px solid var(--bs-border-color);\n}\n.rsi-match-grid > *:last-child,\n.rsi-match-grid .rsi-match-col-header:last-child {\n border-right: 0;\n}\n.rsi-match-section-label {\n position: sticky;\n left: 0;\n padding: 0.75rem 1rem;\n font-weight: 600;\n background-color: var(--bs-tertiary-bg);\n color: var(--bs-body-color);\n width: max-content;\n min-width: 100%;\n border-bottom: 1px solid var(--bs-border-color);\n}\n.rsi-match-section-divider {\n border-top: 1px solid var(--bs-border-color);\n}\n.rsi-status-dot {\n display: inline-block;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n border: 2px solid var(--bs-border-color);\n flex-shrink: 0;\n}\n.rsi-status-dot.rsi-status-matched {\n background-color: var(--bs-success);\n border-color: var(--bs-success);\n}\n.rsi-status-dot.rsi-status-matched-required {\n background-color: var(--bs-success);\n border-color: var(--bs-success);\n}\n.rsi-status-dot.rsi-status-ignored {\n background-color: transparent;\n border-color: var(--bs-secondary-border-subtle);\n}\n.rsi-ignore-btn {\n border: 1px solid var(--bs-border-color);\n background-color: var(--bs-secondary-bg);\n color: var(--bs-secondary-color);\n font-size: 14px;\n}\n.rsi-ignore-btn:hover {\n background-color: var(--bs-tertiary-bg);\n}\n');
+
+// src/ReactSpreadsheetImport.tsx
+var import_react7 = require("react");
+var import_react_bootstrap6 = require("react-bootstrap");
+
+// src/components/Stepper.tsx
+var import_jsx_runtime = require("react/jsx-runtime");
+var ORDER = ["upload", "selectSheet", "selectHeader", "matchColumns", "validate"];
+var POSITIONS = [
+ { stepNames: ["upload", "selectSheet"], key: "upload" },
+ { stepNames: ["selectHeader"], key: "selectHeader" },
+ { stepNames: ["matchColumns"], key: "matchColumns" },
+ { stepNames: ["validate"], key: "submit" }
+];
+function Stepper({ current, translations }) {
+ const currentOrder = ORDER.indexOf(current);
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("nav", { "aria-label": "Import progress", className: "rsi-stepper", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rsi-stepper-row d-flex", children: POSITIONS.map((pos, idx) => {
+ const positionMaxOrder = Math.max(...pos.stepNames.map((s) => ORDER.indexOf(s)));
+ const positionMinOrder = Math.min(...pos.stepNames.map((s) => ORDER.indexOf(s)));
+ const status = currentOrder > positionMaxOrder ? "done" : currentOrder >= positionMinOrder ? "active" : "todo";
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `rsi-stepper-item flex-fill rsi-stepper-${status}`, children: [
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "rsi-stepper-bar", "aria-hidden": "true" }),
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rsi-stepper-label small", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "rsi-stepper-num", children: [
+ idx + 1,
+ "."
+ ] }),
+ " ",
+ translations[pos.key]
+ ] })
+ ] }, pos.key);
+ }) }) });
+}
+
+// src/steps/MatchColumnsStep.tsx
+var import_react = require("react");
+var import_react_bootstrap = require("react-bootstrap");
+
+// src/utils/autoMatch.ts
+var import_fuse = __toESM(require("fuse.js"), 1);
+var NORMALIZE = /[\s_\-./]+/g;
+function normalize(s) {
+ return s.toLowerCase().replace(NORMALIZE, "").trim();
+}
+function autoMatchColumns(headers, fields, distance = 0.25) {
+ const entries = [];
+ for (const f of fields) {
+ const candidates = /* @__PURE__ */ new Set([f.key, f.label, ...f.alternateMatches ?? []]);
+ for (const c of candidates) {
+ entries.push({ fieldKey: f.key, candidate: normalize(c) });
+ }
+ }
+ const fuse = new import_fuse.default(entries, {
+ keys: ["candidate"],
+ threshold: distance,
+ ignoreLocation: true,
+ isCaseSensitive: false
+ });
+ const used = /* @__PURE__ */ new Set();
+ const result = headers.map(() => void 0);
+ const all = [];
+ headers.forEach((h, idx) => {
+ if (!h || !h.trim()) return;
+ const found = fuse.search(normalize(h));
+ const seen = /* @__PURE__ */ new Map();
+ for (const r of found) {
+ const score = r.score ?? 1;
+ const key = r.item.fieldKey;
+ const prev = seen.get(key);
+ if (prev === void 0 || score < prev) seen.set(key, score);
+ }
+ for (const [fieldKey, score] of seen) {
+ all.push({ headerIdx: idx, fieldKey, score });
+ }
+ });
+ all.sort((a, b) => a.score - b.score);
+ const headerAssigned = /* @__PURE__ */ new Set();
+ for (const m of all) {
+ if (headerAssigned.has(m.headerIdx)) continue;
+ if (used.has(m.fieldKey)) continue;
+ result[m.headerIdx] = m.fieldKey;
+ headerAssigned.add(m.headerIdx);
+ used.add(m.fieldKey);
+ }
+ return result;
+}
+
+// src/steps/MatchColumnsStep.tsx
+var import_jsx_runtime2 = require("react/jsx-runtime");
+var IGNORE = "__ignore__";
+var SAMPLE_ROWS = 3;
+var COLUMN_MIN_WIDTH = 180;
+function MatchColumnsStep({
+ fields,
+ headers,
+ rows,
+ autoMapDistance,
+ translations,
+ alertTranslations,
+ onBack,
+ onNext,
+ showTitle = true
+}) {
+ const initial = (0, import_react.useMemo)(
+ () => autoMatchColumns(headers, fields, autoMapDistance),
+ [headers, fields, autoMapDistance]
+ );
+ const [mapping, setMapping] = (0, import_react.useState)(initial);
+ const [showWarn, setShowWarn] = (0, import_react.useState)(false);
+ (0, import_react.useEffect)(() => setMapping(initial), [initial]);
+ const sample = rows.slice(0, SAMPLE_ROWS);
+ function setColumn(idx, value) {
+ setMapping((prev) => {
+ const next = [...prev];
+ const newVal = value === IGNORE || value === "" ? void 0 : value;
+ if (newVal) {
+ for (let i = 0; i < next.length; i++) {
+ if (i !== idx && next[i] === newVal) next[i] = void 0;
+ }
+ }
+ next[idx] = newVal;
+ return next;
+ });
+ }
+ const matched = new Set(mapping.filter((v) => Boolean(v)));
+ const requiredKeys = fields.filter((f) => f.required).map((f) => f.key);
+ const unmatchedRequired = requiredKeys.filter((k) => !matched.has(k));
+ function handleNext() {
+ if (unmatchedRequired.length > 0) {
+ setShowWarn(true);
+ return;
+ }
+ onNext(mapping);
+ }
+ const gridCols = `repeat(${headers.length}, minmax(${COLUMN_MIN_WIDTH}px, 1fr))`;
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "d-flex flex-column gap-3", children: [
+ showTitle && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h5", { className: "m-0", children: translations.title }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rsi-match-card border rounded overflow-auto", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "rsi-match-grid", style: { display: "grid", gridTemplateColumns: gridCols }, children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rsi-match-section-label", style: { gridColumn: "1 / -1" }, children: translations.userTableTitle }),
+ headers.map((h, idx) => {
+ const isIgnored = mapping[idx] === void 0;
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
+ "div",
+ {
+ className: `rsi-match-col-header p-3 border-end ${isIgnored ? "opacity-50" : ""}`,
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "d-flex justify-content-between align-items-start gap-2", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "text-truncate", title: h, children: h || `(column ${idx + 1})` }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
+ import_react_bootstrap.Button,
+ {
+ variant: "light",
+ size: "sm",
+ className: "rsi-ignore-btn p-0 d-inline-flex align-items-center justify-content-center",
+ style: { width: 22, height: 22, lineHeight: 1 },
+ title: translations.ignoredColumnText,
+ onClick: () => setColumn(idx, IGNORE),
+ disabled: isIgnored,
+ children: "\xD7"
+ }
+ )
+ ] })
+ },
+ `h-${idx}`
+ );
+ }),
+ sample.map(
+ (row2, ri) => headers.map((_, ci) => {
+ const isIgnored = mapping[ci] === void 0;
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
+ "div",
+ {
+ className: `rsi-match-col-sample px-3 py-2 border-end small text-muted text-truncate ${isIgnored ? "opacity-50" : ""}`,
+ style: { opacity: isIgnored ? 0.4 : 1 - ri * 0.25 },
+ title: row2[ci] ?? "",
+ children: row2[ci] ?? ""
+ },
+ `s-${ri}-${ci}`
+ );
+ })
+ ),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "rsi-match-section-label rsi-match-section-divider", style: { gridColumn: "1 / -1" }, children: translations.templateTitle }),
+ headers.map((h, idx) => {
+ const value = mapping[idx];
+ const status = statusFor(value, fields);
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "p-3 border-end d-flex align-items-center gap-2", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
+ import_react_bootstrap.Form.Select,
+ {
+ size: "sm",
+ value: value ?? IGNORE,
+ onChange: (e) => setColumn(idx, e.target.value),
+ "aria-label": `${translations.matchDropdownTitle}: ${h}`,
+ children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: IGNORE, children: translations.ignoredColumnText }),
+ fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
+ "option",
+ {
+ value: f.key,
+ disabled: matched.has(f.key) && value !== f.key,
+ children: [
+ f.label,
+ f.required ? " *" : ""
+ ]
+ },
+ f.key
+ ))
+ ]
+ }
+ ),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
+ "span",
+ {
+ className: `rsi-status-dot ${status.className}`,
+ title: status.title,
+ "aria-label": status.title
+ }
+ )
+ ] }, `m-${idx}`);
+ })
+ ] }) }),
+ unmatchedRequired.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_bootstrap.Alert, { variant: "warning", className: "m-0", children: [
+ translations.unmatched,
+ ":",
+ " ",
+ unmatchedRequired.map((k) => fields.find((f) => f.key === k)?.label ?? k).join(", ")
+ ] }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "d-flex justify-content-between", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_bootstrap.Button, { variant: "outline-secondary", onClick: onBack, children: translations.backButtonTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_bootstrap.Button, { variant: "primary", onClick: handleNext, children: translations.nextButtonTitle })
+ ] }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_bootstrap.Modal, { show: showWarn, onHide: () => setShowWarn(false), centered: true, children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_bootstrap.Modal.Header, { closeButton: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_bootstrap.Modal.Title, { children: alertTranslations.headerTitle }) }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_bootstrap.Modal.Body, { children: alertTranslations.bodyText }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_bootstrap.Modal.Footer, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_bootstrap.Button, { variant: "outline-secondary", onClick: () => setShowWarn(false), children: alertTranslations.cancelButtonTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
+ import_react_bootstrap.Button,
+ {
+ variant: "primary",
+ onClick: () => {
+ setShowWarn(false);
+ onNext(mapping);
+ },
+ children: alertTranslations.continueButtonTitle
+ }
+ )
+ ] })
+ ] })
+ ] });
+}
+function statusFor(key, fields) {
+ if (!key) return { className: "rsi-status-ignored", title: "Ignored" };
+ const f = fields.find((x) => x.key === key);
+ if (f?.required) return { className: "rsi-status-matched-required", title: `Matched: ${f.label}` };
+ return { className: "rsi-status-matched", title: `Matched${f ? `: ${f.label}` : ""}` };
+}
+
+// src/steps/SelectHeaderStep.tsx
+var import_react2 = require("react");
+var import_react_bootstrap2 = require("react-bootstrap");
+var import_jsx_runtime3 = require("react/jsx-runtime");
+function SelectHeaderStep({ rows, translations, onBack, onNext, showTitle = true }) {
+ const [selected, setSelected] = (0, import_react2.useState)(0);
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "d-flex flex-column gap-3", children: [
+ showTitle && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h5", { className: "m-0", children: translations.title }),
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border rounded overflow-auto", style: { maxHeight: 320 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_bootstrap2.Table, { hover: true, size: "sm", className: "m-0 align-middle", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: rows.slice(0, 25).map((row2, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
+ "tr",
+ {
+ onClick: () => setSelected(idx),
+ className: selected === idx ? "table-primary" : void 0,
+ style: { cursor: "pointer" },
+ children: [
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { style: { width: 36 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
+ "input",
+ {
+ type: "radio",
+ name: "rsi-header",
+ "aria-label": `Use row ${idx + 1} as header`,
+ checked: selected === idx,
+ onChange: () => setSelected(idx),
+ onClick: (e) => e.stopPropagation()
+ }
+ ) }),
+ row2.map((cell2, ci) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { className: "small text-nowrap", children: cell2 }, ci))
+ ]
+ },
+ idx
+ )) }) }) }),
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "d-flex justify-content-between", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_bootstrap2.Button, { variant: "outline-secondary", onClick: onBack, children: translations.backButtonTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_bootstrap2.Button, { variant: "primary", onClick: () => onNext(selected), children: translations.nextButtonTitle })
+ ] })
+ ] });
+}
+
+// src/steps/SelectSheetStep.tsx
+var import_react3 = require("react");
+var import_react_bootstrap3 = require("react-bootstrap");
+var import_jsx_runtime4 = require("react/jsx-runtime");
+function SelectSheetStep({ workbook, translations, onBack, onNext, showTitle = true }) {
+ const [selected, setSelected] = (0, import_react3.useState)(0);
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "d-flex flex-column gap-3", children: [
+ showTitle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h5", { className: "m-0", children: translations.title }),
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_bootstrap3.Form, { children: workbook.sheets.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
+ import_react_bootstrap3.Form.Check,
+ {
+ type: "radio",
+ id: `rsi-sheet-${i}`,
+ name: "rsi-sheet",
+ label: `${s.name} (${s.rows.length} rows)`,
+ checked: selected === i,
+ onChange: () => setSelected(i)
+ },
+ s.name + i
+ )) }),
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "d-flex justify-content-between mt-2", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_bootstrap3.Button, { variant: "outline-secondary", onClick: onBack, children: translations.backButtonTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_bootstrap3.Button, { variant: "primary", onClick: () => onNext(selected), children: translations.nextButtonTitle })
+ ] })
+ ] });
+}
+
+// src/steps/UploadStep.tsx
+var import_react4 = require("react");
+var import_react_dropzone = require("react-dropzone");
+var import_react_bootstrap4 = require("react-bootstrap");
+
+// src/utils/parseFile.ts
+var import_exceljs = __toESM(require("exceljs"), 1);
+var CSV_TYPES = ["text/csv", "application/csv"];
+var CSV_EXT = /\.csv$/i;
+function cellToString(value) {
+ if (value === null || value === void 0) return "";
+ if (value instanceof Date) return value.toISOString().slice(0, 10);
+ if (typeof value === "object") {
+ const v = value;
+ if (typeof v.text === "string") return v.text;
+ if (Array.isArray(v.richText)) return v.richText.map((r) => r.text).join("");
+ if (v.result !== void 0) return cellToString(v.result);
+ return "";
+ }
+ return String(value);
+}
+function worksheetToRows(ws) {
+ const rows = [];
+ const lastCol = ws.actualColumnCount || ws.columnCount || 0;
+ ws.eachRow({ includeEmpty: true }, (row2) => {
+ const out = [];
+ for (let i = 1; i <= lastCol; i++) {
+ out.push(cellToString(row2.getCell(i).value));
+ }
+ rows.push(out);
+ });
+ while (rows.length && rows[rows.length - 1].every((c) => c === "")) {
+ rows.pop();
+ }
+ return rows;
+}
+async function parseFile(file) {
+ const isCsv = CSV_TYPES.includes(file.type) || CSV_EXT.test(file.name);
+ const buffer = await file.arrayBuffer();
+ if (isCsv) {
+ const text = new TextDecoder("utf-8").decode(buffer);
+ const rows = splitCsv(text);
+ while (rows.length && rows[rows.length - 1].every((c) => c === "")) {
+ rows.pop();
+ }
+ return { file, sheets: [{ name: "Sheet1", rows }] };
+ }
+ const wb = new import_exceljs.default.Workbook();
+ await wb.xlsx.load(buffer);
+ const sheets = wb.worksheets.map((ws) => ({
+ name: ws.name,
+ rows: worksheetToRows(ws)
+ }));
+ return { file, sheets };
+}
+function splitCsv(input) {
+ const out = [];
+ let row2 = [];
+ let cell2 = "";
+ let inQuotes = false;
+ for (let i = 0; i < input.length; i++) {
+ const c = input[i];
+ if (inQuotes) {
+ if (c === '"') {
+ if (input[i + 1] === '"') {
+ cell2 += '"';
+ i++;
+ } else {
+ inQuotes = false;
+ }
+ } else {
+ cell2 += c;
+ }
+ continue;
+ }
+ if (c === '"') {
+ inQuotes = true;
+ continue;
+ }
+ if (c === ",") {
+ row2.push(cell2);
+ cell2 = "";
+ continue;
+ }
+ if (c === "\n" || c === "\r") {
+ if (c === "\r" && input[i + 1] === "\n") i++;
+ row2.push(cell2);
+ out.push(row2);
+ row2 = [];
+ cell2 = "";
+ continue;
+ }
+ cell2 += c;
+ }
+ if (cell2.length > 0 || row2.length > 0) {
+ row2.push(cell2);
+ out.push(row2);
+ }
+ return out;
+}
+
+// src/steps/UploadStep.tsx
+var import_jsx_runtime5 = require("react/jsx-runtime");
+var ACCEPT = {
+ "text/csv": [".csv"],
+ "application/vnd.ms-excel": [".xls"],
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"]
+};
+function UploadStep({ fields, maxFileSize, translations, onLoaded, uploadStepHook, showTitle = true }) {
+ const [loading, setLoading] = (0, import_react4.useState)(false);
+ const [error, setError] = (0, import_react4.useState)(null);
+ const onDrop = (0, import_react4.useCallback)(
+ async (accepted) => {
+ const file = accepted[0];
+ if (!file) return;
+ setError(null);
+ setLoading(true);
+ try {
+ const wb = await parseFile(file);
+ if (uploadStepHook && wb.sheets.length === 1 && wb.sheets[0]) {
+ const transformed = await uploadStepHook(wb.sheets[0].rows);
+ wb.sheets[0].rows = transformed;
+ }
+ onLoaded(wb);
+ } catch (err) {
+ setError(err.message || translations.dropzone.errorToastDescription);
+ } finally {
+ setLoading(false);
+ }
+ },
+ [onLoaded, uploadStepHook, translations.dropzone.errorToastDescription]
+ );
+ const { getRootProps, getInputProps, isDragActive, open } = (0, import_react_dropzone.useDropzone)({
+ onDrop,
+ accept: ACCEPT,
+ maxSize: maxFileSize,
+ multiple: false,
+ noClick: true,
+ noKeyboard: true
+ });
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "rsi-upload-step d-flex flex-column gap-3", children: [
+ showTitle && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h5", { className: "m-0", children: translations.title }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-secondary small mb-1", children: translations.manifestTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-secondary small mb-2", children: translations.manifestDescription }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "border rounded overflow-auto", style: { maxHeight: 160 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_bootstrap4.Table, { size: "sm", className: "m-0", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("th", { className: "text-nowrap small", children: [
+ f.label,
+ f.required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-danger ms-1", children: "*" })
+ ] }, f.key)) }) }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tbody", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: fields.map((f) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: "text-nowrap small text-muted", children: f.example ?? "" }, f.key)) }) })
+ ] }) })
+ ] }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
+ "div",
+ {
+ ...getRootProps(),
+ className: `rsi-dropzone d-flex flex-column align-items-center justify-content-center text-center p-5 border border-2 border-dashed rounded ${isDragActive ? "bg-primary-subtle border-primary" : "bg-body-tertiary"}`,
+ style: { minHeight: 180, cursor: "pointer" },
+ onClick: open,
+ children: [
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("input", { ...getInputProps() }),
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_bootstrap4.Spinner, { animation: "border", className: "mb-2" }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: translations.dropzone.loadingTitle })
+ ] }) : isDragActive ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { children: translations.dropzone.activeDropzoneTitle }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mb-2", children: translations.dropzone.title }),
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
+ import_react_bootstrap4.Button,
+ {
+ type: "button",
+ variant: "primary",
+ onClick: (e) => {
+ e.stopPropagation();
+ open();
+ },
+ children: translations.dropzone.buttonTitle
+ }
+ )
+ ] })
+ ]
+ }
+ ),
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_bootstrap4.Alert, { variant: "danger", className: "m-0", children: error })
+ ] });
+}
+
+// src/steps/ValidationStep.tsx
+var import_react6 = require("react");
+var import_react_dom2 = require("react-dom");
+var import_react_bootstrap5 = require("react-bootstrap");
+
+// node_modules/react-data-grid/lib/index.js
+var import_react5 = require("react");
+var import_react_dom = require("react-dom");
+var import_jsx_runtime6 = require("react/jsx-runtime");
+function getColSpan(column, lastFrozenColumnIndex, args) {
+ const colSpan = typeof column.colSpan === "function" ? column.colSpan(args) : 1;
+ if (Number.isInteger(colSpan) && colSpan > 1 && (!column.frozen || column.idx + colSpan - 1 <= lastFrozenColumnIndex)) return colSpan;
+}
+function stopPropagation(event) {
+ event.stopPropagation();
+}
+function scrollIntoView(element, behavior = "instant") {
+ element?.scrollIntoView({
+ inline: "nearest",
+ block: "nearest",
+ behavior
+ });
+}
+function createCellEvent(event) {
+ let defaultPrevented = false;
+ const cellEvent = {
+ ...event,
+ preventGridDefault() {
+ defaultPrevented = true;
+ },
+ isGridDefaultPrevented() {
+ return defaultPrevented;
+ }
+ };
+ Object.setPrototypeOf(cellEvent, Object.getPrototypeOf(event));
+ return cellEvent;
+}
+var nonInputKeys = /* @__PURE__ */ new Set([
+ "Unidentified",
+ "Alt",
+ "AltGraph",
+ "CapsLock",
+ "Control",
+ "Fn",
+ "FnLock",
+ "Meta",
+ "NumLock",
+ "ScrollLock",
+ "Shift",
+ "Tab",
+ "ArrowDown",
+ "ArrowLeft",
+ "ArrowRight",
+ "ArrowUp",
+ "End",
+ "Home",
+ "PageDown",
+ "PageUp",
+ "Insert",
+ "ContextMenu",
+ "Escape",
+ "Pause",
+ "Play",
+ "PrintScreen",
+ "F1",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12"
+]);
+function isCtrlKeyHeldDown(e) {
+ return (e.ctrlKey || e.metaKey) && e.key !== "Control";
+}
+var vKey = 86;
+function isDefaultCellInput(event, isUserHandlingPaste) {
+ if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isUserHandlingPaste)) return false;
+ return !nonInputKeys.has(event.key);
+}
+function onEditorNavigation({ key, target }) {
+ if (key === "Tab" && (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) return target.closest(".rdg-editor-container")?.querySelectorAll("input, textarea, select").length === 1;
+ return false;
+}
+function getLeftRightKey(direction) {
+ const isRtl = direction === "rtl";
+ return {
+ leftKey: isRtl ? "ArrowRight" : "ArrowLeft",
+ rightKey: isRtl ? "ArrowLeft" : "ArrowRight"
+ };
+}
+var measuringCellClassname = "rdg-7-0-0-beta-58-fa71d63e";
+function renderMeasuringCells(viewportColumns) {
+ return viewportColumns.map(({ key, idx, minWidth, maxWidth }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ className: measuringCellClassname,
+ style: {
+ gridColumnStart: idx + 1,
+ minWidth,
+ maxWidth
+ },
+ "data-measuring-cell-key": key
+ }, key));
+}
+function isSelectedCellEditable({ selectedPosition, columns, rows }) {
+ const column = columns[selectedPosition.idx];
+ const row$1 = rows[selectedPosition.rowIdx];
+ return isCellEditableUtil(column, row$1);
+}
+function isCellEditableUtil(column, row$1) {
+ return column.renderEditCell != null && (typeof column.editable === "function" ? column.editable(row$1) : column.editable) !== false;
+}
+function getSelectedCellColSpan({ rows, topSummaryRows, bottomSummaryRows, rowIdx, mainHeaderRowIdx, lastFrozenColumnIndex, column }) {
+ const topSummaryRowsCount = topSummaryRows?.length ?? 0;
+ if (rowIdx === mainHeaderRowIdx) return getColSpan(column, lastFrozenColumnIndex, { type: "HEADER" });
+ if (topSummaryRows && rowIdx > mainHeaderRowIdx && rowIdx <= topSummaryRowsCount + mainHeaderRowIdx) return getColSpan(column, lastFrozenColumnIndex, {
+ type: "SUMMARY",
+ row: topSummaryRows[rowIdx + topSummaryRowsCount]
+ });
+ if (rowIdx >= 0 && rowIdx < rows.length) {
+ const row$1 = rows[rowIdx];
+ return getColSpan(column, lastFrozenColumnIndex, {
+ type: "ROW",
+ row: row$1
+ });
+ }
+ if (bottomSummaryRows) return getColSpan(column, lastFrozenColumnIndex, {
+ type: "SUMMARY",
+ row: bottomSummaryRows[rowIdx - rows.length]
+ });
+}
+function getNextSelectedCellPosition({ moveUp, moveNext, cellNavigationMode, columns, colSpanColumns, rows, topSummaryRows, bottomSummaryRows, minRowIdx, mainHeaderRowIdx, maxRowIdx, currentPosition: { idx: currentIdx, rowIdx: currentRowIdx }, nextPosition, lastFrozenColumnIndex, isCellWithinBounds }) {
+ let { idx: nextIdx, rowIdx: nextRowIdx } = nextPosition;
+ const columnsCount = columns.length;
+ const setColSpan = (moveNext$1) => {
+ for (const column of colSpanColumns) {
+ const colIdx = column.idx;
+ if (colIdx > nextIdx) break;
+ const colSpan = getSelectedCellColSpan({
+ rows,
+ topSummaryRows,
+ bottomSummaryRows,
+ rowIdx: nextRowIdx,
+ mainHeaderRowIdx,
+ lastFrozenColumnIndex,
+ column
+ });
+ if (colSpan && nextIdx > colIdx && nextIdx < colSpan + colIdx) {
+ nextIdx = colIdx + (moveNext$1 ? colSpan : 0);
+ break;
+ }
+ }
+ };
+ const getParentRowIdx = (parent) => {
+ return parent.level + mainHeaderRowIdx;
+ };
+ const setHeaderGroupColAndRowSpan = () => {
+ if (moveNext) {
+ let parent = columns[nextIdx].parent;
+ while (parent !== void 0) {
+ const parentRowIdx = getParentRowIdx(parent);
+ if (nextRowIdx === parentRowIdx) {
+ nextIdx = parent.idx + parent.colSpan;
+ break;
+ }
+ parent = parent.parent;
+ }
+ } else if (moveUp) {
+ let parent = columns[nextIdx].parent;
+ let found = false;
+ while (parent !== void 0) {
+ const parentRowIdx = getParentRowIdx(parent);
+ if (nextRowIdx >= parentRowIdx) {
+ nextIdx = parent.idx;
+ nextRowIdx = parentRowIdx;
+ found = true;
+ break;
+ }
+ parent = parent.parent;
+ }
+ if (!found) {
+ nextIdx = currentIdx;
+ nextRowIdx = currentRowIdx;
+ }
+ }
+ };
+ if (isCellWithinBounds(nextPosition)) {
+ setColSpan(moveNext);
+ if (nextRowIdx < mainHeaderRowIdx) setHeaderGroupColAndRowSpan();
+ }
+ if (cellNavigationMode === "CHANGE_ROW") {
+ const isAfterLastColumn = nextIdx === columnsCount;
+ const isBeforeFirstColumn = nextIdx === -1;
+ if (isAfterLastColumn) {
+ if (!(nextRowIdx === maxRowIdx)) {
+ nextIdx = 0;
+ nextRowIdx += 1;
+ }
+ } else if (isBeforeFirstColumn) {
+ if (!(nextRowIdx === minRowIdx)) {
+ nextRowIdx -= 1;
+ nextIdx = columnsCount - 1;
+ }
+ setColSpan(false);
+ }
+ }
+ if (nextRowIdx < mainHeaderRowIdx && nextIdx > -1 && nextIdx < columnsCount) {
+ let parent = columns[nextIdx].parent;
+ const nextParentRowIdx = nextRowIdx;
+ nextRowIdx = mainHeaderRowIdx;
+ while (parent !== void 0) {
+ const parentRowIdx = getParentRowIdx(parent);
+ if (parentRowIdx >= nextParentRowIdx) {
+ nextRowIdx = parentRowIdx;
+ nextIdx = parent.idx;
+ }
+ parent = parent.parent;
+ }
+ }
+ return {
+ idx: nextIdx,
+ rowIdx: nextRowIdx
+ };
+}
+function canExitGrid({ maxColIdx, minRowIdx, maxRowIdx, selectedPosition: { rowIdx, idx }, shiftKey }) {
+ return shiftKey ? idx === 0 && rowIdx === minRowIdx : idx === maxColIdx && rowIdx === maxRowIdx;
+}
+var cell = "rdg-7-0-0-beta-58-85c48527";
+var cellClassname = `rdg-cell ${cell}`;
+var cellFrozen = "rdg-7-0-0-beta-58-17a9a6d4";
+var cellFrozenClassname = `rdg-cell-frozen ${cellFrozen}`;
+var cellDragHandle = "rdg-7-0-0-beta-58-bfba19bc";
+var cellDragHandleFrozenClassname = "rdg-7-0-0-beta-58-7abddb3e";
+var cellDragHandleClassname = `rdg-cell-drag-handle ${cellDragHandle}`;
+function getRowStyle(rowIdx) {
+ return { "--rdg-grid-row-start": rowIdx };
+}
+function getHeaderCellStyle(column, rowIdx, rowSpan) {
+ const gridRowEnd = rowIdx + 1;
+ const paddingBlockStart = `calc(${rowSpan - 1} * var(--rdg-header-row-height))`;
+ if (column.parent === void 0) return {
+ insetBlockStart: 0,
+ gridRowStart: 1,
+ gridRowEnd,
+ paddingBlockStart
+ };
+ return {
+ insetBlockStart: `calc(${rowIdx - rowSpan} * var(--rdg-header-row-height))`,
+ gridRowStart: gridRowEnd - rowSpan,
+ gridRowEnd,
+ paddingBlockStart
+ };
+}
+function getCellStyle(column, colSpan = 1) {
+ const index = column.idx + 1;
+ return {
+ gridColumnStart: index,
+ gridColumnEnd: index + colSpan,
+ insetInlineStart: column.frozen ? `var(--rdg-frozen-left-${column.idx})` : void 0
+ };
+}
+function classnames(...args) {
+ let classname = "";
+ for (const arg of args) if (arg) {
+ if (typeof arg === "string") classname += ` ${arg}`;
+ else if (typeof arg === "object") {
+ for (const key in arg) if (arg[key]) classname += ` ${key}`;
+ }
+ }
+ return classname.trimStart();
+}
+function getCellClassname(column, ...extraClasses) {
+ return classnames(cellClassname, { [cellFrozenClassname]: column.frozen }, ...extraClasses);
+}
+var { min, max, floor, sign, abs } = Math;
+function assertIsValidKeyGetter(keyGetter) {
+ if (typeof keyGetter !== "function") throw new Error("Please specify the rowKeyGetter prop to use selection");
+}
+function clampColumnWidth(width, { minWidth, maxWidth }) {
+ width = max(width, minWidth);
+ if (typeof maxWidth === "number" && maxWidth >= minWidth) return min(width, maxWidth);
+ return width;
+}
+function getHeaderCellRowSpan(column, rowIdx) {
+ return column.parent === void 0 ? rowIdx : column.level - column.parent.level;
+}
+var checkboxClassname = `rdg-checkbox-input rdg-7-0-0-beta-58-3b807ead`;
+function renderCheckbox({ onChange, indeterminate, ...props }) {
+ function handleChange(e) {
+ onChange(e.target.checked, e.nativeEvent.shiftKey);
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", {
+ ref: (el) => {
+ if (el) el.indeterminate = indeterminate === true;
+ },
+ type: "checkbox",
+ className: checkboxClassname,
+ onChange: handleChange,
+ ...props
+ });
+}
+function renderValue(props) {
+ try {
+ return props.row[props.column.key];
+ } catch {
+ return null;
+ }
+}
+var DataGridDefaultRenderersContext = (0, import_react5.createContext)(void 0);
+function useDefaultRenderers() {
+ return (0, import_react5.useContext)(DataGridDefaultRenderersContext);
+}
+var RowSelectionContext = (0, import_react5.createContext)(void 0);
+var RowSelectionChangeContext = (0, import_react5.createContext)(void 0);
+function useRowSelection() {
+ const rowSelectionContext = (0, import_react5.useContext)(RowSelectionContext);
+ const rowSelectionChangeContext = (0, import_react5.useContext)(RowSelectionChangeContext);
+ if (rowSelectionContext === void 0 || rowSelectionChangeContext === void 0) throw new Error("useRowSelection must be used within renderCell");
+ return {
+ isRowSelectionDisabled: rowSelectionContext.isRowSelectionDisabled,
+ isRowSelected: rowSelectionContext.isRowSelected,
+ onRowSelectionChange: rowSelectionChangeContext
+ };
+}
+var HeaderRowSelectionContext = (0, import_react5.createContext)(void 0);
+var HeaderRowSelectionChangeContext = (0, import_react5.createContext)(void 0);
+function useHeaderRowSelection() {
+ const headerRowSelectionContext = (0, import_react5.useContext)(HeaderRowSelectionContext);
+ const headerRowSelectionChangeContext = (0, import_react5.useContext)(HeaderRowSelectionChangeContext);
+ if (headerRowSelectionContext === void 0 || headerRowSelectionChangeContext === void 0) throw new Error("useHeaderRowSelection must be used within renderHeaderCell");
+ return {
+ isIndeterminate: headerRowSelectionContext.isIndeterminate,
+ isRowSelected: headerRowSelectionContext.isRowSelected,
+ onRowSelectionChange: headerRowSelectionChangeContext
+ };
+}
+var SELECT_COLUMN_KEY = "rdg-select-column";
+var headerSortCellClassname = "rdg-7-0-0-beta-58-56a248e4";
+var headerSortNameClassname = `rdg-header-sort-name rdg-7-0-0-beta-58-7fad8c83`;
+function renderHeaderCell({ column, sortDirection, priority }) {
+ if (!column.sortable) return column.name;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SortableHeaderCell, {
+ sortDirection,
+ priority,
+ children: column.name
+ });
+}
+function SortableHeaderCell({ sortDirection, priority, children }) {
+ const renderSortStatus$1 = useDefaultRenderers().renderSortStatus;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", {
+ className: headerSortCellClassname,
+ children: [/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", {
+ className: headerSortNameClassname,
+ children
+ }), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: renderSortStatus$1({
+ sortDirection,
+ priority
+ }) })]
+ });
+}
+var DEFAULT_COLUMN_WIDTH = "auto";
+var DEFAULT_COLUMN_MIN_WIDTH = 50;
+function useCalculatedColumns({ rawColumns, defaultColumnOptions, getColumnWidth, viewportWidth, scrollLeft, enableVirtualization }) {
+ const defaultWidth = defaultColumnOptions?.width ?? DEFAULT_COLUMN_WIDTH;
+ const defaultMinWidth = defaultColumnOptions?.minWidth ?? DEFAULT_COLUMN_MIN_WIDTH;
+ const defaultMaxWidth = defaultColumnOptions?.maxWidth ?? void 0;
+ const defaultRenderCell$1 = defaultColumnOptions?.renderCell ?? renderValue;
+ const defaultRenderHeaderCell = defaultColumnOptions?.renderHeaderCell ?? renderHeaderCell;
+ const defaultSortable = defaultColumnOptions?.sortable ?? false;
+ const defaultResizable = defaultColumnOptions?.resizable ?? false;
+ const defaultDraggable = defaultColumnOptions?.draggable ?? false;
+ const { columns, colSpanColumns, lastFrozenColumnIndex, headerRowsCount } = (0, import_react5.useMemo)(() => {
+ let lastFrozenColumnIndex$1 = -1;
+ let headerRowsCount$1 = 1;
+ const columns$1 = [];
+ collectColumns(rawColumns, 1);
+ function collectColumns(rawColumns$1, level, parent) {
+ for (const rawColumn of rawColumns$1) {
+ if ("children" in rawColumn) {
+ const calculatedColumnParent = {
+ name: rawColumn.name,
+ parent,
+ idx: -1,
+ colSpan: 0,
+ level: 0,
+ headerCellClass: rawColumn.headerCellClass
+ };
+ collectColumns(rawColumn.children, level + 1, calculatedColumnParent);
+ continue;
+ }
+ const frozen = rawColumn.frozen ?? false;
+ const column = {
+ ...rawColumn,
+ parent,
+ idx: 0,
+ level: 0,
+ frozen,
+ width: rawColumn.width ?? defaultWidth,
+ minWidth: rawColumn.minWidth ?? defaultMinWidth,
+ maxWidth: rawColumn.maxWidth ?? defaultMaxWidth,
+ sortable: rawColumn.sortable ?? defaultSortable,
+ resizable: rawColumn.resizable ?? defaultResizable,
+ draggable: rawColumn.draggable ?? defaultDraggable,
+ renderCell: rawColumn.renderCell ?? defaultRenderCell$1,
+ renderHeaderCell: rawColumn.renderHeaderCell ?? defaultRenderHeaderCell
+ };
+ columns$1.push(column);
+ if (frozen) lastFrozenColumnIndex$1++;
+ if (level > headerRowsCount$1) headerRowsCount$1 = level;
+ }
+ }
+ columns$1.sort(({ key: aKey, frozen: frozenA }, { key: bKey, frozen: frozenB }) => {
+ if (aKey === SELECT_COLUMN_KEY) return -1;
+ if (bKey === SELECT_COLUMN_KEY) return 1;
+ if (frozenA) {
+ if (frozenB) return 0;
+ return -1;
+ }
+ if (frozenB) return 1;
+ return 0;
+ });
+ const colSpanColumns$1 = [];
+ columns$1.forEach((column, idx) => {
+ column.idx = idx;
+ updateColumnParent(column, idx, 0);
+ if (column.colSpan != null) colSpanColumns$1.push(column);
+ });
+ return {
+ columns: columns$1,
+ colSpanColumns: colSpanColumns$1,
+ lastFrozenColumnIndex: lastFrozenColumnIndex$1,
+ headerRowsCount: headerRowsCount$1
+ };
+ }, [
+ rawColumns,
+ defaultWidth,
+ defaultMinWidth,
+ defaultMaxWidth,
+ defaultRenderCell$1,
+ defaultRenderHeaderCell,
+ defaultResizable,
+ defaultSortable,
+ defaultDraggable
+ ]);
+ const { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics } = (0, import_react5.useMemo)(() => {
+ const columnMetrics$1 = /* @__PURE__ */ new Map();
+ let left = 0;
+ let totalFrozenColumnWidth$1 = 0;
+ const templateColumns$1 = [];
+ for (const column of columns) {
+ let width = getColumnWidth(column);
+ if (typeof width === "number") width = clampColumnWidth(width, column);
+ else width = column.minWidth;
+ templateColumns$1.push(`${width}px`);
+ columnMetrics$1.set(column, {
+ width,
+ left
+ });
+ left += width;
+ }
+ if (lastFrozenColumnIndex !== -1) {
+ const columnMetric = columnMetrics$1.get(columns[lastFrozenColumnIndex]);
+ totalFrozenColumnWidth$1 = columnMetric.left + columnMetric.width;
+ }
+ const layoutCssVars$1 = {};
+ for (let i = 0; i <= lastFrozenColumnIndex; i++) {
+ const column = columns[i];
+ layoutCssVars$1[`--rdg-frozen-left-${column.idx}`] = `${columnMetrics$1.get(column).left}px`;
+ }
+ return {
+ templateColumns: templateColumns$1,
+ layoutCssVars: layoutCssVars$1,
+ totalFrozenColumnWidth: totalFrozenColumnWidth$1,
+ columnMetrics: columnMetrics$1
+ };
+ }, [
+ getColumnWidth,
+ columns,
+ lastFrozenColumnIndex
+ ]);
+ const [colOverscanStartIdx, colOverscanEndIdx] = (0, import_react5.useMemo)(() => {
+ if (!enableVirtualization) return [0, columns.length - 1];
+ const viewportLeft = scrollLeft + totalFrozenColumnWidth;
+ const viewportRight = scrollLeft + viewportWidth;
+ const lastColIdx = columns.length - 1;
+ const firstUnfrozenColumnIdx = min(lastFrozenColumnIndex + 1, lastColIdx);
+ if (viewportLeft >= viewportRight) return [firstUnfrozenColumnIdx, firstUnfrozenColumnIdx];
+ let colVisibleStartIdx = firstUnfrozenColumnIdx;
+ while (colVisibleStartIdx < lastColIdx) {
+ const { left, width } = columnMetrics.get(columns[colVisibleStartIdx]);
+ if (left + width > viewportLeft) break;
+ colVisibleStartIdx++;
+ }
+ let colVisibleEndIdx = colVisibleStartIdx;
+ while (colVisibleEndIdx < lastColIdx) {
+ const { left, width } = columnMetrics.get(columns[colVisibleEndIdx]);
+ if (left + width >= viewportRight) break;
+ colVisibleEndIdx++;
+ }
+ return [max(firstUnfrozenColumnIdx, colVisibleStartIdx - 1), min(lastColIdx, colVisibleEndIdx + 1)];
+ }, [
+ columnMetrics,
+ columns,
+ lastFrozenColumnIndex,
+ scrollLeft,
+ totalFrozenColumnWidth,
+ viewportWidth,
+ enableVirtualization
+ ]);
+ return {
+ columns,
+ colSpanColumns,
+ colOverscanStartIdx,
+ colOverscanEndIdx,
+ templateColumns,
+ layoutCssVars,
+ headerRowsCount,
+ lastFrozenColumnIndex,
+ totalFrozenColumnWidth
+ };
+}
+function updateColumnParent(column, index, level) {
+ if (level < column.level) column.level = level;
+ if (column.parent !== void 0) {
+ const { parent } = column;
+ if (parent.idx === -1) parent.idx = index;
+ parent.colSpan += 1;
+ updateColumnParent(parent, index, level - 1);
+ }
+}
+function useColumnWidths(columns, viewportColumns, templateColumns, gridRef, gridWidth, columnWidths, onColumnWidthsChange, onColumnResize, setColumnResizing) {
+ const [columnToAutoResize, setColumnToAutoResize] = (0, import_react5.useState)(null);
+ const [columnsToMeasureOnResize, setColumnsToMeasureOnResize] = (0, import_react5.useState)(null);
+ const [prevGridWidth, setPreviousGridWidth] = (0, import_react5.useState)(gridWidth);
+ const columnsCanFlex = columns.length === viewportColumns.length;
+ const ignorePreviouslyMeasuredColumnsOnGridWidthChange = columnsCanFlex && gridWidth !== prevGridWidth;
+ const newTemplateColumns = [...templateColumns];
+ const columnsToMeasure = [];
+ for (const { key, idx, width } of viewportColumns) {
+ const columnWidth = columnWidths.get(key);
+ if (key === columnToAutoResize?.key) {
+ newTemplateColumns[idx] = columnToAutoResize.width === "max-content" ? columnToAutoResize.width : `${columnToAutoResize.width}px`;
+ columnsToMeasure.push(key);
+ } else if (typeof width === "string" && columnWidth?.type !== "resized" && (ignorePreviouslyMeasuredColumnsOnGridWidthChange || columnsToMeasureOnResize?.has(key) === true || columnWidth === void 0)) {
+ newTemplateColumns[idx] = width;
+ columnsToMeasure.push(key);
+ }
+ }
+ const gridTemplateColumns = newTemplateColumns.join(" ");
+ (0, import_react5.useLayoutEffect)(updateMeasuredAndResizedWidths);
+ function updateMeasuredAndResizedWidths() {
+ setPreviousGridWidth(gridWidth);
+ if (columnsToMeasure.length === 0) return;
+ const newColumnWidths = new Map(columnWidths);
+ let hasChanges = false;
+ for (const key of columnsToMeasure) {
+ const measuredWidth = measureColumnWidth(gridRef, key);
+ hasChanges || (hasChanges = measuredWidth !== columnWidths.get(key)?.width);
+ if (measuredWidth === void 0) newColumnWidths.delete(key);
+ else newColumnWidths.set(key, {
+ type: "measured",
+ width: measuredWidth
+ });
+ }
+ if (columnToAutoResize !== null) {
+ const resizingKey = columnToAutoResize.key;
+ const oldWidth = columnWidths.get(resizingKey)?.width;
+ const newWidth = measureColumnWidth(gridRef, resizingKey);
+ if (newWidth !== void 0 && oldWidth !== newWidth) {
+ hasChanges = true;
+ newColumnWidths.set(resizingKey, {
+ type: "resized",
+ width: newWidth
+ });
+ }
+ setColumnToAutoResize(null);
+ }
+ if (hasChanges) onColumnWidthsChange(newColumnWidths);
+ }
+ function handleColumnResize(column, nextWidth) {
+ const { key: resizingKey } = column;
+ (0, import_react_dom.flushSync)(() => {
+ if (columnsCanFlex) {
+ const columnsToRemeasure = /* @__PURE__ */ new Set();
+ for (const { key, width } of viewportColumns) if (resizingKey !== key && typeof width === "string" && columnWidths.get(key)?.type !== "resized") columnsToRemeasure.add(key);
+ setColumnsToMeasureOnResize(columnsToRemeasure);
+ }
+ setColumnToAutoResize({
+ key: resizingKey,
+ width: nextWidth
+ });
+ setColumnResizing(typeof nextWidth === "number");
+ });
+ setColumnsToMeasureOnResize(null);
+ if (onColumnResize) {
+ const previousWidth = columnWidths.get(resizingKey)?.width;
+ const newWidth = typeof nextWidth === "number" ? nextWidth : measureColumnWidth(gridRef, resizingKey);
+ if (newWidth !== void 0 && newWidth !== previousWidth) onColumnResize(column, newWidth);
+ }
+ }
+ return {
+ gridTemplateColumns,
+ handleColumnResize
+ };
+}
+function measureColumnWidth(gridRef, key) {
+ const selector = `[data-measuring-cell-key="${CSS.escape(key)}"]`;
+ return gridRef.current?.querySelector(selector)?.getBoundingClientRect().width;
+}
+function useGridDimensions() {
+ const gridRef = (0, import_react5.useRef)(null);
+ const [inlineSize, setInlineSize] = (0, import_react5.useState)(1);
+ const [blockSize, setBlockSize] = (0, import_react5.useState)(1);
+ const [horizontalScrollbarHeight, setHorizontalScrollbarHeight] = (0, import_react5.useState)(0);
+ (0, import_react5.useLayoutEffect)(() => {
+ const { ResizeObserver } = window;
+ if (ResizeObserver == null) return;
+ const { clientWidth, clientHeight, offsetWidth, offsetHeight } = gridRef.current;
+ const { width, height } = gridRef.current.getBoundingClientRect();
+ const initialHorizontalScrollbarHeight = offsetHeight - clientHeight;
+ const initialWidth = width - offsetWidth + clientWidth;
+ const initialHeight = height - initialHorizontalScrollbarHeight;
+ setInlineSize(initialWidth);
+ setBlockSize(initialHeight);
+ setHorizontalScrollbarHeight(initialHorizontalScrollbarHeight);
+ const resizeObserver = new ResizeObserver((entries) => {
+ const size = entries[0].contentBoxSize[0];
+ const { clientHeight: clientHeight$1, offsetHeight: offsetHeight$1 } = gridRef.current;
+ (0, import_react_dom.flushSync)(() => {
+ setInlineSize(size.inlineSize);
+ setBlockSize(size.blockSize);
+ setHorizontalScrollbarHeight(offsetHeight$1 - clientHeight$1);
+ });
+ });
+ resizeObserver.observe(gridRef.current);
+ return () => {
+ resizeObserver.disconnect();
+ };
+ }, []);
+ return [
+ gridRef,
+ inlineSize,
+ blockSize,
+ horizontalScrollbarHeight
+ ];
+}
+function useLatestFunc(fn) {
+ const ref = (0, import_react5.useRef)(fn);
+ (0, import_react5.useLayoutEffect)(() => {
+ ref.current = fn;
+ });
+ const callbackFn = (0, import_react5.useCallback)((...args) => {
+ ref.current(...args);
+ }, []);
+ return fn ? callbackFn : fn;
+}
+function useRovingTabIndex(isSelected) {
+ const [isChildFocused, setIsChildFocused] = (0, import_react5.useState)(false);
+ if (isChildFocused && !isSelected) setIsChildFocused(false);
+ function onFocus(event) {
+ if (event.target === event.currentTarget) {
+ const elementToFocus = event.currentTarget.querySelector('[tabindex="0"]');
+ if (elementToFocus !== null) {
+ elementToFocus.focus({ preventScroll: true });
+ setIsChildFocused(true);
+ } else setIsChildFocused(false);
+ } else setIsChildFocused(true);
+ }
+ return {
+ tabIndex: isSelected && !isChildFocused ? 0 : -1,
+ childTabIndex: isSelected ? 0 : -1,
+ onFocus: isSelected ? onFocus : void 0
+ };
+}
+function useViewportColumns({ columns, colSpanColumns, rows, topSummaryRows, bottomSummaryRows, colOverscanStartIdx, colOverscanEndIdx, lastFrozenColumnIndex, rowOverscanStartIdx, rowOverscanEndIdx }) {
+ const startIdx = (0, import_react5.useMemo)(() => {
+ if (colOverscanStartIdx === 0) return 0;
+ let startIdx$1 = colOverscanStartIdx;
+ const updateStartIdx = (colIdx, colSpan) => {
+ if (colSpan !== void 0 && colIdx + colSpan > colOverscanStartIdx) {
+ startIdx$1 = colIdx;
+ return true;
+ }
+ return false;
+ };
+ for (const column of colSpanColumns) {
+ const colIdx = column.idx;
+ if (colIdx >= startIdx$1) break;
+ if (updateStartIdx(colIdx, getColSpan(column, lastFrozenColumnIndex, { type: "HEADER" }))) break;
+ for (let rowIdx = rowOverscanStartIdx; rowIdx <= rowOverscanEndIdx; rowIdx++) {
+ const row$1 = rows[rowIdx];
+ if (updateStartIdx(colIdx, getColSpan(column, lastFrozenColumnIndex, {
+ type: "ROW",
+ row: row$1
+ }))) break;
+ }
+ if (topSummaryRows != null) {
+ for (const row$1 of topSummaryRows) if (updateStartIdx(colIdx, getColSpan(column, lastFrozenColumnIndex, {
+ type: "SUMMARY",
+ row: row$1
+ }))) break;
+ }
+ if (bottomSummaryRows != null) {
+ for (const row$1 of bottomSummaryRows) if (updateStartIdx(colIdx, getColSpan(column, lastFrozenColumnIndex, {
+ type: "SUMMARY",
+ row: row$1
+ }))) break;
+ }
+ }
+ return startIdx$1;
+ }, [
+ rowOverscanStartIdx,
+ rowOverscanEndIdx,
+ rows,
+ topSummaryRows,
+ bottomSummaryRows,
+ colOverscanStartIdx,
+ lastFrozenColumnIndex,
+ colSpanColumns
+ ]);
+ return (0, import_react5.useMemo)(() => {
+ const viewportColumns = [];
+ for (let colIdx = 0; colIdx <= colOverscanEndIdx; colIdx++) {
+ const column = columns[colIdx];
+ if (colIdx < startIdx && !column.frozen) continue;
+ viewportColumns.push(column);
+ }
+ return viewportColumns;
+ }, [
+ startIdx,
+ colOverscanEndIdx,
+ columns
+ ]);
+}
+function useViewportRows({ rows, rowHeight, clientHeight, scrollTop, enableVirtualization }) {
+ const { totalRowHeight, gridTemplateRows, getRowTop, getRowHeight, findRowIdx } = (0, import_react5.useMemo)(() => {
+ if (typeof rowHeight === "number") return {
+ totalRowHeight: rowHeight * rows.length,
+ gridTemplateRows: ` repeat(${rows.length}, ${rowHeight}px)`,
+ getRowTop: (rowIdx) => rowIdx * rowHeight,
+ getRowHeight: () => rowHeight,
+ findRowIdx: (offset) => floor(offset / rowHeight)
+ };
+ let totalRowHeight$1 = 0;
+ let gridTemplateRows$1 = "";
+ let currentHeight = null;
+ let repeatCount = 0;
+ const rowPositions = rows.map((row$1, index) => {
+ const currentRowHeight = rowHeight(row$1);
+ const position = {
+ top: totalRowHeight$1,
+ height: currentRowHeight
+ };
+ totalRowHeight$1 += currentRowHeight;
+ if (currentHeight === null) {
+ currentHeight = currentRowHeight;
+ repeatCount = 1;
+ } else if (currentHeight === currentRowHeight) repeatCount++;
+ else {
+ if (repeatCount > 1) gridTemplateRows$1 += `repeat(${repeatCount}, ${currentHeight}px) `;
+ else gridTemplateRows$1 += `${currentHeight}px `;
+ currentHeight = currentRowHeight;
+ repeatCount = 1;
+ }
+ if (index === rows.length - 1) if (repeatCount > 1) gridTemplateRows$1 += `repeat(${repeatCount}, ${currentHeight}px)`;
+ else gridTemplateRows$1 += `${currentHeight}px`;
+ return position;
+ });
+ const validateRowIdx = (rowIdx) => {
+ return max(0, min(rows.length - 1, rowIdx));
+ };
+ return {
+ totalRowHeight: totalRowHeight$1,
+ gridTemplateRows: gridTemplateRows$1,
+ getRowTop: (rowIdx) => rowPositions[validateRowIdx(rowIdx)].top,
+ getRowHeight: (rowIdx) => rowPositions[validateRowIdx(rowIdx)].height,
+ findRowIdx(offset) {
+ let start = 0;
+ let end = rowPositions.length - 1;
+ while (start <= end) {
+ const middle = start + floor((end - start) / 2);
+ const currentOffset = rowPositions[middle].top;
+ if (currentOffset === offset) return middle;
+ if (currentOffset < offset) start = middle + 1;
+ else if (currentOffset > offset) end = middle - 1;
+ if (start > end) return end;
+ }
+ return 0;
+ }
+ };
+ }, [rowHeight, rows]);
+ let rowOverscanStartIdx = 0;
+ let rowOverscanEndIdx = rows.length - 1;
+ if (enableVirtualization) {
+ const overscanThreshold = 4;
+ const rowVisibleStartIdx = findRowIdx(scrollTop);
+ const rowVisibleEndIdx = findRowIdx(scrollTop + clientHeight);
+ rowOverscanStartIdx = max(0, rowVisibleStartIdx - overscanThreshold);
+ rowOverscanEndIdx = min(rows.length - 1, rowVisibleEndIdx + overscanThreshold);
+ }
+ return {
+ rowOverscanStartIdx,
+ rowOverscanEndIdx,
+ totalRowHeight,
+ gridTemplateRows,
+ getRowTop,
+ getRowHeight,
+ findRowIdx
+ };
+}
+var cellDraggedOverClassname = `rdg-cell-dragged-over rdg-7-0-0-beta-58-35ccb4c8`;
+function Cell({ column, colSpan, isCellSelected, isDraggedOver, row: row$1, rowIdx, className, onMouseDown, onCellMouseDown, onClick, onCellClick, onDoubleClick, onCellDoubleClick, onContextMenu, onCellContextMenu, onRowChange, selectCell, style, ...props }) {
+ const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
+ const { cellClass } = column;
+ className = getCellClassname(column, { [cellDraggedOverClassname]: isDraggedOver }, typeof cellClass === "function" ? cellClass(row$1) : cellClass, className);
+ const isEditable = isCellEditableUtil(column, row$1);
+ function selectCellWrapper(enableEditor) {
+ selectCell({
+ rowIdx,
+ idx: column.idx
+ }, { enableEditor });
+ }
+ function handleMouseEvent(event, eventHandler) {
+ let eventHandled = false;
+ if (eventHandler) {
+ const cellEvent = createCellEvent(event);
+ eventHandler({
+ rowIdx,
+ row: row$1,
+ column,
+ selectCell: selectCellWrapper
+ }, cellEvent);
+ eventHandled = cellEvent.isGridDefaultPrevented();
+ }
+ return eventHandled;
+ }
+ function handleMouseDown(event) {
+ onMouseDown?.(event);
+ if (!handleMouseEvent(event, onCellMouseDown)) selectCellWrapper();
+ }
+ function handleClick(event) {
+ onClick?.(event);
+ handleMouseEvent(event, onCellClick);
+ }
+ function handleDoubleClick(event) {
+ onDoubleClick?.(event);
+ if (!handleMouseEvent(event, onCellDoubleClick)) selectCellWrapper(true);
+ }
+ function handleContextMenu(event) {
+ onContextMenu?.(event);
+ handleMouseEvent(event, onCellContextMenu);
+ }
+ function handleRowChange(newRow) {
+ onRowChange(column, newRow);
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "gridcell",
+ "aria-colindex": column.idx + 1,
+ "aria-colspan": colSpan,
+ "aria-selected": isCellSelected,
+ "aria-readonly": !isEditable || void 0,
+ tabIndex,
+ className,
+ style: {
+ ...getCellStyle(column, colSpan),
+ ...style
+ },
+ onClick: handleClick,
+ onMouseDown: handleMouseDown,
+ onDoubleClick: handleDoubleClick,
+ onContextMenu: handleContextMenu,
+ onFocus,
+ ...props,
+ children: column.renderCell({
+ column,
+ row: row$1,
+ rowIdx,
+ isCellEditable: isEditable,
+ tabIndex: childTabIndex,
+ onRowChange: handleRowChange
+ })
+ });
+}
+var CellComponent = (0, import_react5.memo)(Cell);
+function defaultRenderCell(key, props) {
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CellComponent, { ...props }, key);
+}
+var canUsePostTask = typeof scheduler === "object" && typeof scheduler.postTask === "function";
+var cellEditing = "rdg-7-0-0-beta-58-46f9ea88";
+function EditCell({ column, colSpan, row: row$1, rowIdx, onRowChange, closeEditor, onKeyDown, navigate }) {
+ const captureEventRef = (0, import_react5.useRef)(void 0);
+ const abortControllerRef = (0, import_react5.useRef)(void 0);
+ const frameRequestRef = (0, import_react5.useRef)(void 0);
+ const commitOnOutsideClick = column.editorOptions?.commitOnOutsideClick ?? true;
+ const commitOnOutsideMouseDown = (0, import_react5.useEffectEvent)(() => {
+ onClose(true, false);
+ });
+ (0, import_react5.useLayoutEffect)(() => {
+ if (!commitOnOutsideClick) return;
+ function onWindowCaptureMouseDown(event) {
+ captureEventRef.current = event;
+ if (canUsePostTask) {
+ const abortController = new AbortController();
+ const { signal } = abortController;
+ abortControllerRef.current = abortController;
+ scheduler.postTask(commitOnOutsideMouseDown, {
+ priority: "user-blocking",
+ signal
+ }).catch(() => {
+ });
+ } else frameRequestRef.current = requestAnimationFrame(commitOnOutsideMouseDown);
+ }
+ function onWindowMouseDown(event) {
+ if (captureEventRef.current === event) commitOnOutsideMouseDown();
+ }
+ addEventListener("mousedown", onWindowCaptureMouseDown, { capture: true });
+ addEventListener("mousedown", onWindowMouseDown);
+ return () => {
+ removeEventListener("mousedown", onWindowCaptureMouseDown, { capture: true });
+ removeEventListener("mousedown", onWindowMouseDown);
+ cancelTask();
+ };
+ }, [commitOnOutsideClick]);
+ function cancelTask() {
+ captureEventRef.current = void 0;
+ if (abortControllerRef.current !== void 0) {
+ abortControllerRef.current.abort();
+ abortControllerRef.current = void 0;
+ }
+ if (frameRequestRef.current !== void 0) {
+ cancelAnimationFrame(frameRequestRef.current);
+ frameRequestRef.current = void 0;
+ }
+ }
+ function handleKeyDown(event) {
+ if (onKeyDown) {
+ const cellEvent = createCellEvent(event);
+ onKeyDown({
+ mode: "EDIT",
+ row: row$1,
+ column,
+ rowIdx,
+ navigate() {
+ navigate(event);
+ },
+ onClose
+ }, cellEvent);
+ if (cellEvent.isGridDefaultPrevented()) return;
+ }
+ if (event.key === "Escape") onClose();
+ else if (event.key === "Enter") onClose(true);
+ else if (onEditorNavigation(event)) navigate(event);
+ }
+ function onClose(commitChanges = false, shouldFocusCell = true) {
+ if (commitChanges) onRowChange(row$1, true, shouldFocusCell);
+ else closeEditor(shouldFocusCell);
+ }
+ function onEditorRowChange(row$2, commitChangesAndFocus = false) {
+ onRowChange(row$2, commitChangesAndFocus, commitChangesAndFocus);
+ }
+ const { cellClass } = column;
+ const className = getCellClassname(column, "rdg-editor-container", !column.editorOptions?.displayCellContent && cellEditing, typeof cellClass === "function" ? cellClass(row$1) : cellClass);
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "gridcell",
+ "aria-colindex": column.idx + 1,
+ "aria-colspan": colSpan,
+ "aria-selected": true,
+ className,
+ style: getCellStyle(column, colSpan),
+ onKeyDown: handleKeyDown,
+ onMouseDownCapture: cancelTask,
+ children: column.renderEditCell != null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [column.renderEditCell({
+ column,
+ row: row$1,
+ rowIdx,
+ onRowChange: onEditorRowChange,
+ onClose
+ }), column.editorOptions?.displayCellContent && column.renderCell({
+ column,
+ row: row$1,
+ rowIdx,
+ isCellEditable: true,
+ tabIndex: -1,
+ onRowChange: onEditorRowChange
+ })] })
+ });
+}
+function GroupedColumnHeaderCell({ column, rowIdx, isCellSelected, selectCell }) {
+ const { tabIndex, onFocus } = useRovingTabIndex(isCellSelected);
+ const { colSpan } = column;
+ const rowSpan = getHeaderCellRowSpan(column, rowIdx);
+ const index = column.idx + 1;
+ function onMouseDown() {
+ selectCell({
+ idx: column.idx,
+ rowIdx
+ });
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "columnheader",
+ "aria-colindex": index,
+ "aria-colspan": colSpan,
+ "aria-rowspan": rowSpan,
+ "aria-selected": isCellSelected,
+ tabIndex,
+ className: classnames(cellClassname, column.headerCellClass),
+ style: {
+ ...getHeaderCellStyle(column, rowIdx, rowSpan),
+ gridColumnStart: index,
+ gridColumnEnd: index + colSpan
+ },
+ onFocus,
+ onMouseDown,
+ children: column.name
+ });
+}
+var cellSortableClassname = "rdg-7-0-0-beta-58-2a7e240d";
+var cellResizableClassname = `rdg-cell-resizable rdg-7-0-0-beta-58-1893dc0f`;
+var resizeHandleClassname = `rdg-resize-handle rdg-7-0-0-beta-58-4e60db91`;
+var cellDraggableClassname = "rdg-cell-draggable";
+var cellDraggingClassname = `rdg-cell-dragging rdg-7-0-0-beta-58-3e1a4ad4`;
+var cellOverClassname = `rdg-cell-drag-over rdg-7-0-0-beta-58-51abd8b8`;
+var dragImageClassname = "rdg-7-0-0-beta-58-c8d7aa64";
+function HeaderCell({ column, colSpan, rowIdx, isCellSelected, onColumnResize, onColumnResizeEnd, onColumnsReorder, sortColumns, onSortColumnsChange, selectCell, shouldFocusGrid, direction, draggedColumnKey, setDraggedColumnKey }) {
+ const [isOver, setIsOver] = (0, import_react5.useState)(false);
+ const dragImageRef = (0, import_react5.useRef)(null);
+ const isDragging = draggedColumnKey === column.key;
+ const rowSpan = getHeaderCellRowSpan(column, rowIdx);
+ const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(shouldFocusGrid || isCellSelected);
+ const sortIndex = sortColumns?.findIndex((sort) => sort.columnKey === column.key);
+ const sortColumn = sortIndex !== void 0 && sortIndex > -1 ? sortColumns[sortIndex] : void 0;
+ const sortDirection = sortColumn?.direction;
+ const priority = sortColumn !== void 0 && sortColumns.length > 1 ? sortIndex + 1 : void 0;
+ const ariaSort = sortDirection && !priority ? sortDirection === "ASC" ? "ascending" : "descending" : void 0;
+ const { sortable, resizable, draggable } = column;
+ const className = getCellClassname(column, column.headerCellClass, {
+ [cellSortableClassname]: sortable,
+ [cellResizableClassname]: resizable,
+ [cellDraggableClassname]: draggable,
+ [cellDraggingClassname]: isDragging,
+ [cellOverClassname]: isOver
+ });
+ function onSort(ctrlClick) {
+ if (onSortColumnsChange == null) return;
+ const { sortDescendingFirst } = column;
+ if (sortColumn === void 0) {
+ const nextSort = {
+ columnKey: column.key,
+ direction: sortDescendingFirst ? "DESC" : "ASC"
+ };
+ onSortColumnsChange(sortColumns && ctrlClick ? [...sortColumns, nextSort] : [nextSort]);
+ } else {
+ let nextSortColumn;
+ if (sortDescendingFirst === true && sortDirection === "DESC" || sortDescendingFirst !== true && sortDirection === "ASC") nextSortColumn = {
+ columnKey: column.key,
+ direction: sortDirection === "ASC" ? "DESC" : "ASC"
+ };
+ if (ctrlClick) {
+ const nextSortColumns = [...sortColumns];
+ if (nextSortColumn) nextSortColumns[sortIndex] = nextSortColumn;
+ else nextSortColumns.splice(sortIndex, 1);
+ onSortColumnsChange(nextSortColumns);
+ } else onSortColumnsChange(nextSortColumn ? [nextSortColumn] : []);
+ }
+ }
+ function handleFocus(event) {
+ onFocus?.(event);
+ if (shouldFocusGrid) selectCell({
+ idx: 0,
+ rowIdx
+ });
+ }
+ function onMouseDown() {
+ selectCell({
+ idx: column.idx,
+ rowIdx
+ });
+ }
+ function onClick(event) {
+ if (sortable) onSort(event.ctrlKey || event.metaKey);
+ }
+ function onKeyDown(event) {
+ const { key } = event;
+ if (sortable && (key === " " || key === "Enter")) {
+ event.preventDefault();
+ onSort(event.ctrlKey || event.metaKey);
+ } else if (resizable && isCtrlKeyHeldDown(event) && (key === "ArrowLeft" || key === "ArrowRight")) {
+ event.stopPropagation();
+ const { width } = event.currentTarget.getBoundingClientRect();
+ const { leftKey } = getLeftRightKey(direction);
+ const newWidth = clampColumnWidth(width + (key === leftKey ? -10 : 10), column);
+ if (newWidth !== width) onColumnResize(column, newWidth);
+ }
+ }
+ function onDragStart(event) {
+ (0, import_react_dom.flushSync)(() => {
+ setDraggedColumnKey(column.key);
+ });
+ event.dataTransfer.setDragImage(dragImageRef.current, 0, 0);
+ event.dataTransfer.dropEffect = "move";
+ }
+ function onDragEnd() {
+ setDraggedColumnKey(void 0);
+ }
+ function onDragOver(event) {
+ event.preventDefault();
+ event.dataTransfer.dropEffect = "move";
+ }
+ function onDrop(event) {
+ setIsOver(false);
+ event.preventDefault();
+ onColumnsReorder?.(draggedColumnKey, column.key);
+ }
+ function onDragEnter(event) {
+ if (isEventPertinent(event)) setIsOver(true);
+ }
+ function onDragLeave(event) {
+ if (isEventPertinent(event)) setIsOver(false);
+ }
+ let dragTargetProps;
+ let dropTargetProps;
+ if (draggable) {
+ dragTargetProps = {
+ draggable: true,
+ onDragStart,
+ onDragEnd
+ };
+ if (draggedColumnKey !== void 0 && draggedColumnKey !== column.key) dropTargetProps = {
+ onDragOver,
+ onDragEnter,
+ onDragLeave,
+ onDrop
+ };
+ }
+ const style = {
+ ...getHeaderCellStyle(column, rowIdx, rowSpan),
+ ...getCellStyle(column, colSpan)
+ };
+ const content = column.renderHeaderCell({
+ column,
+ sortDirection,
+ priority,
+ tabIndex: childTabIndex
+ });
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [isDragging && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ ref: dragImageRef,
+ style,
+ className: getCellClassname(column, column.headerCellClass, dragImageClassname),
+ children: content
+ }), /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", {
+ role: "columnheader",
+ "aria-colindex": column.idx + 1,
+ "aria-colspan": colSpan,
+ "aria-rowspan": rowSpan,
+ "aria-selected": isCellSelected,
+ "aria-sort": ariaSort,
+ tabIndex,
+ className,
+ style,
+ onMouseDown,
+ onFocus: handleFocus,
+ onClick,
+ onKeyDown,
+ ...dragTargetProps,
+ ...dropTargetProps,
+ children: [content, resizable && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ResizeHandle, {
+ direction,
+ column,
+ onColumnResize,
+ onColumnResizeEnd
+ })]
+ })] });
+}
+function ResizeHandle({ direction, column, onColumnResize, onColumnResizeEnd }) {
+ const resizingOffsetRef = (0, import_react5.useRef)(void 0);
+ const isRtl = direction === "rtl";
+ function onPointerDown(event) {
+ if (event.pointerType === "mouse" && event.buttons !== 1) return;
+ event.preventDefault();
+ const { currentTarget, pointerId } = event;
+ currentTarget.setPointerCapture(pointerId);
+ const { right, left } = currentTarget.parentElement.getBoundingClientRect();
+ resizingOffsetRef.current = isRtl ? event.clientX - left : right - event.clientX;
+ }
+ function onPointerMove(event) {
+ const offset = resizingOffsetRef.current;
+ if (offset === void 0) return;
+ const { width, right, left } = event.currentTarget.parentElement.getBoundingClientRect();
+ let newWidth = isRtl ? right + offset - event.clientX : event.clientX + offset - left;
+ newWidth = clampColumnWidth(newWidth, column);
+ if (width > 0 && newWidth !== width) onColumnResize(column, newWidth);
+ }
+ function onLostPointerCapture() {
+ onColumnResizeEnd();
+ resizingOffsetRef.current = void 0;
+ }
+ function onDoubleClick() {
+ onColumnResize(column, "max-content");
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ "aria-hidden": true,
+ className: resizeHandleClassname,
+ onClick: stopPropagation,
+ onPointerDown,
+ onPointerMove,
+ onLostPointerCapture,
+ onDoubleClick
+ });
+}
+function isEventPertinent(event) {
+ const relatedTarget = event.relatedTarget;
+ return !event.currentTarget.contains(relatedTarget);
+}
+var row = "rdg-7-0-0-beta-58-3c083f1b";
+var rowClassname = `rdg-row ${row}`;
+var rowSelected = "rdg-7-0-0-beta-58-3fe773c3";
+var rowSelectedClassname = "rdg-row-selected";
+var rowSelectedWithFrozenCell = "rdg-7-0-0-beta-58-97ce3fde";
+var topSummaryRowClassname = "rdg-top-summary-row";
+var bottomSummaryRowClassname = "rdg-bottom-summary-row";
+var headerRow = "rdg-7-0-0-beta-58-0dbd5994";
+var headerRowClassname = `rdg-header-row ${headerRow}`;
+function HeaderRow({ headerRowClass, rowIdx, columns, onColumnResize, onColumnResizeEnd, onColumnsReorder, sortColumns, onSortColumnsChange, lastFrozenColumnIndex, selectedCellIdx, selectCell, shouldFocusGrid, direction }) {
+ const [draggedColumnKey, setDraggedColumnKey] = (0, import_react5.useState)();
+ const cells = [];
+ for (let index = 0; index < columns.length; index++) {
+ const column = columns[index];
+ const colSpan = getColSpan(column, lastFrozenColumnIndex, { type: "HEADER" });
+ if (colSpan !== void 0) index += colSpan - 1;
+ cells.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HeaderCell, {
+ column,
+ colSpan,
+ rowIdx,
+ isCellSelected: selectedCellIdx === column.idx,
+ onColumnResize,
+ onColumnResizeEnd,
+ onColumnsReorder,
+ onSortColumnsChange,
+ sortColumns,
+ selectCell,
+ shouldFocusGrid: shouldFocusGrid && index === 0,
+ direction,
+ draggedColumnKey,
+ setDraggedColumnKey
+ }, column.key));
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "row",
+ "aria-rowindex": rowIdx,
+ className: classnames(headerRowClassname, { [rowSelectedClassname]: selectedCellIdx === -1 }, headerRowClass),
+ children: cells
+ });
+}
+var HeaderRow_default = (0, import_react5.memo)(HeaderRow);
+function GroupedColumnHeaderRow({ rowIdx, level, columns, selectedCellIdx, selectCell }) {
+ const cells = [];
+ const renderedParents = /* @__PURE__ */ new Set();
+ for (const column of columns) {
+ let { parent } = column;
+ if (parent === void 0) continue;
+ while (parent.level > level) {
+ if (parent.parent === void 0) break;
+ parent = parent.parent;
+ }
+ if (parent.level === level && !renderedParents.has(parent)) {
+ renderedParents.add(parent);
+ const { idx } = parent;
+ cells.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GroupedColumnHeaderCell, {
+ column: parent,
+ rowIdx,
+ isCellSelected: selectedCellIdx === idx,
+ selectCell
+ }, idx));
+ }
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "row",
+ "aria-rowindex": rowIdx,
+ className: headerRowClassname,
+ children: cells
+ });
+}
+var GroupedColumnHeaderRow_default = (0, import_react5.memo)(GroupedColumnHeaderRow);
+function Row({ className, rowIdx, gridRowStart, selectedCellIdx, isRowSelectionDisabled, isRowSelected, draggedOverCellIdx, lastFrozenColumnIndex, row: row$1, viewportColumns, selectedCellEditor, onCellMouseDown, onCellClick, onCellDoubleClick, onCellContextMenu, rowClass, onRowChange, selectCell, style, ...props }) {
+ const renderCell = useDefaultRenderers().renderCell;
+ const handleRowChange = useLatestFunc((column, newRow) => {
+ onRowChange(column, rowIdx, newRow);
+ });
+ className = classnames(rowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, { [rowSelectedClassname]: selectedCellIdx === -1 }, rowClass?.(row$1, rowIdx), className);
+ const cells = [];
+ for (let index = 0; index < viewportColumns.length; index++) {
+ const column = viewportColumns[index];
+ const { idx } = column;
+ const colSpan = getColSpan(column, lastFrozenColumnIndex, {
+ type: "ROW",
+ row: row$1
+ });
+ if (colSpan !== void 0) index += colSpan - 1;
+ const isCellSelected = selectedCellIdx === idx;
+ if (isCellSelected && selectedCellEditor) cells.push(selectedCellEditor);
+ else cells.push(renderCell(column.key, {
+ column,
+ colSpan,
+ row: row$1,
+ rowIdx,
+ isDraggedOver: draggedOverCellIdx === idx,
+ isCellSelected,
+ onCellMouseDown,
+ onCellClick,
+ onCellDoubleClick,
+ onCellContextMenu,
+ onRowChange: handleRowChange,
+ selectCell
+ }));
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RowSelectionContext, {
+ value: (0, import_react5.useMemo)(() => ({
+ isRowSelected,
+ isRowSelectionDisabled
+ }), [isRowSelectionDisabled, isRowSelected]),
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "row",
+ className,
+ style: {
+ ...getRowStyle(gridRowStart),
+ ...style
+ },
+ ...props,
+ children: cells
+ })
+ });
+}
+var RowComponent = (0, import_react5.memo)(Row);
+function defaultRenderRow(key, props) {
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RowComponent, { ...props }, key);
+}
+function ScrollToCell({ scrollToPosition: { idx, rowIdx }, gridRef, setScrollToCellPosition }) {
+ const ref = (0, import_react5.useRef)(null);
+ (0, import_react5.useLayoutEffect)(() => {
+ scrollIntoView(ref.current, "auto");
+ });
+ (0, import_react5.useLayoutEffect)(() => {
+ function removeScrollToCell() {
+ setScrollToCellPosition(null);
+ }
+ const observer = new IntersectionObserver(removeScrollToCell, {
+ root: gridRef.current,
+ threshold: 1
+ });
+ observer.observe(ref.current);
+ return () => {
+ observer.disconnect();
+ };
+ }, [gridRef, setScrollToCellPosition]);
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ ref,
+ style: {
+ gridColumn: idx === void 0 ? "1/-1" : idx + 1,
+ gridRow: rowIdx === void 0 ? "1/-1" : rowIdx + 2
+ }
+ });
+}
+var arrowClassname = `rdg-sort-arrow rdg-7-0-0-beta-58-3d5115f3`;
+function renderSortStatus({ sortDirection, priority }) {
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [renderSortIcon({ sortDirection }), renderSortPriority({ priority })] });
+}
+function renderSortIcon({ sortDirection }) {
+ if (sortDirection === void 0) return null;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", {
+ viewBox: "0 0 12 8",
+ width: "12",
+ height: "8",
+ className: arrowClassname,
+ "aria-hidden": true,
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: sortDirection === "ASC" ? "M0 8 6 0 12 8" : "M0 0 6 8 12 0" })
+ });
+}
+function renderSortPriority({ priority }) {
+ return priority;
+}
+var root = "rdg-7-0-0-beta-58-ccd2e5d9";
+var rootClassname = `rdg ${root}`;
+var viewportDragging = "rdg-7-0-0-beta-58-e9b0e1c9";
+var viewportDraggingClassname = `rdg-viewport-dragging ${viewportDragging}`;
+var focusSinkClassname = "rdg-7-0-0-beta-58-dbb8b3c5";
+var focusSinkHeaderAndSummaryClassname = "rdg-7-0-0-beta-58-e9f55541";
+var summaryCellClassname = "rdg-7-0-0-beta-58-d907aa87";
+function SummaryCell({ column, colSpan, row: row$1, rowIdx, isCellSelected, selectCell }) {
+ const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
+ const { summaryCellClass } = column;
+ const className = getCellClassname(column, summaryCellClassname, typeof summaryCellClass === "function" ? summaryCellClass(row$1) : summaryCellClass);
+ function onMouseDown() {
+ selectCell({
+ rowIdx,
+ idx: column.idx
+ });
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "gridcell",
+ "aria-colindex": column.idx + 1,
+ "aria-colspan": colSpan,
+ "aria-selected": isCellSelected,
+ tabIndex,
+ className,
+ style: getCellStyle(column, colSpan),
+ onMouseDown,
+ onFocus,
+ children: column.renderSummaryCell?.({
+ column,
+ row: row$1,
+ tabIndex: childTabIndex
+ })
+ });
+}
+var SummaryCell_default = (0, import_react5.memo)(SummaryCell);
+var summaryRow = "rdg-7-0-0-beta-58-0b90c82c";
+var topSummaryRow = "rdg-7-0-0-beta-58-d0520eab";
+var summaryRowClassname = `rdg-summary-row ${summaryRow}`;
+function SummaryRow({ rowIdx, gridRowStart, row: row$1, viewportColumns, top, bottom, lastFrozenColumnIndex, selectedCellIdx, isTop, selectCell, "aria-rowindex": ariaRowIndex }) {
+ const cells = [];
+ for (let index = 0; index < viewportColumns.length; index++) {
+ const column = viewportColumns[index];
+ const colSpan = getColSpan(column, lastFrozenColumnIndex, {
+ type: "SUMMARY",
+ row: row$1
+ });
+ if (colSpan !== void 0) index += colSpan - 1;
+ const isCellSelected = selectedCellIdx === column.idx;
+ cells.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SummaryCell_default, {
+ column,
+ colSpan,
+ row: row$1,
+ rowIdx,
+ isCellSelected,
+ selectCell
+ }, column.key));
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "row",
+ "aria-rowindex": ariaRowIndex,
+ className: classnames(rowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, summaryRowClassname, {
+ [rowSelectedClassname]: selectedCellIdx === -1,
+ [`${topSummaryRowClassname} ${topSummaryRow}`]: isTop,
+ [bottomSummaryRowClassname]: !isTop
+ }),
+ style: {
+ ...getRowStyle(gridRowStart),
+ "--rdg-summary-row-top": top !== void 0 ? `${top}px` : void 0,
+ "--rdg-summary-row-bottom": bottom !== void 0 ? `${bottom}px` : void 0
+ },
+ children: cells
+ });
+}
+var SummaryRow_default = (0, import_react5.memo)(SummaryRow);
+function DataGrid(props) {
+ const { ref, columns: rawColumns, rows, topSummaryRows, bottomSummaryRows, rowKeyGetter, onRowsChange, rowHeight: rawRowHeight, headerRowHeight: rawHeaderRowHeight, summaryRowHeight: rawSummaryRowHeight, columnWidths: columnWidthsRaw, onColumnWidthsChange: onColumnWidthsChangeRaw, selectedRows, isRowSelectionDisabled, onSelectedRowsChange, sortColumns, onSortColumnsChange, defaultColumnOptions, onCellMouseDown, onCellClick, onCellDoubleClick, onCellContextMenu, onCellKeyDown, onSelectedCellChange, onScroll, onColumnResize, onColumnsReorder, onFill, onCellCopy, onCellPaste, enableVirtualization: rawEnableVirtualization, renderers, className, style, rowClass, headerRowClass, direction: rawDirection, role: rawRole, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-description": ariaDescription, "aria-describedby": ariaDescribedBy, "aria-rowcount": rawAriaRowCount, "data-testid": testId, "data-cy": dataCy } = props;
+ const defaultRenderers = useDefaultRenderers();
+ const role = rawRole ?? "grid";
+ const rowHeight = rawRowHeight ?? 35;
+ const headerRowHeight = rawHeaderRowHeight ?? (typeof rowHeight === "number" ? rowHeight : 35);
+ const summaryRowHeight = rawSummaryRowHeight ?? (typeof rowHeight === "number" ? rowHeight : 35);
+ const renderRow = renderers?.renderRow ?? defaultRenderers?.renderRow ?? defaultRenderRow;
+ const renderCell = renderers?.renderCell ?? defaultRenderers?.renderCell ?? defaultRenderCell;
+ const renderSortStatus$1 = renderers?.renderSortStatus ?? defaultRenderers?.renderSortStatus ?? renderSortStatus;
+ const renderCheckbox$1 = renderers?.renderCheckbox ?? defaultRenderers?.renderCheckbox ?? renderCheckbox;
+ const noRowsFallback = renderers?.noRowsFallback ?? defaultRenderers?.noRowsFallback;
+ const enableVirtualization = rawEnableVirtualization ?? true;
+ const direction = rawDirection ?? "ltr";
+ const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
+ const [scrollLeft, setScrollLeft] = (0, import_react5.useState)(0);
+ const [columnWidthsInternal, setColumnWidthsInternal] = (0, import_react5.useState)(() => columnWidthsRaw ?? /* @__PURE__ */ new Map());
+ const [isColumnResizing, setColumnResizing] = (0, import_react5.useState)(false);
+ const [isDragging, setDragging] = (0, import_react5.useState)(false);
+ const [draggedOverRowIdx, setDraggedOverRowIdx] = (0, import_react5.useState)(void 0);
+ const [scrollToPosition, setScrollToPosition] = (0, import_react5.useState)(null);
+ const [shouldFocusCell, setShouldFocusCell] = (0, import_react5.useState)(false);
+ const [previousRowIdx, setPreviousRowIdx] = (0, import_react5.useState)(-1);
+ const isColumnWidthsControlled = columnWidthsRaw != null && onColumnWidthsChangeRaw != null && !isColumnResizing;
+ const columnWidths = isColumnWidthsControlled ? columnWidthsRaw : columnWidthsInternal;
+ const onColumnWidthsChange = isColumnWidthsControlled ? (columnWidths$1) => {
+ setColumnWidthsInternal(columnWidths$1);
+ onColumnWidthsChangeRaw(columnWidths$1);
+ } : setColumnWidthsInternal;
+ const getColumnWidth = (0, import_react5.useCallback)((column) => {
+ return columnWidths.get(column.key)?.width ?? column.width;
+ }, [columnWidths]);
+ const [gridRef, gridWidth, gridHeight, horizontalScrollbarHeight] = useGridDimensions();
+ const { columns, colSpanColumns, lastFrozenColumnIndex, headerRowsCount, colOverscanStartIdx, colOverscanEndIdx, templateColumns, layoutCssVars, totalFrozenColumnWidth } = useCalculatedColumns({
+ rawColumns,
+ defaultColumnOptions,
+ getColumnWidth,
+ scrollLeft,
+ viewportWidth: gridWidth,
+ enableVirtualization
+ });
+ const topSummaryRowsCount = topSummaryRows?.length ?? 0;
+ const bottomSummaryRowsCount = bottomSummaryRows?.length ?? 0;
+ const summaryRowsCount = topSummaryRowsCount + bottomSummaryRowsCount;
+ const headerAndTopSummaryRowsCount = headerRowsCount + topSummaryRowsCount;
+ const groupedColumnHeaderRowsCount = headerRowsCount - 1;
+ const minRowIdx = -headerAndTopSummaryRowsCount;
+ const mainHeaderRowIdx = minRowIdx + groupedColumnHeaderRowsCount;
+ const maxRowIdx = rows.length + bottomSummaryRowsCount - 1;
+ const [selectedPosition, setSelectedPosition] = (0, import_react5.useState)(() => ({
+ idx: -1,
+ rowIdx: minRowIdx - 1,
+ mode: "SELECT"
+ }));
+ const focusSinkRef = (0, import_react5.useRef)(null);
+ const isTreeGrid = role === "treegrid";
+ const headerRowsHeight = headerRowsCount * headerRowHeight;
+ const summaryRowsHeight = summaryRowsCount * summaryRowHeight;
+ const clientHeight = gridHeight - headerRowsHeight - summaryRowsHeight;
+ const isSelectable = selectedRows != null && onSelectedRowsChange != null;
+ const { leftKey, rightKey } = getLeftRightKey(direction);
+ const ariaRowCount = rawAriaRowCount ?? headerRowsCount + rows.length + summaryRowsCount;
+ const defaultGridComponents = (0, import_react5.useMemo)(() => ({
+ renderCheckbox: renderCheckbox$1,
+ renderSortStatus: renderSortStatus$1,
+ renderCell
+ }), [
+ renderCheckbox$1,
+ renderSortStatus$1,
+ renderCell
+ ]);
+ const headerSelectionValue = (0, import_react5.useMemo)(() => {
+ let hasSelectedRow = false;
+ let hasUnselectedRow = false;
+ if (rowKeyGetter != null && selectedRows != null && selectedRows.size > 0) for (const row$1 of rows) {
+ if (selectedRows.has(rowKeyGetter(row$1))) hasSelectedRow = true;
+ else hasUnselectedRow = true;
+ if (hasSelectedRow && hasUnselectedRow) break;
+ }
+ return {
+ isRowSelected: hasSelectedRow && !hasUnselectedRow,
+ isIndeterminate: hasSelectedRow && hasUnselectedRow
+ };
+ }, [
+ rows,
+ selectedRows,
+ rowKeyGetter
+ ]);
+ const { rowOverscanStartIdx, rowOverscanEndIdx, totalRowHeight, gridTemplateRows, getRowTop, getRowHeight, findRowIdx } = useViewportRows({
+ rows,
+ rowHeight,
+ clientHeight,
+ scrollTop,
+ enableVirtualization
+ });
+ const viewportColumns = useViewportColumns({
+ columns,
+ colSpanColumns,
+ colOverscanStartIdx,
+ colOverscanEndIdx,
+ lastFrozenColumnIndex,
+ rowOverscanStartIdx,
+ rowOverscanEndIdx,
+ rows,
+ topSummaryRows,
+ bottomSummaryRows
+ });
+ const { gridTemplateColumns, handleColumnResize } = useColumnWidths(columns, viewportColumns, templateColumns, gridRef, gridWidth, columnWidths, onColumnWidthsChange, onColumnResize, setColumnResizing);
+ const minColIdx = isTreeGrid ? -1 : 0;
+ const maxColIdx = columns.length - 1;
+ const selectedCellIsWithinSelectionBounds = isCellWithinSelectionBounds(selectedPosition);
+ const selectedCellIsWithinViewportBounds = isCellWithinViewportBounds(selectedPosition);
+ const scrollHeight = headerRowHeight + totalRowHeight + summaryRowsHeight + horizontalScrollbarHeight;
+ const handleColumnResizeLatest = useLatestFunc(handleColumnResize);
+ const handleColumnResizeEndLatest = useLatestFunc(handleColumnResizeEnd);
+ const onColumnsReorderLastest = useLatestFunc(onColumnsReorder);
+ const onSortColumnsChangeLatest = useLatestFunc(onSortColumnsChange);
+ const onCellMouseDownLatest = useLatestFunc(onCellMouseDown);
+ const onCellClickLatest = useLatestFunc(onCellClick);
+ const onCellDoubleClickLatest = useLatestFunc(onCellDoubleClick);
+ const onCellContextMenuLatest = useLatestFunc(onCellContextMenu);
+ const selectHeaderRowLatest = useLatestFunc(selectHeaderRow);
+ const selectRowLatest = useLatestFunc(selectRow);
+ const handleFormatterRowChangeLatest = useLatestFunc(updateRow);
+ const selectCellLatest = useLatestFunc(selectCell);
+ const selectHeaderCellLatest = useLatestFunc(selectHeaderCell);
+ const focusCell = (0, import_react5.useCallback)((shouldScroll = true) => {
+ const cell$1 = getCellToScroll(gridRef.current);
+ if (cell$1 === null) return;
+ if (shouldScroll) scrollIntoView(cell$1);
+ cell$1.focus({ preventScroll: true });
+ }, [gridRef]);
+ (0, import_react5.useLayoutEffect)(() => {
+ if (shouldFocusCell) {
+ if (focusSinkRef.current !== null && selectedPosition.idx === -1) {
+ focusSinkRef.current.focus({ preventScroll: true });
+ scrollIntoView(focusSinkRef.current);
+ } else focusCell();
+ setShouldFocusCell(false);
+ }
+ }, [
+ shouldFocusCell,
+ focusCell,
+ selectedPosition.idx
+ ]);
+ (0, import_react5.useImperativeHandle)(ref, () => ({
+ element: gridRef.current,
+ scrollToCell({ idx, rowIdx }) {
+ const scrollToIdx = idx !== void 0 && idx > lastFrozenColumnIndex && idx < columns.length ? idx : void 0;
+ const scrollToRowIdx = rowIdx !== void 0 && isRowIdxWithinViewportBounds(rowIdx) ? rowIdx : void 0;
+ if (scrollToIdx !== void 0 || scrollToRowIdx !== void 0) setScrollToPosition({
+ idx: scrollToIdx,
+ rowIdx: scrollToRowIdx
+ });
+ },
+ selectCell
+ }));
+ function selectHeaderRow(args) {
+ if (!onSelectedRowsChange) return;
+ assertIsValidKeyGetter(rowKeyGetter);
+ const newSelectedRows = new Set(selectedRows);
+ for (const row$1 of rows) {
+ if (isRowSelectionDisabled?.(row$1) === true) continue;
+ const rowKey = rowKeyGetter(row$1);
+ if (args.checked) newSelectedRows.add(rowKey);
+ else newSelectedRows.delete(rowKey);
+ }
+ onSelectedRowsChange(newSelectedRows);
+ }
+ function selectRow(args) {
+ if (!onSelectedRowsChange) return;
+ assertIsValidKeyGetter(rowKeyGetter);
+ const { row: row$1, checked, isShiftClick } = args;
+ if (isRowSelectionDisabled?.(row$1) === true) return;
+ const newSelectedRows = new Set(selectedRows);
+ const rowKey = rowKeyGetter(row$1);
+ const rowIdx = rows.indexOf(row$1);
+ setPreviousRowIdx(rowIdx);
+ if (checked) newSelectedRows.add(rowKey);
+ else newSelectedRows.delete(rowKey);
+ if (isShiftClick && previousRowIdx !== -1 && previousRowIdx !== rowIdx && previousRowIdx < rows.length) {
+ const step = sign(rowIdx - previousRowIdx);
+ for (let i = previousRowIdx + step; i !== rowIdx; i += step) {
+ const row$2 = rows[i];
+ if (isRowSelectionDisabled?.(row$2) === true) continue;
+ if (checked) newSelectedRows.add(rowKeyGetter(row$2));
+ else newSelectedRows.delete(rowKeyGetter(row$2));
+ }
+ }
+ onSelectedRowsChange(newSelectedRows);
+ }
+ function handleKeyDown(event) {
+ const { idx, rowIdx, mode } = selectedPosition;
+ if (mode === "EDIT") return;
+ if (onCellKeyDown && isRowIdxWithinViewportBounds(rowIdx)) {
+ const row$1 = rows[rowIdx];
+ const cellEvent = createCellEvent(event);
+ onCellKeyDown({
+ mode: "SELECT",
+ row: row$1,
+ column: columns[idx],
+ rowIdx,
+ selectCell
+ }, cellEvent);
+ if (cellEvent.isGridDefaultPrevented()) return;
+ }
+ if (!(event.target instanceof Element)) return;
+ const isCellEvent = event.target.closest(".rdg-cell") !== null;
+ const isRowEvent = isTreeGrid && event.target === focusSinkRef.current;
+ if (!isCellEvent && !isRowEvent) return;
+ switch (event.key) {
+ case "ArrowUp":
+ case "ArrowDown":
+ case "ArrowLeft":
+ case "ArrowRight":
+ case "Tab":
+ case "Home":
+ case "End":
+ case "PageUp":
+ case "PageDown":
+ navigate(event);
+ break;
+ default:
+ handleCellInput(event);
+ break;
+ }
+ }
+ function handleScroll(event) {
+ const { scrollTop: scrollTop$1, scrollLeft: scrollLeft$1 } = event.currentTarget;
+ (0, import_react_dom.flushSync)(() => {
+ setScrollTop(scrollTop$1);
+ setScrollLeft(abs(scrollLeft$1));
+ });
+ onScroll?.(event);
+ }
+ function updateRow(column, rowIdx, row$1) {
+ if (typeof onRowsChange !== "function") return;
+ if (row$1 === rows[rowIdx]) return;
+ onRowsChange(rows.with(rowIdx, row$1), {
+ indexes: [rowIdx],
+ column
+ });
+ }
+ function commitEditorChanges() {
+ if (selectedPosition.mode !== "EDIT") return;
+ updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row);
+ }
+ function handleCellCopy(event) {
+ if (!selectedCellIsWithinViewportBounds) return;
+ const { idx, rowIdx } = selectedPosition;
+ onCellCopy?.({
+ row: rows[rowIdx],
+ column: columns[idx]
+ }, event);
+ }
+ function handleCellPaste(event) {
+ if (!onCellPaste || !onRowsChange || !isCellEditable(selectedPosition)) return;
+ const { idx, rowIdx } = selectedPosition;
+ const column = columns[idx];
+ updateRow(column, rowIdx, onCellPaste({
+ row: rows[rowIdx],
+ column
+ }, event));
+ }
+ function handleCellInput(event) {
+ if (!selectedCellIsWithinViewportBounds) return;
+ const row$1 = rows[selectedPosition.rowIdx];
+ const { key, shiftKey } = event;
+ if (isSelectable && shiftKey && key === " ") {
+ assertIsValidKeyGetter(rowKeyGetter);
+ const rowKey = rowKeyGetter(row$1);
+ selectRow({
+ row: row$1,
+ checked: !selectedRows.has(rowKey),
+ isShiftClick: false
+ });
+ event.preventDefault();
+ return;
+ }
+ if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onCellPaste != null)) setSelectedPosition(({ idx, rowIdx }) => ({
+ idx,
+ rowIdx,
+ mode: "EDIT",
+ row: row$1,
+ originalRow: row$1
+ }));
+ }
+ function handleColumnResizeEnd() {
+ if (isColumnResizing) {
+ onColumnWidthsChangeRaw?.(columnWidths);
+ setColumnResizing(false);
+ }
+ }
+ function handleDragHandlePointerDown(event) {
+ event.preventDefault();
+ if (event.pointerType === "mouse" && event.buttons !== 1) return;
+ setDragging(true);
+ event.currentTarget.setPointerCapture(event.pointerId);
+ }
+ function handleDragHandlePointerMove(event) {
+ const gridEl = gridRef.current;
+ const overRowIdx = findRowIdx(scrollTop - (headerRowsHeight + topSummaryRowsCount * summaryRowHeight) + event.clientY - gridEl.getBoundingClientRect().top);
+ setDraggedOverRowIdx(overRowIdx);
+ const ariaRowIndex = headerAndTopSummaryRowsCount + overRowIdx + 1;
+ scrollIntoView(gridEl.querySelector(`:scope > [aria-rowindex="${ariaRowIndex}"] > [aria-colindex="${selectedPosition.idx + 1}"]`));
+ }
+ function handleDragHandleLostPointerCapture() {
+ setDragging(false);
+ if (draggedOverRowIdx === void 0) return;
+ const { rowIdx } = selectedPosition;
+ const [startRowIndex, endRowIndex] = rowIdx < draggedOverRowIdx ? [rowIdx + 1, draggedOverRowIdx + 1] : [draggedOverRowIdx, rowIdx];
+ updateRows(startRowIndex, endRowIndex);
+ setDraggedOverRowIdx(void 0);
+ }
+ function handleDragHandleClick() {
+ focusCell(false);
+ }
+ function handleDragHandleDoubleClick(event) {
+ event.stopPropagation();
+ updateRows(selectedPosition.rowIdx + 1, rows.length);
+ }
+ function updateRows(startRowIdx, endRowIdx) {
+ if (onRowsChange == null) return;
+ const { rowIdx, idx } = selectedPosition;
+ const column = columns[idx];
+ const sourceRow = rows[rowIdx];
+ const updatedRows = [...rows];
+ const indexes = [];
+ for (let i = startRowIdx; i < endRowIdx; i++) if (isCellEditable({
+ rowIdx: i,
+ idx
+ })) {
+ const updatedRow = onFill({
+ columnKey: column.key,
+ sourceRow,
+ targetRow: rows[i]
+ });
+ if (updatedRow !== rows[i]) {
+ updatedRows[i] = updatedRow;
+ indexes.push(i);
+ }
+ }
+ if (indexes.length > 0) onRowsChange(updatedRows, {
+ indexes,
+ column
+ });
+ }
+ function isColIdxWithinSelectionBounds(idx) {
+ return idx >= minColIdx && idx <= maxColIdx;
+ }
+ function isRowIdxWithinViewportBounds(rowIdx) {
+ return rowIdx >= 0 && rowIdx < rows.length;
+ }
+ function isCellWithinSelectionBounds({ idx, rowIdx }) {
+ return rowIdx >= minRowIdx && rowIdx <= maxRowIdx && isColIdxWithinSelectionBounds(idx);
+ }
+ function isCellWithinEditBounds({ idx, rowIdx }) {
+ return isRowIdxWithinViewportBounds(rowIdx) && idx >= 0 && idx <= maxColIdx;
+ }
+ function isCellWithinViewportBounds({ idx, rowIdx }) {
+ return isRowIdxWithinViewportBounds(rowIdx) && isColIdxWithinSelectionBounds(idx);
+ }
+ function isCellEditable(position) {
+ return isCellWithinEditBounds(position) && isSelectedCellEditable({
+ columns,
+ rows,
+ selectedPosition: position
+ });
+ }
+ function selectCell(position, options) {
+ if (!isCellWithinSelectionBounds(position)) return;
+ commitEditorChanges();
+ const samePosition = isSamePosition(selectedPosition, position);
+ if (options?.enableEditor && isCellEditable(position)) {
+ const row$1 = rows[position.rowIdx];
+ setSelectedPosition({
+ ...position,
+ mode: "EDIT",
+ row: row$1,
+ originalRow: row$1
+ });
+ } else if (samePosition) scrollIntoView(getCellToScroll(gridRef.current));
+ else {
+ setShouldFocusCell(options?.shouldFocusCell === true);
+ setSelectedPosition({
+ ...position,
+ mode: "SELECT"
+ });
+ }
+ if (onSelectedCellChange && !samePosition) onSelectedCellChange({
+ rowIdx: position.rowIdx,
+ row: isRowIdxWithinViewportBounds(position.rowIdx) ? rows[position.rowIdx] : void 0,
+ column: columns[position.idx]
+ });
+ }
+ function selectHeaderCell({ idx, rowIdx }) {
+ selectCell({
+ rowIdx: minRowIdx + rowIdx - 1,
+ idx
+ });
+ }
+ function getNextPosition(key, ctrlKey, shiftKey) {
+ const { idx, rowIdx } = selectedPosition;
+ const isRowSelected = selectedCellIsWithinSelectionBounds && idx === -1;
+ switch (key) {
+ case "ArrowUp":
+ return {
+ idx,
+ rowIdx: rowIdx - 1
+ };
+ case "ArrowDown":
+ return {
+ idx,
+ rowIdx: rowIdx + 1
+ };
+ case leftKey:
+ return {
+ idx: idx - 1,
+ rowIdx
+ };
+ case rightKey:
+ return {
+ idx: idx + 1,
+ rowIdx
+ };
+ case "Tab":
+ return {
+ idx: idx + (shiftKey ? -1 : 1),
+ rowIdx
+ };
+ case "Home":
+ if (isRowSelected) return {
+ idx,
+ rowIdx: minRowIdx
+ };
+ return {
+ idx: 0,
+ rowIdx: ctrlKey ? minRowIdx : rowIdx
+ };
+ case "End":
+ if (isRowSelected) return {
+ idx,
+ rowIdx: maxRowIdx
+ };
+ return {
+ idx: maxColIdx,
+ rowIdx: ctrlKey ? maxRowIdx : rowIdx
+ };
+ case "PageUp": {
+ if (selectedPosition.rowIdx === minRowIdx) return selectedPosition;
+ const nextRowY = getRowTop(rowIdx) + getRowHeight(rowIdx) - clientHeight;
+ return {
+ idx,
+ rowIdx: nextRowY > 0 ? findRowIdx(nextRowY) : 0
+ };
+ }
+ case "PageDown": {
+ if (selectedPosition.rowIdx >= rows.length) return selectedPosition;
+ const nextRowY = getRowTop(rowIdx) + clientHeight;
+ return {
+ idx,
+ rowIdx: nextRowY < totalRowHeight ? findRowIdx(nextRowY) : rows.length - 1
+ };
+ }
+ default:
+ return selectedPosition;
+ }
+ }
+ function navigate(event) {
+ const { key, shiftKey } = event;
+ let cellNavigationMode = "NONE";
+ if (key === "Tab") {
+ if (canExitGrid({
+ shiftKey,
+ maxColIdx,
+ minRowIdx,
+ maxRowIdx,
+ selectedPosition
+ })) {
+ commitEditorChanges();
+ return;
+ }
+ cellNavigationMode = "CHANGE_ROW";
+ }
+ event.preventDefault();
+ const nextPosition = getNextPosition(key, isCtrlKeyHeldDown(event), shiftKey);
+ if (isSamePosition(selectedPosition, nextPosition)) return;
+ selectCell(getNextSelectedCellPosition({
+ moveUp: key === "ArrowUp",
+ moveNext: key === rightKey || key === "Tab" && !shiftKey,
+ columns,
+ colSpanColumns,
+ rows,
+ topSummaryRows,
+ bottomSummaryRows,
+ minRowIdx,
+ mainHeaderRowIdx,
+ maxRowIdx,
+ lastFrozenColumnIndex,
+ cellNavigationMode,
+ currentPosition: selectedPosition,
+ nextPosition,
+ isCellWithinBounds: isCellWithinSelectionBounds
+ }), { shouldFocusCell: true });
+ }
+ function getDraggedOverCellIdx(currentRowIdx) {
+ if (draggedOverRowIdx === void 0) return;
+ const { rowIdx } = selectedPosition;
+ return (rowIdx < draggedOverRowIdx ? rowIdx < currentRowIdx && currentRowIdx <= draggedOverRowIdx : rowIdx > currentRowIdx && currentRowIdx >= draggedOverRowIdx) ? selectedPosition.idx : void 0;
+ }
+ function getDragHandle() {
+ if (onFill == null || selectedPosition.mode === "EDIT" || !isCellWithinViewportBounds(selectedPosition)) return;
+ const { idx, rowIdx } = selectedPosition;
+ const column = columns[idx];
+ if (column.renderEditCell == null || column.editable === false) return;
+ const isLastRow = rowIdx === maxRowIdx;
+ const columnWidth = getColumnWidth(column);
+ const colSpan = column.colSpan?.({
+ type: "ROW",
+ row: rows[rowIdx]
+ }) ?? 1;
+ const { insetInlineStart, ...style$1 } = getCellStyle(column, colSpan);
+ const marginEnd = "calc(var(--rdg-drag-handle-size) * -0.5 + 1px)";
+ const isLastColumn = column.idx + colSpan - 1 === maxColIdx;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ style: {
+ ...style$1,
+ gridRowStart: headerAndTopSummaryRowsCount + rowIdx + 1,
+ marginInlineEnd: isLastColumn ? void 0 : marginEnd,
+ marginBlockEnd: isLastRow ? void 0 : marginEnd,
+ insetInlineStart: insetInlineStart ? `calc(${insetInlineStart} + ${columnWidth}px + var(--rdg-drag-handle-size) * -0.5 - 1px)` : void 0
+ },
+ className: classnames(cellDragHandleClassname, column.frozen && cellDragHandleFrozenClassname),
+ onPointerDown: handleDragHandlePointerDown,
+ onPointerMove: isDragging ? handleDragHandlePointerMove : void 0,
+ onLostPointerCapture: isDragging ? handleDragHandleLostPointerCapture : void 0,
+ onClick: handleDragHandleClick,
+ onDoubleClick: handleDragHandleDoubleClick
+ });
+ }
+ function getCellEditor(rowIdx) {
+ if (!isCellWithinViewportBounds(selectedPosition) || selectedPosition.rowIdx !== rowIdx || selectedPosition.mode === "SELECT") return;
+ const { idx, row: row$1 } = selectedPosition;
+ const column = columns[idx];
+ const colSpan = getColSpan(column, lastFrozenColumnIndex, {
+ type: "ROW",
+ row: row$1
+ });
+ const closeOnExternalRowChange = column.editorOptions?.closeOnExternalRowChange ?? true;
+ const closeEditor = (shouldFocusCell$1) => {
+ setShouldFocusCell(shouldFocusCell$1);
+ setSelectedPosition(({ idx: idx$1, rowIdx: rowIdx$1 }) => ({
+ idx: idx$1,
+ rowIdx: rowIdx$1,
+ mode: "SELECT"
+ }));
+ };
+ const onRowChange = (row$2, commitChanges, shouldFocusCell$1) => {
+ if (commitChanges) (0, import_react_dom.flushSync)(() => {
+ updateRow(column, selectedPosition.rowIdx, row$2);
+ closeEditor(shouldFocusCell$1);
+ });
+ else setSelectedPosition((position) => ({
+ ...position,
+ row: row$2
+ }));
+ };
+ if (closeOnExternalRowChange && rows[selectedPosition.rowIdx] !== selectedPosition.originalRow) closeEditor(false);
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(EditCell, {
+ column,
+ colSpan,
+ row: row$1,
+ rowIdx,
+ onRowChange,
+ closeEditor,
+ onKeyDown: onCellKeyDown,
+ navigate
+ }, column.key);
+ }
+ function getRowViewportColumns(rowIdx) {
+ const selectedColumn = selectedPosition.idx === -1 ? void 0 : columns[selectedPosition.idx];
+ if (selectedColumn !== void 0 && selectedPosition.rowIdx === rowIdx && !viewportColumns.includes(selectedColumn)) return selectedPosition.idx > colOverscanEndIdx ? [...viewportColumns, selectedColumn] : [
+ ...viewportColumns.slice(0, lastFrozenColumnIndex + 1),
+ selectedColumn,
+ ...viewportColumns.slice(lastFrozenColumnIndex + 1)
+ ];
+ return viewportColumns;
+ }
+ function getViewportRows() {
+ const rowElements = [];
+ const { idx: selectedIdx, rowIdx: selectedRowIdx } = selectedPosition;
+ const startRowIdx = selectedCellIsWithinViewportBounds && selectedRowIdx < rowOverscanStartIdx ? rowOverscanStartIdx - 1 : rowOverscanStartIdx;
+ const endRowIdx = selectedCellIsWithinViewportBounds && selectedRowIdx > rowOverscanEndIdx ? rowOverscanEndIdx + 1 : rowOverscanEndIdx;
+ for (let viewportRowIdx = startRowIdx; viewportRowIdx <= endRowIdx; viewportRowIdx++) {
+ const isRowOutsideViewport = viewportRowIdx === rowOverscanStartIdx - 1 || viewportRowIdx === rowOverscanEndIdx + 1;
+ const rowIdx = isRowOutsideViewport ? selectedRowIdx : viewportRowIdx;
+ let rowColumns = viewportColumns;
+ const selectedColumn = selectedIdx === -1 ? void 0 : columns[selectedIdx];
+ if (selectedColumn !== void 0) if (isRowOutsideViewport) rowColumns = [selectedColumn];
+ else rowColumns = getRowViewportColumns(rowIdx);
+ const row$1 = rows[rowIdx];
+ const gridRowStart = headerAndTopSummaryRowsCount + rowIdx + 1;
+ let key = rowIdx;
+ let isRowSelected = false;
+ if (typeof rowKeyGetter === "function") {
+ key = rowKeyGetter(row$1);
+ isRowSelected = selectedRows?.has(key) ?? false;
+ }
+ rowElements.push(renderRow(key, {
+ "aria-rowindex": headerAndTopSummaryRowsCount + rowIdx + 1,
+ "aria-selected": isSelectable ? isRowSelected : void 0,
+ rowIdx,
+ row: row$1,
+ viewportColumns: rowColumns,
+ isRowSelectionDisabled: isRowSelectionDisabled?.(row$1) ?? false,
+ isRowSelected,
+ onCellMouseDown: onCellMouseDownLatest,
+ onCellClick: onCellClickLatest,
+ onCellDoubleClick: onCellDoubleClickLatest,
+ onCellContextMenu: onCellContextMenuLatest,
+ rowClass,
+ gridRowStart,
+ selectedCellIdx: selectedRowIdx === rowIdx ? selectedIdx : void 0,
+ draggedOverCellIdx: getDraggedOverCellIdx(rowIdx),
+ lastFrozenColumnIndex,
+ onRowChange: handleFormatterRowChangeLatest,
+ selectCell: selectCellLatest,
+ selectedCellEditor: getCellEditor(rowIdx)
+ }));
+ }
+ return rowElements;
+ }
+ if (selectedPosition.idx > maxColIdx || selectedPosition.rowIdx > maxRowIdx) {
+ setSelectedPosition({
+ idx: -1,
+ rowIdx: minRowIdx - 1,
+ mode: "SELECT"
+ });
+ setDraggedOverRowIdx(void 0);
+ }
+ if (isColumnWidthsControlled && columnWidthsInternal !== columnWidthsRaw) setColumnWidthsInternal(columnWidthsRaw);
+ let templateRows = `repeat(${headerRowsCount}, ${headerRowHeight}px)`;
+ if (topSummaryRowsCount > 0) templateRows += ` repeat(${topSummaryRowsCount}, ${summaryRowHeight}px)`;
+ if (rows.length > 0) templateRows += gridTemplateRows;
+ if (bottomSummaryRowsCount > 0) templateRows += ` repeat(${bottomSummaryRowsCount}, ${summaryRowHeight}px)`;
+ const isGroupRowFocused = selectedPosition.idx === -1 && selectedPosition.rowIdx !== minRowIdx - 1;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", {
+ role,
+ "aria-label": ariaLabel,
+ "aria-labelledby": ariaLabelledBy,
+ "aria-description": ariaDescription,
+ "aria-describedby": ariaDescribedBy,
+ "aria-multiselectable": isSelectable ? true : void 0,
+ "aria-colcount": columns.length,
+ "aria-rowcount": ariaRowCount,
+ tabIndex: -1,
+ className: classnames(rootClassname, { [viewportDraggingClassname]: isDragging }, className),
+ style: {
+ ...style,
+ scrollPaddingInlineStart: selectedPosition.idx > lastFrozenColumnIndex || scrollToPosition?.idx !== void 0 ? `${totalFrozenColumnWidth}px` : void 0,
+ scrollPaddingBlock: isRowIdxWithinViewportBounds(selectedPosition.rowIdx) || scrollToPosition?.rowIdx !== void 0 ? `${headerRowsHeight + topSummaryRowsCount * summaryRowHeight}px ${bottomSummaryRowsCount * summaryRowHeight}px` : void 0,
+ gridTemplateColumns,
+ gridTemplateRows: templateRows,
+ "--rdg-header-row-height": `${headerRowHeight}px`,
+ "--rdg-scroll-height": `${scrollHeight}px`,
+ ...layoutCssVars
+ },
+ dir: direction,
+ ref: gridRef,
+ onScroll: handleScroll,
+ onKeyDown: handleKeyDown,
+ onCopy: handleCellCopy,
+ onPaste: handleCellPaste,
+ "data-testid": testId,
+ "data-cy": dataCy,
+ children: [
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(DataGridDefaultRenderersContext, {
+ value: defaultGridComponents,
+ children: [/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HeaderRowSelectionChangeContext, {
+ value: selectHeaderRowLatest,
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(HeaderRowSelectionContext, {
+ value: headerSelectionValue,
+ children: [Array.from({ length: groupedColumnHeaderRowsCount }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GroupedColumnHeaderRow_default, {
+ rowIdx: index + 1,
+ level: -groupedColumnHeaderRowsCount + index,
+ columns: getRowViewportColumns(minRowIdx + index),
+ selectedCellIdx: selectedPosition.rowIdx === minRowIdx + index ? selectedPosition.idx : void 0,
+ selectCell: selectHeaderCellLatest
+ }, index)), /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HeaderRow_default, {
+ headerRowClass,
+ rowIdx: headerRowsCount,
+ columns: getRowViewportColumns(mainHeaderRowIdx),
+ onColumnResize: handleColumnResizeLatest,
+ onColumnResizeEnd: handleColumnResizeEndLatest,
+ onColumnsReorder: onColumnsReorderLastest,
+ sortColumns,
+ onSortColumnsChange: onSortColumnsChangeLatest,
+ lastFrozenColumnIndex,
+ selectedCellIdx: selectedPosition.rowIdx === mainHeaderRowIdx ? selectedPosition.idx : void 0,
+ selectCell: selectHeaderCellLatest,
+ shouldFocusGrid: !selectedCellIsWithinSelectionBounds,
+ direction
+ })]
+ })
+ }), rows.length === 0 && noRowsFallback ? noRowsFallback : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
+ topSummaryRows?.map((row$1, rowIdx) => {
+ const gridRowStart = headerRowsCount + 1 + rowIdx;
+ const summaryRowIdx = mainHeaderRowIdx + 1 + rowIdx;
+ const isSummaryRowSelected = selectedPosition.rowIdx === summaryRowIdx;
+ const top = headerRowsHeight + summaryRowHeight * rowIdx;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SummaryRow_default, {
+ "aria-rowindex": gridRowStart,
+ rowIdx: summaryRowIdx,
+ gridRowStart,
+ row: row$1,
+ top,
+ bottom: void 0,
+ viewportColumns: getRowViewportColumns(summaryRowIdx),
+ lastFrozenColumnIndex,
+ selectedCellIdx: isSummaryRowSelected ? selectedPosition.idx : void 0,
+ isTop: true,
+ selectCell: selectCellLatest
+ }, rowIdx);
+ }),
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RowSelectionChangeContext, {
+ value: selectRowLatest,
+ children: getViewportRows()
+ }),
+ bottomSummaryRows?.map((row$1, rowIdx) => {
+ const gridRowStart = headerAndTopSummaryRowsCount + rows.length + rowIdx + 1;
+ const summaryRowIdx = rows.length + rowIdx;
+ const isSummaryRowSelected = selectedPosition.rowIdx === summaryRowIdx;
+ const top = clientHeight > totalRowHeight ? gridHeight - summaryRowHeight * (bottomSummaryRows.length - rowIdx) : void 0;
+ const bottom = top === void 0 ? summaryRowHeight * (bottomSummaryRows.length - 1 - rowIdx) : void 0;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SummaryRow_default, {
+ "aria-rowindex": ariaRowCount - bottomSummaryRowsCount + rowIdx + 1,
+ rowIdx: summaryRowIdx,
+ gridRowStart,
+ row: row$1,
+ top,
+ bottom,
+ viewportColumns: getRowViewportColumns(summaryRowIdx),
+ lastFrozenColumnIndex,
+ selectedCellIdx: isSummaryRowSelected ? selectedPosition.idx : void 0,
+ isTop: false,
+ selectCell: selectCellLatest
+ }, rowIdx);
+ })
+ ] })]
+ }),
+ getDragHandle(),
+ renderMeasuringCells(viewportColumns),
+ isTreeGrid && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ ref: focusSinkRef,
+ tabIndex: isGroupRowFocused ? 0 : -1,
+ className: classnames(focusSinkClassname, {
+ [focusSinkHeaderAndSummaryClassname]: !isRowIdxWithinViewportBounds(selectedPosition.rowIdx),
+ [rowSelected]: isGroupRowFocused,
+ [rowSelectedWithFrozenCell]: isGroupRowFocused && lastFrozenColumnIndex !== -1
+ }),
+ style: { gridRowStart: selectedPosition.rowIdx + headerAndTopSummaryRowsCount + 1 }
+ }),
+ scrollToPosition !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScrollToCell, {
+ scrollToPosition,
+ setScrollToCellPosition: setScrollToPosition,
+ gridRef
+ })
+ ]
+ });
+}
+function getCellToScroll(gridEl) {
+ return gridEl.querySelector(':scope > [role="row"] > [tabindex="0"]');
+}
+function isSamePosition(p1, p2) {
+ return p1.idx === p2.idx && p1.rowIdx === p2.rowIdx;
+}
+function GroupCell({ id, groupKey, childRows, isExpanded, isCellSelected, column, row: row$1, groupColumnIndex, isGroupByColumn, toggleGroup: toggleGroupWrapper }) {
+ const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);
+ function toggleGroup() {
+ toggleGroupWrapper(id);
+ }
+ const isLevelMatching = isGroupByColumn && groupColumnIndex === column.idx;
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "gridcell",
+ "aria-colindex": column.idx + 1,
+ "aria-selected": isCellSelected,
+ tabIndex,
+ className: getCellClassname(column),
+ style: {
+ ...getCellStyle(column),
+ cursor: isLevelMatching ? "pointer" : "default"
+ },
+ onMouseDown: (event) => {
+ event.preventDefault();
+ },
+ onClick: isLevelMatching ? toggleGroup : void 0,
+ onFocus,
+ children: (!isGroupByColumn || isLevelMatching) && column.renderGroupCell?.({
+ groupKey,
+ childRows,
+ column,
+ row: row$1,
+ isExpanded,
+ tabIndex: childTabIndex,
+ toggleGroup
+ })
+ }, column.key);
+}
+var GroupCell_default = (0, import_react5.memo)(GroupCell);
+var groupRowClassname = `rdg-group-row rdg-7-0-0-beta-58-e74a2be3`;
+function GroupedRow({ className, row: row$1, rowIdx, viewportColumns, selectedCellIdx, isRowSelected, selectCell, gridRowStart, groupBy, toggleGroup, isRowSelectionDisabled, ...props }) {
+ const idx = viewportColumns[0].key === SELECT_COLUMN_KEY ? row$1.level + 1 : row$1.level;
+ function handleSelectGroup() {
+ selectCell({
+ rowIdx,
+ idx: -1
+ }, { shouldFocusCell: true });
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RowSelectionContext, {
+ value: (0, import_react5.useMemo)(() => ({
+ isRowSelectionDisabled: false,
+ isRowSelected
+ }), [isRowSelected]),
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {
+ role: "row",
+ "aria-level": row$1.level + 1,
+ "aria-setsize": row$1.setSize,
+ "aria-posinset": row$1.posInSet + 1,
+ "aria-expanded": row$1.isExpanded,
+ className: classnames(rowClassname, groupRowClassname, `rdg-row-${rowIdx % 2 === 0 ? "even" : "odd"}`, selectedCellIdx === -1 && rowSelectedClassname, className),
+ onMouseDown: handleSelectGroup,
+ style: getRowStyle(gridRowStart),
+ ...props,
+ children: viewportColumns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(GroupCell_default, {
+ id: row$1.id,
+ groupKey: row$1.groupKey,
+ childRows: row$1.childRows,
+ isExpanded: row$1.isExpanded,
+ isCellSelected: selectedCellIdx === column.idx,
+ column,
+ row: row$1,
+ groupColumnIndex: idx,
+ toggleGroup,
+ isGroupByColumn: groupBy.includes(column.key)
+ }, column.key))
+ })
+ });
+}
+var GroupRow_default = (0, import_react5.memo)(GroupedRow);
+var textEditorInternalClassname = "rdg-7-0-0-beta-58-2f8db206";
+var textEditorClassname = `rdg-text-editor ${textEditorInternalClassname}`;
+
+// src/utils/validateRows.ts
+var import_yup = require("yup");
+async function validateRows(opts) {
+ const { rows, fields, schema, rowHook } = opts;
+ const uniqueFields = fields.filter((f) => f.unique);
+ const out = [];
+ for (let i = 0; i < rows.length; i++) {
+ let values = { ...rows[i] };
+ const errors = {};
+ if (schema) {
+ try {
+ await schema.validate(values, { abortEarly: false });
+ } catch (e) {
+ if (e instanceof import_yup.ValidationError) {
+ for (const inner of e.inner.length ? e.inner : [e]) {
+ const path = inner.path;
+ if (!path) continue;
+ if (errors[path]) continue;
+ errors[path] = { message: inner.message, level: "error" };
+ }
+ } else {
+ throw e;
+ }
+ }
+ }
+ if (rowHook) {
+ const addError = (k, err) => {
+ errors[k] = err;
+ };
+ values = rowHook(values, addError, rows) ?? values;
+ }
+ out.push({
+ ...values,
+ __index: String(i),
+ __errors: Object.keys(errors).length ? errors : void 0
+ });
+ }
+ if (uniqueFields.length) {
+ for (const f of uniqueFields) {
+ const seen = /* @__PURE__ */ new Map();
+ out.forEach((r, idx) => {
+ const v = r[f.key];
+ if (v === void 0 || v === "") return;
+ const prev = seen.get(v);
+ if (prev !== void 0) {
+ const msg = f.uniqueErrorMessage ?? `${f.label} must be unique`;
+ markError(out[prev], f.key, msg);
+ markError(out[idx], f.key, msg);
+ } else {
+ seen.set(v, idx);
+ }
+ });
+ }
+ }
+ return out;
+}
+function markError(row2, key, message) {
+ const existing = row2.__errors ?? {};
+ existing[key] = { message, level: "error" };
+ row2.__errors = existing;
+}
+function rowHasErrors(row2) {
+ if (!row2.__errors) return false;
+ for (const k in row2.__errors) {
+ if (row2.__errors[k]?.level === "error") return true;
+ }
+ return false;
+}
+
+// src/steps/ValidationStep.tsx
+var import_jsx_runtime7 = require("react/jsx-runtime");
+function SelectHeaderCell(props) {
+ const { isIndeterminate, isRowSelected, onRowSelectionChange } = useHeaderRowSelection();
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ "input",
+ {
+ type: "checkbox",
+ className: "form-check-input m-0",
+ tabIndex: props.tabIndex,
+ ref: (el) => {
+ if (el) el.indeterminate = isIndeterminate;
+ },
+ checked: isRowSelected,
+ onChange: (e) => onRowSelectionChange({ checked: isIndeterminate ? false : e.target.checked })
+ }
+ ) });
+}
+function SelectRowCell(props) {
+ const { isRowSelected, onRowSelectionChange } = useRowSelection();
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", height: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ "input",
+ {
+ type: "checkbox",
+ className: "form-check-input m-0",
+ tabIndex: props.tabIndex,
+ checked: isRowSelected,
+ onChange: (e) => onRowSelectionChange({ row: props.row, checked: e.target.checked, isShiftClick: false })
+ }
+ ) });
+}
+var CustomSelectColumn = {
+ key: SELECT_COLUMN_KEY,
+ name: "",
+ width: 40,
+ minWidth: 40,
+ maxWidth: 40,
+ resizable: false,
+ sortable: false,
+ frozen: true,
+ renderHeaderCell: (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectHeaderCell, { tabIndex: props.tabIndex }),
+ renderCell: (props) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SelectRowCell, { tabIndex: props.tabIndex, row: props.row })
+};
+function ErrorCell({ className, message, children }) {
+ const ref = (0, import_react6.useRef)(null);
+ const [show, setShow] = (0, import_react6.useState)(false);
+ const [pos, setPos] = (0, import_react6.useState)({ top: 0, left: 0 });
+ function handleEnter() {
+ if (ref.current) {
+ const rect = ref.current.getBoundingClientRect();
+ setPos({ top: rect.top - 6, left: rect.left + rect.width / 2 });
+ }
+ setShow(true);
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ "div",
+ {
+ ref,
+ className,
+ style: { width: "100%", height: "100%", display: "flex", alignItems: "center" },
+ onMouseEnter: handleEnter,
+ onMouseLeave: () => setShow(false),
+ children
+ }
+ ),
+ show && (0, import_react_dom2.createPortal)(
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "rsi-tooltip-fixed", style: { position: "fixed", top: pos.top, left: pos.left, transform: "translate(-50%, -100%)", zIndex: 9999 }, children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rsi-tooltip-inner", children: message }),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rsi-tooltip-arrow" })
+ ] }),
+ document.body
+ )
+ ] });
+}
+function ValidationStep({
+ fields,
+ initialRows,
+ schema,
+ rowHook,
+ allowInvalidSubmit,
+ translations,
+ alertTranslations,
+ onBack,
+ onSubmit,
+ showTitle = true
+}) {
+ const [rows, setRows] = (0, import_react6.useState)([]);
+ const [selected, setSelected] = (0, import_react6.useState)(/* @__PURE__ */ new Set());
+ const [filterErrors, setFilterErrors] = (0, import_react6.useState)(false);
+ const [submitting, setSubmitting] = (0, import_react6.useState)(false);
+ const [showConfirm, setShowConfirm] = (0, import_react6.useState)(false);
+ const [loading, setLoading] = (0, import_react6.useState)(true);
+ (0, import_react6.useEffect)(() => {
+ let cancelled = false;
+ setLoading(true);
+ validateRows({ rows: initialRows, fields, schema, rowHook }).then((res) => {
+ if (!cancelled) {
+ setRows(res);
+ setLoading(false);
+ }
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [initialRows, fields, schema, rowHook]);
+ const columns = (0, import_react6.useMemo)(() => {
+ return [CustomSelectColumn, ...fields.map((f) => ({
+ key: f.key,
+ name: f.label,
+ editable: true,
+ resizable: true,
+ renderEditCell: ({ row: row2, onRowChange, onClose }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ "input",
+ {
+ autoFocus: true,
+ className: "form-control form-control-sm rsi-cell-edit",
+ value: row2[f.key] ?? "",
+ onChange: (e) => onRowChange({ ...row2, [f.key]: e.target.value }),
+ onBlur: () => onClose(true),
+ onKeyDown: (e) => {
+ if (e.key === "Enter") onClose(true);
+ if (e.key === "Escape") onClose(false);
+ }
+ }
+ ),
+ renderCell: ({ row: row2 }) => {
+ const value = row2[f.key];
+ const err = row2.__errors?.[f.key];
+ if (!err) {
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "100%", height: "100%", display: "flex", alignItems: "center" }, children: value ?? "" });
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorCell, { className: `rsi-cell-${err.level}`, message: err.message, children: value ?? "" });
+ }
+ }))];
+ }, [fields]);
+ async function revalidate(next) {
+ const stripped = next.map((r) => {
+ const { __index, __errors, ...rest } = r;
+ return rest;
+ });
+ const validated = await validateRows({ rows: stripped, fields, schema, rowHook });
+ setRows(validated);
+ }
+ const visibleRows = filterErrors ? rows.filter(rowHasErrors) : rows;
+ const errorCount = rows.filter(rowHasErrors).length;
+ function handleDiscard() {
+ setRows((prev) => prev.filter((r) => !selected.has(r.__index)));
+ setSelected(/* @__PURE__ */ new Set());
+ }
+ async function doSubmit() {
+ const valid = rows.filter((r) => !rowHasErrors(r));
+ const invalid = rows.filter(rowHasErrors);
+ const result = {
+ validData: valid.map(({ __index, __errors, ...rest }) => rest),
+ invalidData: invalid,
+ all: rows
+ };
+ setSubmitting(true);
+ try {
+ await onSubmit(result);
+ } finally {
+ setSubmitting(false);
+ }
+ }
+ function handleSubmit() {
+ if (errorCount > 0) {
+ setShowConfirm(true);
+ return;
+ }
+ void doSubmit();
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "d-flex flex-column gap-3", style: { minHeight: 400 }, children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "d-flex align-items-center justify-content-between", children: [
+ showTitle ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h5", { className: "m-0", children: translations.title }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", {}),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "d-flex align-items-center gap-3", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ import_react_bootstrap5.Form.Check,
+ {
+ type: "switch",
+ id: "rsi-filter-errors",
+ label: translations.filterSwitchTitle,
+ checked: filterErrors,
+ onChange: (e) => setFilterErrors(e.target.checked)
+ }
+ ),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_bootstrap5.Button, { variant: "outline-danger", size: "sm", disabled: selected.size === 0, onClick: handleDiscard, children: [
+ translations.discardButtonTitle,
+ " (",
+ selected.size,
+ ")"
+ ] })
+ ] })
+ ] }),
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "d-flex align-items-center justify-content-center flex-grow-1", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Spinner, { animation: "border" }) }) : visibleRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Alert, { variant: "info", className: "m-0", children: filterErrors ? translations.noRowsMessageWhenFiltered : translations.noRowsMessage }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "rsi-grid-wrapper", style: { flex: 1, minHeight: 320 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ DataGrid,
+ {
+ className: "rdg-light",
+ columns,
+ rows: visibleRows,
+ rowKeyGetter: (r) => r.__index,
+ selectedRows: selected,
+ onSelectedRowsChange: (rows2) => setSelected(rows2),
+ onRowsChange: (updated) => {
+ const updatedByIndex = new Map(updated.map((r) => [r.__index, r]));
+ const next = rows.map((r) => updatedByIndex.get(r.__index) ?? r);
+ setRows(next);
+ void revalidate(next);
+ },
+ style: { blockSize: "100%" }
+ }
+ ) }),
+ errorCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "text-danger small", children: [
+ errorCount,
+ " row",
+ errorCount === 1 ? "" : "s",
+ " with errors"
+ ] }),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "d-flex justify-content-between", children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Button, { variant: "outline-secondary", onClick: onBack, disabled: submitting, children: translations.backButtonTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Button, { variant: "primary", onClick: handleSubmit, disabled: submitting || !allowInvalidSubmit && errorCount > 0, children: submitting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Spinner, { size: "sm", animation: "border" }) : translations.submitButtonTitle })
+ ] }),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_bootstrap5.Modal, { show: showConfirm, onHide: () => setShowConfirm(false), centered: true, children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Modal.Header, { closeButton: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Modal.Title, { children: alertTranslations.headerTitle }) }),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Modal.Body, { children: allowInvalidSubmit ? alertTranslations.bodyText : alertTranslations.bodyTextSubmitForbidden }),
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_bootstrap5.Modal.Footer, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_bootstrap5.Button, { variant: "outline-secondary", onClick: () => setShowConfirm(false), children: alertTranslations.cancelButtonTitle }),
+ allowInvalidSubmit && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
+ import_react_bootstrap5.Button,
+ {
+ variant: "primary",
+ onClick: () => {
+ setShowConfirm(false);
+ void doSubmit();
+ },
+ children: alertTranslations.finishButtonTitle
+ }
+ )
+ ] })
+ ] })
+ ] });
+}
+
+// src/translations/defaultTranslations.ts
+var defaultTranslations = {
+ stepper: {
+ upload: "Upload Roster",
+ selectHeader: "Select Header Row",
+ matchColumns: "Map Columns",
+ submit: "Submit"
+ },
+ uploadStep: {
+ title: "Upload file",
+ manifestTitle: "Data that we expect:",
+ manifestDescription: "(You will have a chance to rename or remove columns in next steps)",
+ maxRecordsExceeded: (max2) => `Too many records. Up to ${max2} allowed`,
+ dropzone: {
+ title: "Upload .xlsx, .xls or .csv file",
+ errorToastDescription: "upload rejected",
+ activeDropzoneTitle: "Drop file here...",
+ buttonTitle: "Select file",
+ loadingTitle: "Processing..."
+ }
+ },
+ selectSheetStep: {
+ title: "Select the sheet to use",
+ nextButtonTitle: "Next",
+ backButtonTitle: "Back"
+ },
+ selectHeaderStep: {
+ title: "Select header row",
+ nextButtonTitle: "Next",
+ backButtonTitle: "Back"
+ },
+ matchColumnsStep: {
+ title: "Match Columns",
+ nextButtonTitle: "Next",
+ backButtonTitle: "Back",
+ userTableTitle: "Your table",
+ templateTitle: "Will become",
+ selectPlaceholder: "Select column...",
+ ignoredColumnText: "Column ignored",
+ subSelectPlaceholder: "Select...",
+ matchDropdownTitle: "Match",
+ unmatched: "Unmatched",
+ duplicateColumnWarningTitle: "Another column unselected",
+ duplicateColumnWarningDescription: "Columns cannot duplicate"
+ },
+ validationStep: {
+ title: "Validate data",
+ nextButtonTitle: "Confirm",
+ backButtonTitle: "Back",
+ noRowsMessage: "No data found",
+ noRowsMessageWhenFiltered: "No data containing errors",
+ discardButtonTitle: "Discard selected rows",
+ filterSwitchTitle: "Show only rows with errors",
+ submitButtonTitle: "Confirm"
+ },
+ alerts: {
+ confirmClose: {
+ headerTitle: "Exit import flow",
+ bodyText: "Are you sure? Your current information will not be saved.",
+ cancelButtonTitle: "Cancel",
+ exitButtonTitle: "Exit flow"
+ },
+ submitIncomplete: {
+ headerTitle: "Errors detected",
+ bodyText: "There are still some rows that contain errors. Rows with errors will be ignored when submitting.",
+ bodyTextSubmitForbidden: "There are still some rows containing errors.",
+ cancelButtonTitle: "Cancel",
+ finishButtonTitle: "Submit"
+ },
+ unmatchedRequiredFields: {
+ headerTitle: "Not all columns matched",
+ bodyText: "There are required columns that are not matched or ignored. Do you want to continue?",
+ cancelButtonTitle: "Cancel",
+ continueButtonTitle: "Continue"
+ },
+ toast: { error: "Error" }
+ }
+};
+function mergeTranslations(base, override) {
+ if (!override) return base;
+ const out = Array.isArray(base) ? [...base] : { ...base };
+ for (const k of Object.keys(override)) {
+ const ov = override[k];
+ const bv = base[k];
+ if (ov && typeof ov === "object" && !Array.isArray(ov) && bv && typeof bv === "object") {
+ out[k] = mergeTranslations(bv, ov);
+ } else if (ov !== void 0) {
+ out[k] = ov;
+ }
+ }
+ return out;
+}
+
+// src/ReactSpreadsheetImport.tsx
+var import_jsx_runtime8 = require("react/jsx-runtime");
+var INITIAL_STATE = {
+ step: "upload",
+ workbook: null,
+ sheetIndex: 0,
+ headerIndex: 0,
+ mapping: [],
+ mappedRows: [],
+ showCloseConfirm: false,
+ maxExceeded: false
+};
+function ReactSpreadsheetImport(props) {
+ const {
+ isOpen = true,
+ onClose,
+ onSubmit,
+ fields,
+ schema,
+ rowHook,
+ uploadStepHook,
+ selectHeaderStepHook,
+ matchColumnsStepHook,
+ maxRecords,
+ maxFileSize,
+ allowInvalidSubmit = true,
+ autoMapHeaders,
+ autoMapDistance,
+ translations: translationsOverride,
+ title,
+ inline = false,
+ hideStepper = false,
+ hideStepTitles = false
+ } = props;
+ const t = (0, import_react7.useMemo)(
+ () => mergeTranslations(defaultTranslations, translationsOverride),
+ [translationsOverride]
+ );
+ const [state, setState] = (0, import_react7.useState)(INITIAL_STATE);
+ (0, import_react7.useEffect)(() => {
+ if (!inline && isOpen) {
+ setState(INITIAL_STATE);
+ }
+ }, [isOpen, inline]);
+ const sheet = state.workbook?.sheets[state.sheetIndex];
+ const dataRows = sheet?.rows ?? [];
+ const headerRow2 = dataRows[state.headerIndex] ?? [];
+ const bodyRows = dataRows.slice(state.headerIndex + 1);
+ function tryClose() {
+ if (!onClose) return;
+ if (state.step === "upload") {
+ onClose();
+ } else {
+ setState((s) => ({ ...s, showCloseConfirm: true }));
+ }
+ }
+ async function handleUploaded(wb) {
+ if (wb.sheets.length > 1) {
+ setState((s) => ({ ...s, workbook: wb, step: "selectSheet" }));
+ return;
+ }
+ advanceFromSheet(wb, 0);
+ }
+ function advanceFromSheet(wb, idx) {
+ const rows = wb.sheets[idx]?.rows ?? [];
+ const exceeded = maxRecords !== void 0 && rows.length - 1 > maxRecords;
+ setState((s) => ({
+ ...s,
+ workbook: wb,
+ sheetIndex: idx,
+ step: "selectHeader",
+ maxExceeded: exceeded
+ }));
+ }
+ async function handleHeader(idx) {
+ let nextHeader = dataRows[idx] ?? [];
+ let nextBody = dataRows.slice(idx + 1);
+ if (selectHeaderStepHook) {
+ const r = await selectHeaderStepHook(nextHeader, nextBody);
+ nextHeader = r.headerValues;
+ nextBody = r.data;
+ }
+ if (state.workbook && state.workbook.sheets[state.sheetIndex]) {
+ const sheets = state.workbook.sheets.slice();
+ sheets[state.sheetIndex] = {
+ ...sheets[state.sheetIndex],
+ rows: [nextHeader, ...nextBody]
+ };
+ setState((s) => ({ ...s, workbook: { ...state.workbook, sheets }, headerIndex: 0, step: "matchColumns" }));
+ } else {
+ setState((s) => ({ ...s, headerIndex: idx, step: "matchColumns" }));
+ }
+ }
+ async function handleMatch(mapping) {
+ const mapped = bodyRows.map((row2) => {
+ const obj = {};
+ mapping.forEach((key, i) => {
+ if (key) obj[key] = row2[i] ?? "";
+ });
+ return obj;
+ });
+ const transformed = matchColumnsStepHook ? await matchColumnsStepHook(mapped) : mapped;
+ setState((s) => ({ ...s, mapping, mappedRows: transformed, step: "validate" }));
+ }
+ async function handleSubmit(result) {
+ if (!state.workbook) return;
+ await onSubmit(result, state.workbook.file);
+ onClose?.();
+ }
+ const body = /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "d-flex flex-column gap-3", children: [
+ !hideStepper && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Stepper, { current: state.step, translations: t.stepper }),
+ state.maxExceeded && maxRecords !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Alert, { variant: "danger", className: "m-0", children: t.uploadStep.maxRecordsExceeded(maxRecords) }),
+ state.step === "upload" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
+ UploadStep,
+ {
+ fields,
+ maxFileSize,
+ translations: t.uploadStep,
+ onLoaded: handleUploaded,
+ uploadStepHook,
+ showTitle: !hideStepTitles
+ }
+ ),
+ state.step === "selectSheet" && state.workbook && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
+ SelectSheetStep,
+ {
+ workbook: state.workbook,
+ translations: t.selectSheetStep,
+ onBack: () => setState((s) => ({ ...s, step: "upload" })),
+ onNext: (idx) => advanceFromSheet(state.workbook, idx),
+ showTitle: !hideStepTitles
+ }
+ ),
+ state.step === "selectHeader" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
+ SelectHeaderStep,
+ {
+ rows: dataRows,
+ translations: t.selectHeaderStep,
+ onBack: () => setState((s) => ({
+ ...s,
+ step: state.workbook && state.workbook.sheets.length > 1 ? "selectSheet" : "upload"
+ })),
+ onNext: handleHeader,
+ showTitle: !hideStepTitles
+ }
+ ),
+ state.step === "matchColumns" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
+ MatchColumnsStep,
+ {
+ fields,
+ headers: headerRow2,
+ rows: bodyRows,
+ autoMapHeaders,
+ autoMapDistance,
+ translations: t.matchColumnsStep,
+ alertTranslations: t.alerts.unmatchedRequiredFields,
+ onBack: () => setState((s) => ({ ...s, step: "selectHeader" })),
+ onNext: handleMatch,
+ showTitle: !hideStepTitles
+ }
+ ),
+ state.step === "validate" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
+ ValidationStep,
+ {
+ fields,
+ initialRows: state.mappedRows,
+ schema,
+ rowHook,
+ allowInvalidSubmit,
+ translations: t.validationStep,
+ alertTranslations: t.alerts.submitIncomplete,
+ onBack: () => setState((s) => ({ ...s, step: "matchColumns" })),
+ onSubmit: handleSubmit,
+ showTitle: !hideStepTitles
+ }
+ )
+ ] });
+ if (inline) {
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "rsi-inline", children: body });
+ }
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
+ import_react_bootstrap6.Modal,
+ {
+ show: isOpen,
+ onHide: tryClose,
+ size: "xl",
+ backdrop: "static",
+ scrollable: true,
+ className: "rsi-modal",
+ contentClassName: "rsi-modal-content",
+ children: [
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Modal.Header, { closeButton: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Modal.Title, { children: title ?? "Spreadsheet importer" }) }),
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Modal.Body, { children: body })
+ ]
+ }
+ ),
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
+ import_react_bootstrap6.Modal,
+ {
+ show: state.showCloseConfirm,
+ onHide: () => setState((s) => ({ ...s, showCloseConfirm: false })),
+ centered: true,
+ children: [
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Modal.Header, { closeButton: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Modal.Title, { children: t.alerts.confirmClose.headerTitle }) }),
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Modal.Body, { children: t.alerts.confirmClose.bodyText }),
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_bootstrap6.Modal.Footer, { children: [
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_bootstrap6.Button, { variant: "outline-secondary", onClick: () => setState((s) => ({ ...s, showCloseConfirm: false })), children: t.alerts.confirmClose.cancelButtonTitle }),
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
+ import_react_bootstrap6.Button,
+ {
+ variant: "danger",
+ onClick: () => {
+ setState((s) => ({ ...s, showCloseConfirm: false }));
+ onClose?.();
+ },
+ children: t.alerts.confirmClose.exitButtonTitle
+ }
+ )
+ ] })
+ ]
+ }
+ )
+ ] });
+}
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+ ReactSpreadsheetImport,
+ autoMatchColumns,
+ defaultTranslations,
+ rowHasErrors,
+ validateRows
+});
+//# sourceMappingURL=index.cjs.map
\ No newline at end of file
diff --git a/dist/index.cjs.map b/dist/index.cjs.map
new file mode 100644
index 00000000..01c2d57e
--- /dev/null
+++ b/dist/index.cjs.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../src/index.ts","#style-inject:#style-inject","../src/styles.css","../src/ReactSpreadsheetImport.tsx","../src/components/Stepper.tsx","../src/steps/MatchColumnsStep.tsx","../src/utils/autoMatch.ts","../src/steps/SelectHeaderStep.tsx","../src/steps/SelectSheetStep.tsx","../src/steps/UploadStep.tsx","../src/utils/parseFile.ts","../src/steps/ValidationStep.tsx","../node_modules/react-data-grid/src/utils/colSpanUtils.ts","../node_modules/react-data-grid/src/utils/domUtils.ts","../node_modules/react-data-grid/src/utils/eventUtils.ts","../node_modules/react-data-grid/src/utils/keyboardUtils.ts","../node_modules/react-data-grid/src/utils/renderMeasuringCells.tsx","../node_modules/react-data-grid/src/utils/selectedCellUtils.ts","../node_modules/react-data-grid/src/style/cell.ts","../node_modules/react-data-grid/src/utils/styleUtils.ts","../node_modules/react-data-grid/src/utils/index.ts","../node_modules/react-data-grid/src/cellRenderers/renderCheckbox.tsx","../node_modules/react-data-grid/src/cellRenderers/renderToggleGroup.tsx","../node_modules/react-data-grid/src/cellRenderers/renderValue.tsx","../node_modules/react-data-grid/src/DataGridDefaultRenderersContext.ts","../node_modules/react-data-grid/src/cellRenderers/SelectCellFormatter.tsx","../node_modules/react-data-grid/src/hooks/useRowSelection.ts","../node_modules/react-data-grid/src/Columns.tsx","../node_modules/react-data-grid/src/renderHeaderCell.tsx","../node_modules/react-data-grid/src/hooks/useCalculatedColumns.ts","../node_modules/react-data-grid/src/hooks/useColumnWidths.ts","../node_modules/react-data-grid/src/hooks/useGridDimensions.ts","../node_modules/react-data-grid/src/hooks/useLatestFunc.ts","../node_modules/react-data-grid/src/hooks/useRovingTabIndex.ts","../node_modules/react-data-grid/src/hooks/useViewportColumns.ts","../node_modules/react-data-grid/src/hooks/useViewportRows.ts","../node_modules/react-data-grid/src/Cell.tsx","../node_modules/react-data-grid/src/EditCell.tsx","../node_modules/react-data-grid/src/GroupedColumnHeaderCell.tsx","../node_modules/react-data-grid/src/HeaderCell.tsx","../node_modules/react-data-grid/src/style/row.ts","../node_modules/react-data-grid/src/HeaderRow.tsx","../node_modules/react-data-grid/src/GroupedColumnHeaderRow.tsx","../node_modules/react-data-grid/src/Row.tsx","../node_modules/react-data-grid/src/ScrollToCell.tsx","../node_modules/react-data-grid/src/sortStatus.tsx","../node_modules/react-data-grid/src/style/core.ts","../node_modules/react-data-grid/src/SummaryCell.tsx","../node_modules/react-data-grid/src/SummaryRow.tsx","../node_modules/react-data-grid/src/DataGrid.tsx","../node_modules/react-data-grid/src/GroupCell.tsx","../node_modules/react-data-grid/src/GroupRow.tsx","../node_modules/react-data-grid/src/TreeDataGrid.tsx","../node_modules/react-data-grid/src/editors/renderTextEditor.tsx","../src/utils/validateRows.ts","../src/translations/defaultTranslations.ts"],"sourcesContent":["import \"./styles.css\";\n\nexport { ReactSpreadsheetImport } from \"./ReactSpreadsheetImport\";\nexport { defaultTranslations } from \"./translations/defaultTranslations\";\nexport { autoMatchColumns } from \"./utils/autoMatch\";\nexport { validateRows, rowHasErrors } from \"./utils/validateRows\";\nexport type {\n DeepPartial,\n ErrorLevel,\n Field,\n FieldError,\n FieldInputType,\n ImportResult,\n ImportedRow,\n RawData,\n RawSheet,\n ReactSpreadsheetImportProps,\n RowErrors,\n RowHook,\n SelectOption,\n StepName,\n Translations,\n UploadedWorkbook,\n} from \"./types\";\n","\n export default function styleInject(css, { insertAt } = {}) {\n if (!css || typeof document === 'undefined') return\n \n const head = document.head || document.getElementsByTagName('head')[0]\n const style = document.createElement('style')\n style.type = 'text/css'\n \n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild)\n } else {\n head.appendChild(style)\n }\n } else {\n head.appendChild(style)\n }\n \n if (style.styleSheet) {\n style.styleSheet.cssText = css\n } else {\n style.appendChild(document.createTextNode(css))\n }\n }\n ","import styleInject from '#style-inject';styleInject(\"@layer rdg {\\n @layer Defaults, FocusSink, CheckboxInput, CheckboxIcon, CheckboxLabel, Cell, HeaderCell, SummaryCell, EditCell, Row, HeaderRow, SummaryRow, GroupedRow, Root;\\n}\\n.rdg-7-0-0-beta-58-fa71d63e {\\n @layer rdg.MeasuringCell {\\n contain: strict;\\n grid-row: 1;\\n visibility: hidden;\\n }\\n}\\n.rdg-7-0-0-beta-58-85c48527 {\\n @layer rdg.Cell {\\n position: relative;\\n padding-block: 0;\\n padding-inline: 8px;\\n border-inline-end: var(--rdg-border-width) solid var(--rdg-border-color);\\n border-block-end: var(--rdg-border-width) solid var(--rdg-border-color);\\n grid-row-start: var(--rdg-grid-row-start);\\n align-content: center;\\n background-color: inherit;\\n white-space: nowrap;\\n overflow: clip;\\n text-overflow: ellipsis;\\n outline: none;\\n &[aria-selected=true] {\\n outline: var(--rdg-selection-width) solid var(--rdg-selection-color);\\n outline-offset: calc(var(--rdg-selection-width) * -1);\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-17a9a6d4 {\\n @layer rdg.Cell {\\n position: sticky;\\n z-index: 1;\\n &:nth-last-child(1 of &) {\\n box-shadow: var(--rdg-cell-frozen-box-shadow);\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-bfba19bc {\\n @layer rdg.DragHandle {\\n --rdg-drag-handle-size: 8px;\\n z-index: 0;\\n cursor: move;\\n inline-size: var(--rdg-drag-handle-size);\\n block-size: var(--rdg-drag-handle-size);\\n background-color: var(--rdg-selection-color);\\n place-self: end;\\n &:hover {\\n --rdg-drag-handle-size: 16px;\\n border: 2px solid var(--rdg-selection-color);\\n background-color: var(--rdg-background-color);\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-7abddb3e {\\n @layer rdg.DragHandle {\\n z-index: 1;\\n position: sticky;\\n }\\n}\\n.rdg-7-0-0-beta-58-3b807ead {\\n @layer rdg.CheckboxInput {\\n display: block;\\n margin: auto;\\n inline-size: 20px;\\n block-size: 20px;\\n &:focus-visible {\\n outline: 2px solid var(--rdg-checkbox-focus-color);\\n outline-offset: -3px;\\n }\\n &:enabled {\\n cursor: pointer;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-07919382 {\\n @layer rdg.GroupCellContent {\\n outline: none;\\n }\\n}\\n.rdg-7-0-0-beta-58-02a50147 {\\n @layer rdg.GroupCellCaret {\\n margin-inline-start: 4px;\\n stroke: currentColor;\\n stroke-width: 1.5px;\\n fill: transparent;\\n vertical-align: middle;\\n > path {\\n transition: d 0.1s;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-56a248e4 {\\n @layer rdg.SortableHeaderCell {\\n display: flex;\\n }\\n}\\n.rdg-7-0-0-beta-58-7fad8c83 {\\n @layer rdg.SortableHeaderCellName {\\n flex-grow: 1;\\n overflow: clip;\\n text-overflow: ellipsis;\\n }\\n}\\n.rdg-7-0-0-beta-58-35ccb4c8 {\\n @layer rdg.Cell {\\n background-color: #ccccff;\\n }\\n}\\n.rdg-7-0-0-beta-58-46f9ea88 {\\n @layer rdg.EditCell {\\n padding: 0;\\n }\\n}\\n.rdg-7-0-0-beta-58-0dbd5994 {\\n @layer rdg.HeaderRow {\\n display: contents;\\n background-color: var(--rdg-header-background-color);\\n font-weight: bold;\\n & > .rdg-7-0-0-beta-58-85c48527 {\\n z-index: 2;\\n position: sticky;\\n }\\n & > .rdg-7-0-0-beta-58-17a9a6d4 {\\n z-index: 3;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-2a7e240d {\\n @layer rdg.HeaderCell {\\n cursor: pointer;\\n }\\n}\\n.rdg-7-0-0-beta-58-1893dc0f {\\n @layer rdg.HeaderCell {\\n touch-action: none;\\n }\\n}\\n.rdg-7-0-0-beta-58-4e60db91 {\\n @layer rdg.HeaderCell {\\n cursor: col-resize;\\n position: absolute;\\n inset-block-start: 0;\\n inset-inline-end: 0;\\n inset-block-end: 0;\\n inline-size: 10px;\\n }\\n}\\n.rdg-7-0-0-beta-58-3e1a4ad4 {\\n @layer rdg.HeaderCell {\\n background-color: var(--rdg-header-draggable-background-color);\\n }\\n}\\n.rdg-7-0-0-beta-58-51abd8b8 {\\n @layer rdg.HeaderCell {\\n background-color: var(--rdg-header-draggable-background-color);\\n }\\n}\\n.rdg-7-0-0-beta-58-c8d7aa64 {\\n @layer rdg.HeaderCell {\\n border-radius: 4px;\\n width: fit-content;\\n outline: 2px solid hsl(207, 100%, 50%);\\n outline-offset: -2px;\\n }\\n}\\n.rdg-7-0-0-beta-58-3c083f1b {\\n @layer rdg.Row {\\n display: contents;\\n background-color: var(--rdg-background-color);\\n &:hover {\\n background-color: var(--rdg-row-hover-background-color);\\n }\\n &[aria-selected=true] {\\n background-color: var(--rdg-row-selected-background-color);\\n &:hover {\\n background-color: var(--rdg-row-selected-hover-background-color);\\n }\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-3fe773c3 {\\n @layer rdg.FocusSink {\\n outline: 2px solid var(--rdg-selection-color);\\n outline-offset: -2px;\\n }\\n}\\n.rdg-7-0-0-beta-58-97ce3fde {\\n @layer rdg.FocusSink {\\n &::before {\\n content: \\\"\\\";\\n display: inline-block;\\n block-size: 100%;\\n position: sticky;\\n inset-inline-start: 0;\\n border-inline-start: 2px solid var(--rdg-selection-color);\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-3d5115f3 {\\n @layer rdg.SortIcon {\\n fill: currentColor;\\n > path {\\n transition: d 0.1s;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-ccd2e5d9 {\\n @layer rdg.Defaults {\\n *,\\n *::before,\\n *::after {\\n box-sizing: inherit;\\n }\\n }\\n @layer rdg.Root {\\n --rdg-selection-width: 2px;\\n --rdg-selection-color: hsl(207, 75%, 66%);\\n --rdg-font-size: 14px;\\n --rdg-cell-frozen-box-shadow: 2px 0 5px -2px rgba(136, 136, 136, 0.3);\\n --rdg-border-width: 1px;\\n --rdg-summary-border-width: calc(var(--rdg-border-width) * 2);\\n --rdg-color: light-dark(#000, #ddd);\\n --rdg-border-color: light-dark(#ddd, #444);\\n --rdg-summary-border-color: light-dark(#aaa, #555);\\n --rdg-background-color: light-dark(hsl(0deg 0% 100%), hsl(0deg 0% 13%));\\n --rdg-header-background-color: light-dark(hsl(0deg 0% 97.5%), hsl(0deg 0% 10.5%));\\n --rdg-header-draggable-background-color: light-dark(hsl(0deg 0% 90.5%), hsl(0deg 0% 17.5%));\\n --rdg-row-hover-background-color: light-dark(hsl(0deg 0% 96%), hsl(0deg 0% 9%));\\n --rdg-row-selected-background-color: light-dark(hsl(207deg 76% 92%), hsl(207deg 76% 42%));\\n --rdg-row-selected-hover-background-color: light-dark(hsl(207deg 76% 88%), hsl(207deg 76% 38%));\\n --rdg-checkbox-focus-color: hsl(207deg 100% 69%);\\n &.rdg-dark {\\n --rdg-color-scheme: dark;\\n }\\n &.rdg-light {\\n --rdg-color-scheme: light;\\n }\\n color-scheme: var(--rdg-color-scheme, light dark);\\n &:dir(rtl) {\\n --rdg-cell-frozen-box-shadow: -2px 0 5px -2px rgba(136, 136, 136, 0.3);\\n }\\n display: grid;\\n accent-color: light-dark(hsl(207deg 100% 29%), hsl(207deg 100% 79%));\\n contain: content;\\n content-visibility: auto;\\n block-size: 350px;\\n border: 1px solid var(--rdg-border-color);\\n box-sizing: border-box;\\n overflow: auto;\\n background-color: var(--rdg-background-color);\\n color: var(--rdg-color);\\n font-size: var(--rdg-font-size);\\n &::before {\\n content: \\\"\\\";\\n grid-column: 1/-1;\\n grid-row: 1/-1;\\n }\\n > :nth-last-child(1 of .rdg-top-summary-row) {\\n > .rdg-7-0-0-beta-58-85c48527 {\\n border-block-end: var(--rdg-summary-border-width) solid var(--rdg-summary-border-color);\\n }\\n }\\n > :nth-child(1 of .rdg-bottom-summary-row) {\\n > .rdg-7-0-0-beta-58-85c48527 {\\n border-block-start: var(--rdg-summary-border-width) solid var(--rdg-summary-border-color);\\n }\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-e9b0e1c9 {\\n @layer rdg.Root {\\n user-select: none;\\n & .rdg-7-0-0-beta-58-3c083f1b {\\n cursor: move;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-dbb8b3c5 {\\n @layer rdg.FocusSink {\\n grid-column: 1/-1;\\n pointer-events: none;\\n z-index: 1;\\n }\\n}\\n.rdg-7-0-0-beta-58-e9f55541 {\\n @layer rdg.FocusSink {\\n z-index: 3;\\n }\\n}\\n.rdg-7-0-0-beta-58-0b90c82c {\\n @layer rdg.SummaryRow {\\n > .rdg-7-0-0-beta-58-85c48527 {\\n position: sticky;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-d0520eab {\\n @layer rdg.SummaryRow {\\n > .rdg-7-0-0-beta-58-85c48527 {\\n z-index: 2;\\n }\\n > .rdg-7-0-0-beta-58-17a9a6d4 {\\n z-index: 3;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-d907aa87 {\\n @layer rdg.SummaryCell {\\n inset-block-start: var(--rdg-summary-row-top);\\n inset-block-end: var(--rdg-summary-row-bottom);\\n }\\n}\\n.rdg-7-0-0-beta-58-e74a2be3 {\\n @layer rdg.GroupedRow {\\n &:not([aria-selected=true]) {\\n background-color: var(--rdg-header-background-color);\\n }\\n > .rdg-7-0-0-beta-58-85c48527:not(:last-child, .rdg-7-0-0-beta-58-17a9a6d4),\\n > :nth-last-child(n+2 of .rdg-7-0-0-beta-58-17a9a6d4) {\\n border-inline-end: none;\\n }\\n }\\n}\\n.rdg-7-0-0-beta-58-2f8db206 {\\n @layer rdg.TextEditor {\\n appearance: none;\\n box-sizing: border-box;\\n inline-size: 100%;\\n block-size: 100%;\\n padding-block: 0;\\n padding-inline: 6px;\\n border: 2px solid #ccc;\\n vertical-align: top;\\n color: var(--rdg-color);\\n background-color: var(--rdg-background-color);\\n font-family: inherit;\\n font-size: var(--rdg-font-size);\\n &:focus {\\n border-color: var(--rdg-selection-color);\\n outline: none;\\n }\\n &::placeholder {\\n color: #999;\\n opacity: 1;\\n }\\n }\\n}\\n.rsi-modal-content {\\n --rsi-error: var(--bs-danger);\\n --rsi-warning: var(--bs-warning);\\n --rsi-info: var(--bs-info);\\n}\\n.rsi-dropzone {\\n border-style: dashed !important;\\n transition: background-color 120ms ease, border-color 120ms ease;\\n}\\n.rsi-grid-wrapper .rdg {\\n block-size: 100%;\\n border: 1px solid var(--bs-border-color);\\n border-radius: var(--bs-border-radius);\\n --rdg-border-color: var(--bs-border-color-translucent);\\n --rdg-color: var(--bs-body-color);\\n --rdg-background-color: var(--bs-body-bg);\\n --rdg-header-background-color: var(--bs-tertiary-bg);\\n --rdg-row-hover-background-color: var(--bs-secondary-bg);\\n --rdg-row-selected-background-color: var(--bs-primary-bg-subtle);\\n --rdg-row-selected-hover-background-color: var(--bs-primary-bg-subtle);\\n --rdg-selection-color: var(--bs-primary);\\n font-family: inherit;\\n font-size: 0.875rem;\\n}\\n.rsi-cell-error {\\n background-color: var(--bs-danger-bg-subtle);\\n color: var(--bs-danger-text-emphasis);\\n padding: 0 4px;\\n border-radius: 2px;\\n cursor: default;\\n}\\n.rsi-tooltip-fixed {\\n pointer-events: none;\\n}\\n.rsi-tooltip-inner {\\n background-color: #0a2540;\\n color: #ffffff;\\n font-size: 0.8125rem;\\n max-width: 320px;\\n text-align: left;\\n padding: 6px 10px;\\n border-radius: 4px;\\n}\\n.rsi-tooltip-arrow {\\n width: 0;\\n height: 0;\\n border-left: 6px solid transparent;\\n border-right: 6px solid transparent;\\n border-top: 6px solid #0a2540;\\n margin: 0 auto;\\n}\\n.rsi-cell-warning {\\n background-color: var(--bs-warning-bg-subtle);\\n color: var(--bs-warning-text-emphasis);\\n padding: 0 4px;\\n border-radius: 2px;\\n}\\n.rsi-cell-info {\\n background-color: var(--bs-info-bg-subtle);\\n color: var(--bs-info-text-emphasis);\\n padding: 0 4px;\\n border-radius: 2px;\\n}\\n.rsi-cell-edit {\\n height: 100%;\\n border-radius: 0;\\n}\\n.rsi-stepper-row {\\n gap: 1px;\\n background-color: transparent;\\n}\\n.rsi-stepper-item {\\n min-width: 0;\\n padding-right: 1rem;\\n}\\n.rsi-stepper-item:last-child {\\n padding-right: 0;\\n}\\n.rsi-stepper-bar {\\n height: 4px;\\n background-color: var(--bs-border-color);\\n border-radius: 2px;\\n margin-bottom: 0.5rem;\\n}\\n.rsi-stepper-done .rsi-stepper-bar,\\n.rsi-stepper-active .rsi-stepper-bar {\\n background-color: var(--bs-primary);\\n}\\n.rsi-stepper-label {\\n color: var(--bs-secondary-color);\\n font-weight: 500;\\n}\\n.rsi-stepper-active .rsi-stepper-label {\\n color: var(--bs-body-color);\\n font-weight: 600;\\n}\\n.rsi-stepper-num {\\n color: inherit;\\n}\\n.rsi-stepper-active .rsi-stepper-num {\\n color: #1b9aa9;\\n}\\n.rsi-inline .btn-primary,\\n.rsi-modal-content .btn-primary {\\n color: #1b9aa9;\\n background-color: #ffffff;\\n border-color: #dfeff3;\\n}\\n.rsi-inline .btn-primary:hover,\\n.rsi-modal-content .btn-primary:hover,\\n.rsi-inline .btn-primary:focus,\\n.rsi-modal-content .btn-primary:focus {\\n color: #2696a6;\\n background-color: #f2fafb;\\n border-color: #d1ebee;\\n}\\n.rsi-inline .btn-primary:active,\\n.rsi-modal-content .btn-primary:active,\\n.rsi-inline .btn-primary:disabled,\\n.rsi-modal-content .btn-primary:disabled {\\n color: #1b9aa9;\\n background-color: #f2fafb;\\n border-color: #d1ebee;\\n}\\n.rsi-inline .btn-primary:focus-visible,\\n.rsi-modal-content .btn-primary:focus-visible {\\n box-shadow: 0 0 0 0.25rem rgba(27, 154, 169, 0.25);\\n}\\n.rsi-inline .btn-outline-secondary,\\n.rsi-modal-content .btn-outline-secondary {\\n color: #858c9c;\\n background-color: #ffffff;\\n border-color: #e7e7ec;\\n}\\n.rsi-inline .btn-outline-secondary:hover,\\n.rsi-modal-content .btn-outline-secondary:hover,\\n.rsi-inline .btn-outline-secondary:focus,\\n.rsi-modal-content .btn-outline-secondary:focus {\\n color: #0a2540;\\n background-color: #f8f8f8;\\n border-color: #cfcfd7;\\n}\\n.rsi-inline .btn-outline-secondary:active,\\n.rsi-modal-content .btn-outline-secondary:active,\\n.rsi-inline .btn-outline-secondary:disabled,\\n.rsi-modal-content .btn-outline-secondary:disabled {\\n color: #858c9c;\\n background-color: #f8f8f8;\\n border-color: #cfcfd7;\\n}\\n.rsi-inline .btn-outline-secondary:focus-visible,\\n.rsi-modal-content .btn-outline-secondary:focus-visible {\\n box-shadow: 0 0 0 0.25rem rgba(133, 140, 156, 0.25);\\n}\\n.rsi-match-grid > * {\\n border-bottom: 1px solid var(--bs-border-color);\\n}\\n.rsi-match-grid > *:last-child,\\n.rsi-match-grid .rsi-match-col-header:last-child {\\n border-right: 0;\\n}\\n.rsi-match-section-label {\\n position: sticky;\\n left: 0;\\n padding: 0.75rem 1rem;\\n font-weight: 600;\\n background-color: var(--bs-tertiary-bg);\\n color: var(--bs-body-color);\\n width: max-content;\\n min-width: 100%;\\n border-bottom: 1px solid var(--bs-border-color);\\n}\\n.rsi-match-section-divider {\\n border-top: 1px solid var(--bs-border-color);\\n}\\n.rsi-status-dot {\\n display: inline-block;\\n width: 14px;\\n height: 14px;\\n border-radius: 50%;\\n border: 2px solid var(--bs-border-color);\\n flex-shrink: 0;\\n}\\n.rsi-status-dot.rsi-status-matched {\\n background-color: var(--bs-success);\\n border-color: var(--bs-success);\\n}\\n.rsi-status-dot.rsi-status-matched-required {\\n background-color: var(--bs-success);\\n border-color: var(--bs-success);\\n}\\n.rsi-status-dot.rsi-status-ignored {\\n background-color: transparent;\\n border-color: var(--bs-secondary-border-subtle);\\n}\\n.rsi-ignore-btn {\\n border: 1px solid var(--bs-border-color);\\n background-color: var(--bs-secondary-bg);\\n color: var(--bs-secondary-color);\\n font-size: 14px;\\n}\\n.rsi-ignore-btn:hover {\\n background-color: var(--bs-tertiary-bg);\\n}\\n\")","import { useEffect, useMemo, useState, type ReactNode } from \"react\";\nimport { Alert, Button, Modal } from \"react-bootstrap\";\nimport { Stepper } from \"./components/Stepper\";\nimport { MatchColumnsStep } from \"./steps/MatchColumnsStep\";\nimport { SelectHeaderStep } from \"./steps/SelectHeaderStep\";\nimport { SelectSheetStep } from \"./steps/SelectSheetStep\";\nimport { UploadStep } from \"./steps/UploadStep\";\nimport { ValidationStep } from \"./steps/ValidationStep\";\nimport { defaultTranslations, mergeTranslations } from \"./translations/defaultTranslations\";\nimport type {\n ImportResult,\n ReactSpreadsheetImportProps,\n StepName,\n Translations,\n UploadedWorkbook,\n} from \"./types\";\n\ninterface State {\n step: StepName;\n workbook: UploadedWorkbook | null;\n sheetIndex: number;\n headerIndex: number;\n mapping: Array;\n mappedRows: Array>>;\n showCloseConfirm: boolean;\n maxExceeded: boolean;\n}\n\nconst INITIAL_STATE = {\n step: \"upload\" as StepName,\n workbook: null,\n sheetIndex: 0,\n headerIndex: 0,\n mapping: [] as Array,\n mappedRows: [] as Array>>,\n showCloseConfirm: false,\n maxExceeded: false,\n};\n\nexport function ReactSpreadsheetImport(props: ReactSpreadsheetImportProps) {\n const {\n isOpen = true,\n onClose,\n onSubmit,\n fields,\n schema,\n rowHook,\n uploadStepHook,\n selectHeaderStepHook,\n matchColumnsStepHook,\n maxRecords,\n maxFileSize,\n allowInvalidSubmit = true,\n autoMapHeaders,\n autoMapDistance,\n translations: translationsOverride,\n title,\n inline = false,\n hideStepper = false,\n hideStepTitles = false,\n } = props;\n\n const t: Translations = useMemo(\n () => mergeTranslations(defaultTranslations, translationsOverride as Partial | undefined),\n [translationsOverride],\n );\n\n const [state, setState] = useState>(INITIAL_STATE as State);\n\n // Reset whenever the modal is re-opened. In inline mode, the component lifecycle handles reset.\n useEffect(() => {\n if (!inline && isOpen) {\n setState(INITIAL_STATE as State);\n }\n }, [isOpen, inline]);\n\n const sheet = state.workbook?.sheets[state.sheetIndex];\n const dataRows = sheet?.rows ?? [];\n const headerRow = dataRows[state.headerIndex] ?? [];\n const bodyRows = dataRows.slice(state.headerIndex + 1);\n\n function tryClose() {\n if (!onClose) return;\n if (state.step === \"upload\") {\n onClose();\n } else {\n setState((s) => ({ ...s, showCloseConfirm: true }));\n }\n }\n\n async function handleUploaded(wb: UploadedWorkbook) {\n if (wb.sheets.length > 1) {\n setState((s) => ({ ...s, workbook: wb, step: \"selectSheet\" }));\n return;\n }\n advanceFromSheet(wb, 0);\n }\n\n function advanceFromSheet(wb: UploadedWorkbook, idx: number) {\n const rows = wb.sheets[idx]?.rows ?? [];\n const exceeded = maxRecords !== undefined && rows.length - 1 > maxRecords;\n setState((s) => ({\n ...s,\n workbook: wb,\n sheetIndex: idx,\n step: \"selectHeader\",\n maxExceeded: exceeded,\n }));\n }\n\n async function handleHeader(idx: number) {\n let nextHeader = dataRows[idx] ?? [];\n let nextBody = dataRows.slice(idx + 1);\n if (selectHeaderStepHook) {\n const r = await selectHeaderStepHook(nextHeader, nextBody);\n nextHeader = r.headerValues;\n nextBody = r.data;\n }\n if (state.workbook && state.workbook.sheets[state.sheetIndex]) {\n const sheets = state.workbook.sheets.slice();\n sheets[state.sheetIndex] = {\n ...sheets[state.sheetIndex]!,\n rows: [nextHeader, ...nextBody],\n };\n setState((s) => ({ ...s, workbook: { ...state.workbook!, sheets }, headerIndex: 0, step: \"matchColumns\" }));\n } else {\n setState((s) => ({ ...s, headerIndex: idx, step: \"matchColumns\" }));\n }\n }\n\n async function handleMatch(mapping: Array) {\n const mapped = bodyRows.map((row) => {\n const obj: Partial> = {};\n mapping.forEach((key, i) => {\n if (key) (obj as any)[key] = row[i] ?? \"\";\n });\n return obj;\n });\n const transformed = matchColumnsStepHook ? await matchColumnsStepHook(mapped as any) : mapped;\n setState((s) => ({ ...s, mapping, mappedRows: transformed as Array>>, step: \"validate\" }));\n }\n\n async function handleSubmit(result: ImportResult) {\n if (!state.workbook) return;\n await onSubmit(result, state.workbook.file);\n onClose?.();\n }\n\n const body: ReactNode = (\n \n {!hideStepper &&
}\n\n {state.maxExceeded && maxRecords !== undefined && (\n
\n {t.uploadStep.maxRecordsExceeded(maxRecords)}\n \n )}\n\n {state.step === \"upload\" && (\n
}\n maxFileSize={maxFileSize}\n translations={t.uploadStep}\n onLoaded={handleUploaded}\n uploadStepHook={uploadStepHook}\n showTitle={!hideStepTitles}\n />\n )}\n\n {state.step === \"selectSheet\" && state.workbook && (\n setState((s) => ({ ...s, step: \"upload\" }))}\n onNext={(idx) => advanceFromSheet(state.workbook!, idx)}\n showTitle={!hideStepTitles}\n />\n )}\n\n {state.step === \"selectHeader\" && (\n \n setState((s) => ({\n ...s,\n step: state.workbook && state.workbook.sheets.length > 1 ? \"selectSheet\" : \"upload\",\n }))\n }\n onNext={handleHeader}\n showTitle={!hideStepTitles}\n />\n )}\n\n {state.step === \"matchColumns\" && (\n setState((s) => ({ ...s, step: \"selectHeader\" }))}\n onNext={handleMatch}\n showTitle={!hideStepTitles}\n />\n )}\n\n {state.step === \"validate\" && (\n setState((s) => ({ ...s, step: \"matchColumns\" }))}\n onSubmit={handleSubmit}\n showTitle={!hideStepTitles}\n />\n )}\n \n );\n\n if (inline) {\n return {body}
;\n }\n\n return (\n <>\n \n \n {title ?? \"Spreadsheet importer\"} \n \n {body} \n \n\n setState((s) => ({ ...s, showCloseConfirm: false }))}\n centered\n >\n \n {t.alerts.confirmClose.headerTitle} \n \n {t.alerts.confirmClose.bodyText} \n \n setState((s) => ({ ...s, showCloseConfirm: false }))}>\n {t.alerts.confirmClose.cancelButtonTitle}\n \n {\n setState((s) => ({ ...s, showCloseConfirm: false }));\n onClose?.();\n }}\n >\n {t.alerts.confirmClose.exitButtonTitle}\n \n \n \n >\n );\n}\n","import type { StepName, Translations } from \"../types\";\n\ninterface StepperProps {\n current: StepName;\n translations: Translations[\"stepper\"];\n}\n\n// Always 4 segments. SelectSheet (only relevant for multi-sheet xlsx) is hidden from the bar\n// and rolls under \"Upload Roster\" visually.\nconst ORDER: StepName[] = [\"upload\", \"selectSheet\", \"selectHeader\", \"matchColumns\", \"validate\"];\n\nconst POSITIONS: Array<{ stepNames: StepName[]; key: keyof Translations[\"stepper\"] }> = [\n { stepNames: [\"upload\", \"selectSheet\"], key: \"upload\" },\n { stepNames: [\"selectHeader\"], key: \"selectHeader\" },\n { stepNames: [\"matchColumns\"], key: \"matchColumns\" },\n { stepNames: [\"validate\"], key: \"submit\" },\n];\n\nexport function Stepper({ current, translations }: StepperProps) {\n const currentOrder = ORDER.indexOf(current);\n\n return (\n \n \n {POSITIONS.map((pos, idx) => {\n const positionMaxOrder = Math.max(...pos.stepNames.map((s) => ORDER.indexOf(s)));\n const positionMinOrder = Math.min(...pos.stepNames.map((s) => ORDER.indexOf(s)));\n const status: \"done\" | \"active\" | \"todo\" =\n currentOrder > positionMaxOrder\n ? \"done\"\n : currentOrder >= positionMinOrder\n ? \"active\"\n : \"todo\";\n return (\n
\n
\n
\n {idx + 1}. {translations[pos.key]}\n
\n
\n );\n })}\n
\n \n );\n}\n","import { useEffect, useMemo, useState } from \"react\";\nimport { Alert, Button, Form, Modal } from \"react-bootstrap\";\nimport type { Field, RawSheet, Translations } from \"../types\";\nimport { autoMatchColumns } from \"../utils/autoMatch\";\n\ninterface Props {\n fields: ReadonlyArray>;\n headers: ReadonlyArray;\n rows: RawSheet;\n autoMapHeaders?: boolean;\n autoMapDistance?: number;\n translations: Translations[\"matchColumnsStep\"];\n alertTranslations: Translations[\"alerts\"][\"unmatchedRequiredFields\"];\n onBack: () => void;\n onNext: (mapping: Array) => void;\n showTitle?: boolean;\n}\n\nconst IGNORE = \"__ignore__\";\nconst SAMPLE_ROWS = 3;\nconst COLUMN_MIN_WIDTH = 180;\n\nexport function MatchColumnsStep({\n fields,\n headers,\n rows,\n autoMapDistance,\n translations,\n alertTranslations,\n onBack,\n onNext,\n showTitle = true,\n}: Props) {\n const initial = useMemo(\n () => autoMatchColumns(headers, fields as ReadonlyArray, autoMapDistance),\n [headers, fields, autoMapDistance],\n );\n const [mapping, setMapping] = useState>(initial);\n const [showWarn, setShowWarn] = useState(false);\n\n useEffect(() => setMapping(initial), [initial]);\n\n const sample = rows.slice(0, SAMPLE_ROWS);\n\n function setColumn(idx: number, value: string) {\n setMapping((prev) => {\n const next = [...prev];\n const newVal = value === IGNORE || value === \"\" ? undefined : value;\n if (newVal) {\n for (let i = 0; i < next.length; i++) {\n if (i !== idx && next[i] === newVal) next[i] = undefined;\n }\n }\n next[idx] = newVal;\n return next;\n });\n }\n\n const matched = new Set(mapping.filter((v): v is string => Boolean(v)));\n const requiredKeys = fields.filter((f) => f.required).map((f) => f.key);\n const unmatchedRequired = requiredKeys.filter((k) => !matched.has(k));\n\n function handleNext() {\n if (unmatchedRequired.length > 0) {\n setShowWarn(true);\n return;\n }\n onNext(mapping);\n }\n\n const gridCols = `repeat(${headers.length}, minmax(${COLUMN_MIN_WIDTH}px, 1fr))`;\n\n return (\n \n {showTitle &&
{translations.title} }\n\n
\n
\n
\n {translations.userTableTitle}\n
\n\n {headers.map((h, idx) => {\n const isIgnored = mapping[idx] === undefined;\n return (\n
\n
\n {h || `(column ${idx + 1})`} \n setColumn(idx, IGNORE)}\n disabled={isIgnored}\n >\n ร\n \n
\n
\n );\n })}\n {sample.map((row, ri) =>\n headers.map((_, ci) => {\n const isIgnored = mapping[ci] === undefined;\n return (\n
\n {row[ci] ?? \"\"}\n
\n );\n }),\n )}\n\n
\n {translations.templateTitle}\n
\n\n {headers.map((h, idx) => {\n const value = mapping[idx];\n const status = statusFor(value, fields);\n return (\n
\n
setColumn(idx, e.target.value)}\n aria-label={`${translations.matchDropdownTitle}: ${h}`}\n >\n {translations.ignoredColumnText} \n {fields.map((f) => (\n \n {f.label}\n {f.required ? \" *\" : \"\"}\n \n ))}\n \n \n \n );\n })}\n
\n
\n\n {unmatchedRequired.length > 0 && (\n
\n {translations.unmatched}:{\" \"}\n {unmatchedRequired\n .map((k) => fields.find((f) => f.key === k)?.label ?? k)\n .join(\", \")}\n \n )}\n\n
\n {translations.backButtonTitle} \n {translations.nextButtonTitle} \n
\n\n
setShowWarn(false)} centered>\n \n {alertTranslations.headerTitle} \n \n {alertTranslations.bodyText} \n \n setShowWarn(false)}>\n {alertTranslations.cancelButtonTitle}\n \n {\n setShowWarn(false);\n onNext(mapping);\n }}\n >\n {alertTranslations.continueButtonTitle}\n \n \n \n
\n );\n}\n\nfunction statusFor(\n key: string | undefined,\n fields: ReadonlyArray,\n): { className: string; title: string } {\n if (!key) return { className: \"rsi-status-ignored\", title: \"Ignored\" };\n const f = fields.find((x) => x.key === key);\n if (f?.required) return { className: \"rsi-status-matched-required\", title: `Matched: ${f.label}` };\n return { className: \"rsi-status-matched\", title: `Matched${f ? `: ${f.label}` : \"\"}` };\n}\n","import Fuse from \"fuse.js\";\nimport type { Field } from \"../types\";\n\ninterface SearchEntry {\n fieldKey: string;\n candidate: string;\n}\n\nconst NORMALIZE = /[\\s_\\-./]+/g;\n\nfunction normalize(s: string): string {\n return s.toLowerCase().replace(NORMALIZE, \"\").trim();\n}\n\n/**\n * Auto-match incoming spreadsheet headers to field keys using Fuse fuzzy search.\n * Returns a map: headerIndex -> matched field key (or undefined when no good match).\n *\n * `distance` is fuse.js threshold; lower = stricter. Default 0.25 is fairly strict.\n */\nexport function autoMatchColumns(\n headers: ReadonlyArray,\n fields: ReadonlyArray,\n distance = 0.25,\n): Array {\n const entries: SearchEntry[] = [];\n for (const f of fields) {\n const candidates = new Set([f.key, f.label, ...(f.alternateMatches ?? [])]);\n for (const c of candidates) {\n entries.push({ fieldKey: f.key, candidate: normalize(c) });\n }\n }\n\n const fuse = new Fuse(entries, {\n keys: [\"candidate\"],\n threshold: distance,\n ignoreLocation: true,\n isCaseSensitive: false,\n });\n\n // Greedy assignment: each header gets its best field match, but each field key only used once.\n const used = new Set();\n const result: Array = headers.map(() => undefined);\n\n // Score every (header, fieldKey) pair, then pick best pairs first.\n type Match = { headerIdx: number; fieldKey: string; score: number };\n const all: Match[] = [];\n\n headers.forEach((h, idx) => {\n if (!h || !h.trim()) return;\n const found = fuse.search(normalize(h));\n // Best match per fieldKey (fuse may return many entries for the same fieldKey via alternates).\n const seen = new Map();\n for (const r of found) {\n const score = r.score ?? 1;\n const key = r.item.fieldKey;\n const prev = seen.get(key);\n if (prev === undefined || score < prev) seen.set(key, score);\n }\n for (const [fieldKey, score] of seen) {\n all.push({ headerIdx: idx, fieldKey, score });\n }\n });\n\n all.sort((a, b) => a.score - b.score);\n const headerAssigned = new Set();\n for (const m of all) {\n if (headerAssigned.has(m.headerIdx)) continue;\n if (used.has(m.fieldKey)) continue;\n result[m.headerIdx] = m.fieldKey;\n headerAssigned.add(m.headerIdx);\n used.add(m.fieldKey);\n }\n return result;\n}\n","import { useState } from \"react\";\nimport { Button, Table } from \"react-bootstrap\";\nimport type { RawSheet, Translations } from \"../types\";\n\ninterface Props {\n rows: RawSheet;\n translations: Translations[\"selectHeaderStep\"];\n onBack: () => void;\n onNext: (headerIndex: number) => void;\n showTitle?: boolean;\n}\n\nexport function SelectHeaderStep({ rows, translations, onBack, onNext, showTitle = true }: Props) {\n const [selected, setSelected] = useState(0);\n\n return (\n \n {showTitle &&
{translations.title} }\n
\n
\n {translations.backButtonTitle} \n onNext(selected)}>{translations.nextButtonTitle} \n
\n
\n );\n}\n","import { useState } from \"react\";\nimport { Button, Form } from \"react-bootstrap\";\nimport type { Translations, UploadedWorkbook } from \"../types\";\n\ninterface Props {\n workbook: UploadedWorkbook;\n translations: Translations[\"selectSheetStep\"];\n onBack: () => void;\n onNext: (sheetIndex: number) => void;\n showTitle?: boolean;\n}\n\nexport function SelectSheetStep({ workbook, translations, onBack, onNext, showTitle = true }: Props) {\n const [selected, setSelected] = useState(0);\n return (\n \n {showTitle &&
{translations.title} }\n
setSelected(i)}\n />\n ))}\n \n \n {translations.backButtonTitle} \n onNext(selected)}>{translations.nextButtonTitle} \n
\n \n );\n}\n","import { useCallback, useState } from \"react\";\nimport { useDropzone } from \"react-dropzone\";\nimport { Alert, Button, Spinner, Table } from \"react-bootstrap\";\nimport type { Field, RawSheet, Translations, UploadedWorkbook } from \"../types\";\nimport { parseFile } from \"../utils/parseFile\";\n\ninterface UploadStepProps {\n fields: ReadonlyArray;\n maxFileSize?: number;\n translations: Translations[\"uploadStep\"];\n onLoaded: (wb: UploadedWorkbook) => void;\n uploadStepHook?: (data: RawSheet) => RawSheet | Promise;\n showTitle?: boolean;\n}\n\nconst ACCEPT = {\n \"text/csv\": [\".csv\"],\n \"application/vnd.ms-excel\": [\".xls\"],\n \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\": [\".xlsx\"],\n};\n\nexport function UploadStep({ fields, maxFileSize, translations, onLoaded, uploadStepHook, showTitle = true }: UploadStepProps) {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n const onDrop = useCallback(\n async (accepted: File[]) => {\n const file = accepted[0];\n if (!file) return;\n setError(null);\n setLoading(true);\n try {\n const wb = await parseFile(file);\n if (uploadStepHook && wb.sheets.length === 1 && wb.sheets[0]) {\n const transformed = await uploadStepHook(wb.sheets[0].rows);\n wb.sheets[0].rows = transformed;\n }\n onLoaded(wb);\n } catch (err) {\n setError((err as Error).message || translations.dropzone.errorToastDescription);\n } finally {\n setLoading(false);\n }\n },\n [onLoaded, uploadStepHook, translations.dropzone.errorToastDescription],\n );\n\n const { getRootProps, getInputProps, isDragActive, open } = useDropzone({\n onDrop,\n accept: ACCEPT,\n maxSize: maxFileSize,\n multiple: false,\n noClick: true,\n noKeyboard: true,\n });\n\n return (\n \n {showTitle &&
{translations.title} }\n
\n
{translations.manifestTitle}
\n
{translations.manifestDescription}
\n
\n
\n \n \n {fields.map((f) => (\n \n {f.label}\n {f.required && * }\n \n ))}\n \n \n \n \n {fields.map((f) => (\n \n {f.example ?? \"\"}\n \n ))}\n \n \n
\n
\n
\n\n
\n
\n {loading ? (\n <>\n
\n
{translations.dropzone.loadingTitle}
\n >\n ) : isDragActive ? (\n
{translations.dropzone.activeDropzoneTitle}
\n ) : (\n <>\n
{translations.dropzone.title}
\n
{\n e.stopPropagation();\n open();\n }}\n >\n {translations.dropzone.buttonTitle}\n \n >\n )}\n
\n\n {error &&
{error} }\n
\n );\n}\n","import ExcelJS from \"exceljs\";\nimport type { RawSheet, UploadedWorkbook } from \"../types\";\n\nconst CSV_TYPES = [\"text/csv\", \"application/csv\"];\nconst CSV_EXT = /\\.csv$/i;\n\nfunction cellToString(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (value instanceof Date) return value.toISOString().slice(0, 10);\n if (typeof value === \"object\") {\n const v = value as { text?: string; result?: unknown; richText?: { text: string }[] };\n if (typeof v.text === \"string\") return v.text;\n if (Array.isArray(v.richText)) return v.richText.map((r) => r.text).join(\"\");\n if (v.result !== undefined) return cellToString(v.result);\n return \"\";\n }\n return String(value);\n}\n\nfunction worksheetToRows(ws: ExcelJS.Worksheet): RawSheet {\n const rows: RawSheet = [];\n const lastCol = ws.actualColumnCount || ws.columnCount || 0;\n ws.eachRow({ includeEmpty: true }, (row) => {\n const out: string[] = [];\n for (let i = 1; i <= lastCol; i++) {\n out.push(cellToString(row.getCell(i).value));\n }\n rows.push(out);\n });\n // Trim trailing fully-empty rows.\n while (rows.length && rows[rows.length - 1]!.every((c) => c === \"\")) {\n rows.pop();\n }\n return rows;\n}\n\nexport async function parseFile(file: File): Promise {\n const isCsv = CSV_TYPES.includes(file.type) || CSV_EXT.test(file.name);\n const buffer = await file.arrayBuffer();\n\n if (isCsv) {\n const text = new TextDecoder(\"utf-8\").decode(buffer);\n const rows = splitCsv(text);\n // Trim trailing empty rows.\n while (rows.length && rows[rows.length - 1]!.every((c) => c === \"\")) {\n rows.pop();\n }\n return { file, sheets: [{ name: \"Sheet1\", rows }] };\n }\n\n const wb = new ExcelJS.Workbook();\n await wb.xlsx.load(buffer);\n const sheets = wb.worksheets.map((ws) => ({\n name: ws.name,\n rows: worksheetToRows(ws),\n }));\n return { file, sheets };\n}\n\n/** Minimal CSV parser handling quoted fields with commas, quotes, and newlines. */\nfunction splitCsv(input: string): string[][] {\n const out: string[][] = [];\n let row: string[] = [];\n let cell = \"\";\n let inQuotes = false;\n for (let i = 0; i < input.length; i++) {\n const c = input[i];\n if (inQuotes) {\n if (c === '\"') {\n if (input[i + 1] === '\"') {\n cell += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n cell += c;\n }\n continue;\n }\n if (c === '\"') {\n inQuotes = true;\n continue;\n }\n if (c === \",\") {\n row.push(cell);\n cell = \"\";\n continue;\n }\n if (c === \"\\n\" || c === \"\\r\") {\n if (c === \"\\r\" && input[i + 1] === \"\\n\") i++;\n row.push(cell);\n out.push(row);\n row = [];\n cell = \"\";\n continue;\n }\n cell += c;\n }\n if (cell.length > 0 || row.length > 0) {\n row.push(cell);\n out.push(row);\n }\n return out;\n}\n","import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Alert, Button, Form, Modal, Spinner } from \"react-bootstrap\";\nimport { DataGrid, type Column, SELECT_COLUMN_KEY, useRowSelection, useHeaderRowSelection } from \"react-data-grid\";\nimport type { ObjectSchema } from \"yup\";\nimport type {\n Field,\n ImportResult,\n ImportedRow,\n RowHook,\n Translations,\n} from \"../types\";\nimport { rowHasErrors, validateRows } from \"../utils/validateRows\";\n\nfunction SelectHeaderCell(props: { tabIndex: number }) {\n const { isIndeterminate, isRowSelected, onRowSelectionChange } = useHeaderRowSelection();\n return (\n \n { if (el) el.indeterminate = isIndeterminate; }}\n checked={isRowSelected}\n onChange={(e) => onRowSelectionChange({ checked: isIndeterminate ? false : e.target.checked })}\n />\n
\n );\n}\n\nfunction SelectRowCell(props: { tabIndex: number; row: any }) {\n const { isRowSelected, onRowSelectionChange } = useRowSelection();\n return (\n \n onRowSelectionChange({ row: props.row, checked: e.target.checked, isShiftClick: false })}\n />\n
\n );\n}\n\nconst CustomSelectColumn: Column = {\n key: SELECT_COLUMN_KEY,\n name: \"\",\n width: 40,\n minWidth: 40,\n maxWidth: 40,\n resizable: false,\n sortable: false,\n frozen: true,\n renderHeaderCell: (props) => ,\n renderCell: (props) => ,\n};\n\nfunction ErrorCell({ className, message, children }: { className: string; message: string; children: React.ReactNode }) {\n const ref = useRef(null);\n const [show, setShow] = useState(false);\n const [pos, setPos] = useState({ top: 0, left: 0 });\n\n function handleEnter() {\n if (ref.current) {\n const rect = ref.current.getBoundingClientRect();\n setPos({ top: rect.top - 6, left: rect.left + rect.width / 2 });\n }\n setShow(true);\n }\n\n return (\n <>\n setShow(false)}\n >\n {children}\n
\n {show && createPortal(\n ,\n document.body,\n )}\n >\n );\n}\n\ninterface Props {\n fields: ReadonlyArray>;\n initialRows: Array>>;\n schema?: ObjectSchema>>;\n rowHook?: RowHook;\n allowInvalidSubmit: boolean;\n translations: Translations[\"validationStep\"];\n alertTranslations: Translations[\"alerts\"][\"submitIncomplete\"];\n onBack: () => void;\n onSubmit: (result: ImportResult) => void | Promise;\n showTitle?: boolean;\n}\n\nexport function ValidationStep({\n fields,\n initialRows,\n schema,\n rowHook,\n allowInvalidSubmit,\n translations,\n alertTranslations,\n onBack,\n onSubmit,\n showTitle = true,\n}: Props) {\n const [rows, setRows] = useState>>([]);\n const [selected, setSelected] = useState>(new Set());\n const [filterErrors, setFilterErrors] = useState(false);\n const [submitting, setSubmitting] = useState(false);\n const [showConfirm, setShowConfirm] = useState(false);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n validateRows({ rows: initialRows, fields, schema, rowHook }).then((res) => {\n if (!cancelled) {\n setRows(res);\n setLoading(false);\n }\n });\n return () => {\n cancelled = true;\n };\n }, [initialRows, fields, schema, rowHook]);\n\n const columns = useMemo>[]>(() => {\n return [CustomSelectColumn as Column>, ...fields.map((f) => ({\n key: f.key,\n name: f.label,\n editable: true,\n resizable: true,\n renderEditCell: ({ row, onRowChange, onClose }) => (\n onRowChange({ ...row, [f.key]: e.target.value })}\n onBlur={() => onClose(true)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") onClose(true);\n if (e.key === \"Escape\") onClose(false);\n }}\n />\n ),\n renderCell: ({ row }) => {\n const value = (row as any)[f.key] as string | undefined;\n const err = row.__errors?.[f.key];\n if (!err) {\n return (\n \n {value ?? \"\"}\n
\n );\n }\n return (\n \n {value ?? \"\"}\n \n );\n },\n }))];\n }, [fields]);\n\n async function revalidate(next: Array>) {\n const stripped = next.map((r) => {\n const { __index, __errors, ...rest } = r as any;\n return rest as Partial>;\n });\n const validated = await validateRows({ rows: stripped, fields, schema, rowHook });\n setRows(validated);\n }\n\n const visibleRows = filterErrors ? rows.filter(rowHasErrors) : rows;\n\n const errorCount = rows.filter(rowHasErrors).length;\n\n function handleDiscard() {\n setRows((prev) => prev.filter((r) => !selected.has(r.__index)));\n setSelected(new Set());\n }\n\n async function doSubmit() {\n const valid = rows.filter((r) => !rowHasErrors(r));\n const invalid = rows.filter(rowHasErrors);\n const result: ImportResult = {\n validData: valid.map(({ __index, __errors, ...rest }) => rest as any),\n invalidData: invalid,\n all: rows,\n };\n setSubmitting(true);\n try {\n await onSubmit(result);\n } finally {\n setSubmitting(false);\n }\n }\n\n function handleSubmit() {\n if (errorCount > 0) {\n setShowConfirm(true);\n return;\n }\n void doSubmit();\n }\n\n return (\n \n
\n {showTitle ?
{translations.title} :
}\n
\n
setFilterErrors(e.target.checked)}\n />\n \n {translations.discardButtonTitle} ({selected.size})\n \n \n
\n\n {loading ? (\n
\n \n
\n ) : visibleRows.length === 0 ? (\n
\n {filterErrors ? translations.noRowsMessageWhenFiltered : translations.noRowsMessage}\n \n ) : (\n
\n , unknown, string>\n className=\"rdg-light\"\n columns={columns}\n rows={visibleRows}\n rowKeyGetter={(r: ImportedRow) => r.__index}\n selectedRows={selected as Set}\n onSelectedRowsChange={(rows: Set) => setSelected(rows)}\n onRowsChange={(updated: ImportedRow[]) => {\n const updatedByIndex = new Map(updated.map((r) => [r.__index, r]));\n const next = rows.map((r) => updatedByIndex.get(r.__index) ?? r);\n setRows(next);\n void revalidate(next);\n }}\n style={{ blockSize: \"100%\" }}\n />\n
\n )}\n\n {errorCount > 0 && (\n
\n {errorCount} row{errorCount === 1 ? \"\" : \"s\"} with errors\n
\n )}\n\n
\n \n {translations.backButtonTitle}\n \n 0)}>\n {submitting ? : translations.submitButtonTitle}\n \n
\n\n
setShowConfirm(false)} centered>\n \n {alertTranslations.headerTitle} \n \n \n {allowInvalidSubmit ? alertTranslations.bodyText : alertTranslations.bodyTextSubmitForbidden}\n \n \n setShowConfirm(false)}>\n {alertTranslations.cancelButtonTitle}\n \n {allowInvalidSubmit && (\n {\n setShowConfirm(false);\n void doSubmit();\n }}\n >\n {alertTranslations.finishButtonTitle}\n \n )}\n \n \n
\n );\n}\n","import type { CalculatedColumn, ColSpanArgs } from '../types';\n\nexport function getColSpan(\n column: CalculatedColumn,\n lastFrozenColumnIndex: number,\n args: ColSpanArgs\n): number | undefined {\n const colSpan = typeof column.colSpan === 'function' ? column.colSpan(args) : 1;\n if (\n Number.isInteger(colSpan) &&\n colSpan! > 1 &&\n // ignore colSpan if it spans over both frozen and regular columns\n (!column.frozen || column.idx + colSpan! - 1 <= lastFrozenColumnIndex)\n ) {\n return colSpan!;\n }\n return undefined;\n}\n","import type { Maybe } from '../types';\n\nexport function stopPropagation(event: React.SyntheticEvent) {\n event.stopPropagation();\n}\n\nexport function scrollIntoView(element: Maybe, behavior: ScrollBehavior = 'instant') {\n element?.scrollIntoView({ inline: 'nearest', block: 'nearest', behavior });\n}\n","import type { CellEvent } from '../types';\n\nexport function createCellEvent>(\n event: E\n): CellEvent {\n let defaultPrevented = false;\n const cellEvent = {\n ...event,\n preventGridDefault() {\n defaultPrevented = true;\n },\n isGridDefaultPrevented() {\n return defaultPrevented;\n }\n };\n\n Object.setPrototypeOf(cellEvent, Object.getPrototypeOf(event));\n\n return cellEvent;\n}\n","import type { Direction, Maybe } from '../types';\n\n// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values\nconst nonInputKeys = new Set([\n // Special keys\n 'Unidentified',\n // Modifier keys\n 'Alt',\n 'AltGraph',\n 'CapsLock',\n 'Control',\n 'Fn',\n 'FnLock',\n 'Meta',\n 'NumLock',\n 'ScrollLock',\n 'Shift',\n // Whitespace keys\n 'Tab',\n // Navigation keys\n 'ArrowDown',\n 'ArrowLeft',\n 'ArrowRight',\n 'ArrowUp',\n 'End',\n 'Home',\n 'PageDown',\n 'PageUp',\n // Editing\n 'Insert',\n // UI keys\n 'ContextMenu',\n 'Escape',\n 'Pause',\n 'Play',\n // Device keys\n 'PrintScreen',\n // Function keys\n 'F1',\n // 'F2', /!\\ specifically allowed, do not edit\n 'F3',\n 'F4',\n 'F5',\n 'F6',\n 'F7',\n 'F8',\n 'F9',\n 'F10',\n 'F11',\n 'F12'\n]);\n\nexport function isCtrlKeyHeldDown(e: React.KeyboardEvent): boolean {\n return (e.ctrlKey || e.metaKey) && e.key !== 'Control';\n}\n\n// event.key may differ by keyboard input language, so we use event.keyCode instead\n// event.nativeEvent.code cannot be used either as it would break copy/paste for the DVORAK layout\nconst vKey = 86;\n\nexport function isDefaultCellInput(\n event: React.KeyboardEvent,\n isUserHandlingPaste: boolean\n): boolean {\n // eslint-disable-next-line @typescript-eslint/no-deprecated\n if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isUserHandlingPaste)) return false;\n return !nonInputKeys.has(event.key);\n}\n\n/**\n * By default, the following navigation keys are enabled while an editor is open, under specific conditions:\n * - Tab:\n * - The editor must be an , a