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 -
- -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/UgnisSoftware/react-spreadsheet-import/Node.js%20CI) -![GitHub](https://img.shields.io/github/license/UgnisSoftware/react-spreadsheet-import) [![npm](https://img.shields.io/npm/v/react-spreadsheet-import)](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` -
- -![rsi-preview](https://user-images.githubusercontent.com/45755753/159503528-90aacb69-128f-4ece-b45b-ab97d403a9d3.gif) - -## 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 - -``` - -Screenshot 2022-04-13 at 10 24 34 - -2.) Change all components of the same type, like all Buttons, at the same time - -```jsx - -``` - -Screenshot 2022-04-13 at 11 04 30 - -3.) Change components specifically in each Step. -```jsx - -``` -Screenshot 2022-04-13 at 10 21 58 - -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 \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}\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 \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 \n \n
\n\n setShowWarn(false)} centered>\n \n {alertTranslations.headerTitle}\n \n {alertTranslations.bodyText}\n \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 \n {rows.slice(0, 25).map((row, idx) => (\n setSelected(idx)}\n className={selected === idx ? \"table-primary\" : undefined}\n style={{ cursor: \"pointer\" }}\n >\n \n {row.map((cell, ci) => (\n \n ))}\n \n ))}\n \n
\n setSelected(idx)}\n onClick={(e) => e.stopPropagation()}\n />\n {cell}
\n
\n
\n \n \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
\n {workbook.sheets.map((s, i) => (\n setSelected(i)}\n />\n ))}\n \n
\n \n \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 ))}\n \n \n \n \n {fields.map((f) => (\n \n ))}\n \n \n
\n {f.label}\n {f.required && *}\n
\n {f.example ?? \"\"}\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
{message}
\n
\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
\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 \n
\n\n setShowConfirm(false)} centered>\n \n {alertTranslations.headerTitle}\n \n \n {allowInvalidSubmit ? alertTranslations.bodyText : alertTranslations.bodyTextSubmitForbidden}\n \n \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