From dc886c164008d5ada26f8d27c9acebac6d745e0a Mon Sep 17 00:00:00 2001 From: peter <20213436+petera2c@users.noreply.github.com> Date: Sun, 31 May 2026 09:56:25 -0500 Subject: [PATCH 1/4] Hover fix --- .github/workflows/publish.yml | 5 +- .github/workflows/test.yml | 34 ++ .../core/src/core/rendering/TableRenderer.ts | 4 +- packages/react/package.json | 5 +- .../react/src/__tests__/rowHover.test.tsx | 112 +++++ packages/react/vitest.config.ts | 20 + packages/react/vitest.setup.ts | 37 ++ pnpm-lock.yaml | 446 +++++++++++++++++- 8 files changed, 652 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 packages/react/src/__tests__/rowHover.test.tsx create mode 100644 packages/react/vitest.config.ts create mode 100644 packages/react/vitest.setup.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee1d66951..f6d0370f1 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,6 +12,9 @@ permissions: contents: write jobs: + tests: + uses: ./.github/workflows/test.yml + verify-examples: runs-on: ubuntu-latest steps: @@ -37,7 +40,7 @@ jobs: run: pnpm run build:examples publish: - needs: verify-examples + needs: [tests, verify-examples] runs-on: ubuntu-latest steps: - name: Checkout code diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..e838727e5 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: Test + +on: + pull_request: + push: + branches: + - main + - master + # Allow other workflows (e.g. publish) to gate on this suite. + workflow_call: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + # The React adapter Vitest suite aliases `simple-table-core` to the core + # *source*, so no build step is required. + - name: Run React adapter tests + run: pnpm --filter @simple-table/react run test diff --git a/packages/core/src/core/rendering/TableRenderer.ts b/packages/core/src/core/rendering/TableRenderer.ts index 4f0cd5d95..43654af89 100644 --- a/packages/core/src/core/rendering/TableRenderer.ts +++ b/packages/core/src/core/rendering/TableRenderer.ts @@ -455,7 +455,9 @@ export class TableRenderer { selectedRowCount, cellUpdateFlash: deps.config.cellUpdateFlash, useOddColumnBackground: deps.config.useOddColumnBackground, - useHoverRowBackground: deps.config.useHoverRowBackground, + // Defaults to true (documented default) so row hover works out of the box + // when consumers don't explicitly pass the flag. Explicit `false` is honored. + useHoverRowBackground: deps.config.useHoverRowBackground ?? true, useOddEvenRowBackground: deps.config.useOddEvenRowBackground, rowGrouping: deps.config.rowGrouping, headers: deps.effectiveHeaders, diff --git a/packages/react/package.json b/packages/react/package.json index 5519c059b..6a7a47bc8 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -7,6 +7,7 @@ "scripts": { "build": "rollup -c", "preview": "rollup -c -w", + "test": "vitest run", "version:patch": "npm version patch && git push && git push --tags", "version:minor": "npm version minor && git push && git push --tags", "version:major": "npm version major && git push && git push --tags" @@ -43,6 +44,7 @@ "@rollup/plugin-node-resolve": "^15.3.0", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "jsdom": "^24.1.3", "react": "^18.0.0", "react-dom": "^18.0.0", "rollup": "^2.79.2", @@ -51,7 +53,8 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.37.0", "tslib": "^2.8.1", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "vitest": "^2.1.9" }, "description": "React adapter for simple-table-core — use the Simple Table data grid with full React component support for renderers.", "repository": { diff --git a/packages/react/src/__tests__/rowHover.test.tsx b/packages/react/src/__tests__/rowHover.test.tsx new file mode 100644 index 000000000..b12b34862 --- /dev/null +++ b/packages/react/src/__tests__/rowHover.test.tsx @@ -0,0 +1,112 @@ +import { createElement } from "react"; +import { createRoot, type Root } from "react-dom/client"; +import { afterEach, describe, expect, it } from "vitest"; +import { SimpleTable } from "../index"; +import type { ReactHeaderObject, SimpleTableReactProps } from "../index"; + +const headers: ReactHeaderObject[] = [ + { accessor: "id", label: "ID", width: 80, type: "number" }, + { accessor: "name", label: "Name", width: 120, type: "string" }, +]; + +const rows = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, +]; + +let container: HTMLDivElement | null = null; +let root: Root | null = null; + +afterEach(() => { + root?.unmount(); + root = null; + container?.remove(); + container = null; +}); + +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +/** Poll the DOM until `selector` matches, or fail after `timeoutMs`. */ +async function waitForElement( + scope: HTMLElement, + selector: string, + timeoutMs = 3000, +): Promise { + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + const el = scope.querySelector(selector); + if (el) return el; + await wait(20); + } + throw new Error(`Timed out waiting for element: ${selector}`); +} + +/** Mount the React adapter and wait until the first body cell has rendered. */ +async function mountTable( + props: Partial, +): Promise<{ host: HTMLDivElement; firstCell: HTMLElement }> { + const host = document.createElement("div"); + document.body.appendChild(host); + container = host; + + root = createRoot(host); + root.render( + createElement(SimpleTable, { + defaultHeaders: headers, + rows, + getRowId: (p) => String((p.row as { id?: number })?.id), + height: "250px", + theme: "light", + ...props, + }), + ); + + // The adapter mounts the vanilla table asynchronously (queueMicrotask + rAF), + // so wait until a body cell has rendered. + let firstCell: HTMLElement; + try { + firstCell = await waitForElement(host, ".st-body-container .st-cell"); + } catch (e) { + // eslint-disable-next-line no-console + console.log("DEBUG innerHTML:\n", host.innerHTML.slice(0, 4000)); + throw e; + } + return { host, firstCell }; +} + +// "st-row-hovered" is the class the core toggles on every cell in a hovered +// row; the CSS rule `.st-cell.st-row-hovered { background-color: +// var(--st-hover-row-background-color) }` is what actually changes the color. +// Asserting on the class (rather than a computed color) keeps the test fast and +// independent of CSS loading, while still guarding the behaviour a user sees. +describe("SimpleTable (React adapter) — row hover styling", () => { + it("applies the hover class to a row's cells on mouseenter when useHoverRowBackground is enabled", async () => { + const { host, firstCell } = await mountTable({ useHoverRowBackground: true }); + + firstCell.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + await wait(50); + + expect(host.querySelectorAll(".st-cell.st-row-hovered").length).toBeGreaterThan(0); + }); + + it("removes the hover class when the pointer leaves the row", async () => { + const { host, firstCell } = await mountTable({ useHoverRowBackground: true }); + + firstCell.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + await wait(50); + expect(host.querySelectorAll(".st-cell.st-row-hovered").length).toBeGreaterThan(0); + + firstCell.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true })); + await wait(50); + expect(host.querySelectorAll(".st-cell.st-row-hovered").length).toBe(0); + }); + + it("does not apply the hover class when useHoverRowBackground is disabled", async () => { + const { host, firstCell } = await mountTable({ useHoverRowBackground: false }); + + firstCell.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true })); + await wait(50); + + expect(host.querySelectorAll(".st-cell.st-row-hovered").length).toBe(0); + }); +}); diff --git a/packages/react/vitest.config.ts b/packages/react/vitest.config.ts new file mode 100644 index 000000000..b4b3cacdc --- /dev/null +++ b/packages/react/vitest.config.ts @@ -0,0 +1,20 @@ +import { resolve } from "path"; +import { defineConfig } from "vitest/config"; + +// Test the adapter against the core *source* (not the built dist) so tests run +// without a prior build step. Mirrors the path alias in tsconfig.json. +export default defineConfig({ + resolve: { + alias: { + "simple-table-core": resolve(__dirname, "../core/src/index.ts"), + }, + }, + test: { + environment: "jsdom", + globals: true, + // The vanilla core imports a CSS bundle on load. We assert on DOM classes, + // not computed colors, so CSS processing is unnecessary here. + css: false, + setupFiles: ["./vitest.setup.ts"], + }, +}); diff --git a/packages/react/vitest.setup.ts b/packages/react/vitest.setup.ts new file mode 100644 index 000000000..48e351443 --- /dev/null +++ b/packages/react/vitest.setup.ts @@ -0,0 +1,37 @@ +// jsdom does not implement ResizeObserver, but the core DimensionManager +// constructs one when the table mounts and relies on its *initial* callback to +// read the container width and trigger the first render. A no-op stub leaves +// the table empty, so deliver one initial notification on observe(). +class ResizeObserverStub { + private callback: ResizeObserverCallback; + constructor(callback: ResizeObserverCallback) { + this.callback = callback; + } + observe(): void { + // Schedule on a microtask so the DimensionManager has finished wiring its + // subscriber before the (rAF-deferred) width notification fires. + queueMicrotask(() => this.callback([], this as unknown as ResizeObserver)); + } + unobserve(): void {} + disconnect(): void {} +} + +globalThis.ResizeObserver = ResizeObserverStub as unknown as typeof ResizeObserver; + +// The DimensionManager defers its width notification to requestAnimationFrame. +// jsdom may not provide rAF, so polyfill it with a macrotask fallback. +if (typeof globalThis.requestAnimationFrame !== "function") { + globalThis.requestAnimationFrame = ((cb: FrameRequestCallback) => + setTimeout(() => cb(performance.now()), 0) as unknown as number) as typeof requestAnimationFrame; + globalThis.cancelAnimationFrame = ((id: number) => + clearTimeout(id as unknown as ReturnType)) as typeof cancelAnimationFrame; +} + +// jsdom reports 0 for all layout box metrics. Give elements a non-zero width so +// the core's column virtualization renders body cells instead of an empty grid. +Object.defineProperty(HTMLElement.prototype, "clientWidth", { + configurable: true, + get() { + return 800; + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af3b7dcc1..683332353 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -452,6 +452,9 @@ importers: '@types/react-dom': specifier: ^18.0.0 version: 18.3.7(@types/react@18.3.28) + jsdom: + specifier: ^24.1.3 + version: 24.1.3 react: specifier: ^18.0.0 version: 18.3.1 @@ -479,6 +482,9 @@ importers: typescript: specifier: ^4.9.5 version: 4.9.5 + vitest: + specifier: ^2.1.9 + version: 2.1.9(@types/node@20.19.39)(jsdom@24.1.3)(lightningcss@1.32.0)(terser@5.46.1) packages/solid: dependencies: @@ -728,6 +734,9 @@ packages: react: '>=19.0.0' react-dom: '>=19.0.0' + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -3458,15 +3467,38 @@ packages: '@vitest/expect@2.0.5': resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@2.0.5': resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} '@vitest/pretty-format@2.1.9': resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + '@vitest/spy@2.0.5': resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + '@vitest/utils@2.0.5': resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} @@ -3573,6 +3605,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3864,6 +3900,10 @@ packages: resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + caching-transform@4.0.0: resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} engines: {node: '>=8'} @@ -4235,6 +4275,10 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -4249,6 +4293,10 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -4291,6 +4339,9 @@ packages: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} @@ -4760,6 +4811,10 @@ packages: resolution: {integrity: sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==} deprecated: ⚠️ The 'expect-playwright' package is deprecated. The Playwright core assertions (via @playwright/test) now cover the same functionality. Please migrate to built-in expect. See https://playwright.dev/docs/test-assertions for migration. + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5087,6 +5142,10 @@ packages: resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} engines: {node: '>=0.10.0'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-entities@2.3.3: resolution: {integrity: sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==} @@ -5131,6 +5190,14 @@ packages: htmlparser2@6.1.0: resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -5320,6 +5387,9 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} @@ -5622,6 +5692,15 @@ packages: resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} engines: {node: '>=12.0.0'} + jsdom@24.1.3: + resolution: {integrity: sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -5829,6 +5908,9 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -6147,6 +6229,9 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + nyc@15.1.0: resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} engines: {node: '>=8.9'} @@ -6314,6 +6399,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@2.0.1: resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} engines: {node: '>= 14.16'} @@ -6972,6 +7060,9 @@ packages: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -6986,6 +7077,9 @@ packages: resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -7369,6 +7463,9 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resend@6.10.0: resolution: {integrity: sha512-i7CwZpYj4Oho1RxsTpLcCUkO08+HiL4NXrm6jLJ2WzJ89UGI8eROSieLONJA3hnUrf1OYnCyfq5F6POnHUMv1Q==} engines: {node: '>=20'} @@ -7483,6 +7580,12 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -7514,6 +7617,10 @@ packages: resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} engines: {node: '>=11.0.0'} + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -7599,6 +7706,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -7685,9 +7795,15 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + standardwebhooks@1.0.0: resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -7896,6 +8012,9 @@ packages: svix@1.88.0: resolution: {integrity: sha512-vm/JrrUd3bVyBE+3L33TIyVSs8gS5fYx7lrISvKlDJXTYX1ACH4REX8P1tHxsSKoZi/rvifM1t0XRc5Vc45THw==} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwindcss@4.2.2: resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} @@ -7939,10 +8058,20 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + tinyrainbow@1.2.0: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} @@ -7965,6 +8094,14 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -8109,6 +8246,10 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -8129,6 +8270,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + url@0.11.4: resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==} engines: {node: '>= 0.4'} @@ -8167,6 +8311,11 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + vite-plugin-solid@2.11.11: resolution: {integrity: sha512-YMZCXsLw9kyuvQFEdwLP27fuTQJLmjNoHy90AOJnbRuJ6DwShUxKFo38gdFrWn9v11hnGicKCZEaeI/TFs6JKw==} peerDependencies: @@ -8216,6 +8365,31 @@ packages: vite: optional: true + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vue@3.5.31: resolution: {integrity: sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==} peerDependencies: @@ -8224,6 +8398,10 @@ packages: typescript: optional: true + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + wait-on@7.2.0: resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} engines: {node: '>=12.0.0'} @@ -8245,6 +8423,10 @@ packages: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + webpack-bundle-analyzer@4.10.1: resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==} engines: {node: '>= 10.13.0'} @@ -8288,6 +8470,10 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -8316,6 +8502,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -8366,9 +8557,16 @@ packages: utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + xml@1.0.1: resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -8562,6 +8760,14 @@ snapshots: react: 19.2.5 react-dom: 19.2.5(react@19.2.5) + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -11241,6 +11447,21 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1) + '@vitest/pretty-format@2.0.5': dependencies: tinyrainbow: 1.2.0 @@ -11249,10 +11470,25 @@ snapshots: dependencies: tinyrainbow: 1.2.0 + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + '@vitest/spy@2.0.5': dependencies: tinyspy: 3.0.2 + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + '@vitest/utils@2.0.5': dependencies: '@vitest/pretty-format': 2.0.5 @@ -11420,6 +11656,8 @@ snapshots: acorn@8.16.0: {} + agent-base@7.1.4: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -11801,6 +12039,8 @@ snapshots: bytes-iec@3.1.1: {} + cac@6.7.14: {} + caching-transform@4.0.0: dependencies: hasha: 5.2.2 @@ -12242,6 +12482,11 @@ snapshots: dependencies: css-tree: 2.2.1 + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + csstype@3.2.3: {} cwd@0.10.0: @@ -12253,6 +12498,11 @@ snapshots: data-uri-to-buffer@4.0.1: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -12287,6 +12537,8 @@ snapshots: decamelize@1.2.0: {} + decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 @@ -12704,8 +12956,8 @@ snapshots: '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.6.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.39.4(jiti@2.6.1)) @@ -12724,7 +12976,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -12735,22 +12987,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.10 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -12761,7 +13013,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4(jiti@2.6.1) eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.58.1(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)))(eslint@9.39.4(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -12975,6 +13227,8 @@ snapshots: expect-playwright@0.8.0: {} + expect-type@1.3.0: {} + expect@29.7.0: dependencies: '@jest/expect-utils': 29.7.0 @@ -13350,6 +13604,10 @@ snapshots: dependencies: parse-passwd: 1.0.0 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-entities@2.3.3: {} html-entities@2.6.0: {} @@ -13407,6 +13665,20 @@ snapshots: domutils: 2.8.0 entities: 2.2.0 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} iconv-lite@0.6.3: @@ -13573,6 +13845,8 @@ snapshots: is-plain-object@5.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-reference@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -14112,6 +14386,34 @@ snapshots: jsdoc-type-pratt-parser@4.8.0: {} + jsdom@24.1.3: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + form-data: 4.0.5 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.20.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -14265,6 +14567,8 @@ snapshots: dependencies: tslib: 2.8.1 + lru-cache@10.4.3: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -14747,6 +15051,8 @@ snapshots: dependencies: boolbase: 1.0.0 + nwsapi@2.2.23: {} + nyc@15.1.0: dependencies: '@istanbuljs/load-nyc-config': 1.1.0 @@ -14963,6 +15269,8 @@ snapshots: path-type@4.0.0: {} + pathe@1.1.2: {} + pathval@2.0.1: {} picocolors@1.1.1: {} @@ -15643,6 +15951,10 @@ snapshots: proxy-from-env@2.1.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + punycode@1.4.1: {} punycode@2.3.1: {} @@ -15653,6 +15965,8 @@ snapshots: dependencies: side-channel: 1.1.0 + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} randombytes@2.1.0: @@ -16181,6 +16495,8 @@ snapshots: require-main-filename@2.0.0: {} + requires-port@1.0.0: {} + resend@6.10.0: dependencies: postal-mime: 2.7.4 @@ -16342,6 +16658,10 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.0 fsevents: 2.3.3 + rrweb-cssom@0.7.1: {} + + rrweb-cssom@0.8.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -16377,6 +16697,10 @@ snapshots: sax@1.6.0: {} + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -16506,6 +16830,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} sirv@2.0.4: @@ -16610,11 +16936,15 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + standardwebhooks@1.0.0: dependencies: '@stablelib/base64': 1.0.1 fast-sha256: 1.3.0 + std-env@3.10.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -16835,6 +17165,8 @@ snapshots: standardwebhooks: 1.0.0 uuid: 10.0.0 + symbol-tree@3.2.4: {} + tailwindcss@4.2.2: {} tapable@2.3.2: {} @@ -16883,11 +17215,17 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinypool@1.1.1: {} + tinyrainbow@1.2.0: {} tinyspy@3.0.2: {} @@ -16902,6 +17240,17 @@ snapshots: totalist@3.0.1: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -17102,6 +17451,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universalify@0.2.0: {} + universalify@2.0.1: {} unplugin@1.16.1: @@ -17143,6 +17494,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + url@0.11.4: dependencies: punycode: 1.4.1 @@ -17184,6 +17540,24 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite-node@2.1.9(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.5.0)(solid-js@1.9.12)(vite@5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1)): dependencies: '@babel/core': 7.29.0 @@ -17214,6 +17588,42 @@ snapshots: optionalDependencies: vite: 5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1) + vitest@2.1.9(@types/node@20.19.39)(jsdom@24.1.3)(lightningcss@1.32.0)(terser@5.46.1): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1) + vite-node: 2.1.9(@types/node@20.19.39)(lightningcss@1.32.0)(terser@5.46.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.19.39 + jsdom: 24.1.3 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vue@3.5.31(typescript@4.9.5): dependencies: '@vue/compiler-dom': 3.5.31 @@ -17234,6 +17644,10 @@ snapshots: optionalDependencies: typescript: 5.9.3 + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + wait-on@7.2.0: dependencies: axios: 1.14.0 @@ -17263,6 +17677,8 @@ snapshots: web-streams-polyfill@3.3.3: {} + webidl-conversions@7.0.0: {} + webpack-bundle-analyzer@4.10.1: dependencies: '@discoveryjs/json-ext': 0.5.7 @@ -17373,6 +17789,11 @@ snapshots: whatwg-mimetype@4.0.0: {} + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -17424,6 +17845,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: @@ -17462,8 +17888,12 @@ snapshots: ws@8.20.0: {} + xml-name-validator@5.0.0: {} + xml@1.0.1: {} + xmlchars@2.2.0: {} + y18n@4.0.3: {} y18n@5.0.8: {} From 90ade6930805d51aa3e21713cf1a699140ee9d43 Mon Sep 17 00:00:00 2001 From: peter <20213436+petera2c@users.noreply.github.com> Date: Sun, 31 May 2026 10:10:08 -0500 Subject: [PATCH 2/4] Drag custom icon fix --- packages/core/src/core/rendering/TableRenderer.ts | 2 ++ .../src/utils/columnEditor/createColumnEditor.ts | 8 ++++++++ .../utils/columnEditor/createColumnEditorPopout.ts | 5 +++++ .../src/utils/columnEditor/createColumnEditorRow.ts | 13 ++++++++++++- 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/core/src/core/rendering/TableRenderer.ts b/packages/core/src/core/rendering/TableRenderer.ts index 43654af89..26f0a85b5 100644 --- a/packages/core/src/core/rendering/TableRenderer.ts +++ b/packages/core/src/core/rendering/TableRenderer.ts @@ -904,6 +904,7 @@ export class TableRenderer { searchPlaceholder: mergedColumnEditorConfig.searchPlaceholder, searchFunction: mergedColumnEditorConfig.searchFunction, columnEditorConfig: mergedColumnEditorConfig, + icons: deps.resolvedIcons, essentialAccessors: deps.essentialAccessors, setHeaders: (newHeaders: HeaderObject[]) => { deps.setHeaders(newHeaders); @@ -929,6 +930,7 @@ export class TableRenderer { searchPlaceholder: mergedColumnEditorConfig.searchPlaceholder, searchFunction: mergedColumnEditorConfig.searchFunction, columnEditorConfig: mergedColumnEditorConfig, + icons: deps.resolvedIcons, essentialAccessors: deps.essentialAccessors, setHeaders: (newHeaders: HeaderObject[]) => { deps.setHeaders(newHeaders); diff --git a/packages/core/src/utils/columnEditor/createColumnEditor.ts b/packages/core/src/utils/columnEditor/createColumnEditor.ts index c998549f1..4f961aadb 100644 --- a/packages/core/src/utils/columnEditor/createColumnEditor.ts +++ b/packages/core/src/utils/columnEditor/createColumnEditor.ts @@ -2,6 +2,7 @@ import HeaderObject from "../../types/HeaderObject"; import { ColumnEditorSearchFunction, ColumnEditorConfig } from "../../types/ColumnEditorConfig"; import { createColumnEditorPopout } from "./createColumnEditorPopout"; import { ColumnVisibilityState } from "../../types/ColumnVisibilityTypes"; +import { IconsConfig } from "../../types/IconsConfig"; import { COLUMN_EDIT_WIDTH } from "../../consts/general-consts"; export interface CreateColumnEditorOptions { @@ -13,6 +14,7 @@ export interface CreateColumnEditorOptions { searchPlaceholder: string; searchFunction?: ColumnEditorSearchFunction; columnEditorConfig: ColumnEditorConfig; + icons?: IconsConfig; essentialAccessors?: ReadonlySet; resetColumns?: () => void; setHeaders: (headers: HeaderObject[]) => void; @@ -31,6 +33,7 @@ export const createColumnEditor = (options: CreateColumnEditorOptions) => { searchPlaceholder, searchFunction, columnEditorConfig, + icons, essentialAccessors, resetColumns, setHeaders, @@ -69,6 +72,7 @@ export const createColumnEditor = (options: CreateColumnEditorOptions) => { searchPlaceholder, searchFunction, columnEditorConfig, + icons, essentialAccessors, resetColumns, setHeaders, @@ -100,6 +104,9 @@ export const createColumnEditor = (options: CreateColumnEditorOptions) => { if (newOptions.essentialAccessors !== undefined) { essentialAccessors = newOptions.essentialAccessors; } + if (newOptions.icons !== undefined) { + icons = newOptions.icons; + } if (newOptions.resetColumns !== undefined) { resetColumns = newOptions.resetColumns; } @@ -110,6 +117,7 @@ export const createColumnEditor = (options: CreateColumnEditorOptions) => { searchPlaceholder: newOptions.searchPlaceholder, searchFunction: newOptions.searchFunction, columnEditorConfig: newOptions.columnEditorConfig, + icons: newOptions.icons, essentialAccessors: newOptions.essentialAccessors, resetColumns: newOptions.resetColumns, setHeaders: newOptions.setHeaders, diff --git a/packages/core/src/utils/columnEditor/createColumnEditorPopout.ts b/packages/core/src/utils/columnEditor/createColumnEditorPopout.ts index f64811d65..d189511e1 100644 --- a/packages/core/src/utils/columnEditor/createColumnEditorPopout.ts +++ b/packages/core/src/utils/columnEditor/createColumnEditorPopout.ts @@ -4,6 +4,7 @@ import { ColumnEditorCustomRenderer } from "../../types/ColumnEditorCustomRender import { FlattenedHeader } from "../../types/FlattenedHeader"; import { createColumnEditorRow } from "./createColumnEditorRow"; import { ColumnVisibilityState } from "../../types/ColumnVisibilityTypes"; +import { IconsConfig } from "../../types/IconsConfig"; import { partitionRootHeadersByPin, PanelSection } from "../../utils/pinnedColumnUtils"; export interface CreateColumnEditorPopoutOptions { @@ -13,6 +14,7 @@ export interface CreateColumnEditorPopoutOptions { searchPlaceholder: string; searchFunction?: ColumnEditorSearchFunction; columnEditorConfig: ColumnEditorConfig; + icons?: IconsConfig; essentialAccessors?: ReadonlySet; setHeaders: (headers: HeaderObject[]) => void; onColumnVisibilityChange?: (state: ColumnVisibilityState) => void; @@ -142,6 +144,7 @@ export const createColumnEditorPopout = (initialOptions: CreateColumnEditorPopou searchPlaceholder, searchFunction, columnEditorConfig, + icons, essentialAccessors, setHeaders, onColumnVisibilityChange, @@ -355,6 +358,7 @@ export const createColumnEditorPopout = (initialOptions: CreateColumnEditorPopou setExpandedHeaders, setHoveredSeparatorIndex, columnEditorConfig, + icons, essentialAccessors: essentialAccessors ?? new Set(), headers, setHeaders, @@ -405,6 +409,7 @@ export const createColumnEditorPopout = (initialOptions: CreateColumnEditorPopou if (newOptions.searchPlaceholder !== undefined) searchPlaceholder = newOptions.searchPlaceholder; if (newOptions.searchFunction !== undefined) searchFunction = newOptions.searchFunction; + if (newOptions.icons !== undefined) icons = newOptions.icons; if (newOptions.essentialAccessors !== undefined) essentialAccessors = newOptions.essentialAccessors; if (newOptions.setHeaders !== undefined) setHeaders = newOptions.setHeaders; if (newOptions.onColumnVisibilityChange !== undefined) diff --git a/packages/core/src/utils/columnEditor/createColumnEditorRow.ts b/packages/core/src/utils/columnEditor/createColumnEditorRow.ts index 629d4c2c6..9d0367035 100644 --- a/packages/core/src/utils/columnEditor/createColumnEditorRow.ts +++ b/packages/core/src/utils/columnEditor/createColumnEditorRow.ts @@ -1,5 +1,6 @@ import HeaderObject from "../../types/HeaderObject"; import { ColumnEditorConfig } from "../../types/ColumnEditorConfig"; +import { IconsConfig } from "../../types/IconsConfig"; import { ColumnEditorRowRendererComponents } from "../../types/ColumnEditorRowRendererProps"; import { areAllChildrenHidden, @@ -56,6 +57,8 @@ export interface CreateColumnEditorRowOptions { setExpandedHeaders: (headers: Set) => void; setHoveredSeparatorIndex: (index: number | null) => void; columnEditorConfig: ColumnEditorConfig; + /** Resolved table icons; `icons.drag` overrides the default column-editor drag handle. */ + icons?: IconsConfig; essentialAccessors?: ReadonlySet; headers: HeaderObject[]; setHeaders: (headers: HeaderObject[]) => void; @@ -385,7 +388,15 @@ export const createColumnEditorRow = (options: CreateColumnEditorRowOptions): Cr const dragIcon = document.createElement("div"); dragIcon.className = "st-drag-icon-container"; - dragIcon.innerHTML = DRAG_ICON_SVG; + const customDragIcon = options.icons?.drag; + if (typeof customDragIcon === "string") { + dragIcon.innerHTML = customDragIcon; + } else if (customDragIcon instanceof HTMLElement || customDragIcon instanceof SVGSVGElement) { + dragIcon.appendChild(customDragIcon.cloneNode(true)); + } else { + dragIcon.innerHTML = DRAG_ICON_SVG; + } + const label = document.createElement("div"); label.className = "st-column-label-container"; From 112ec5ee14d58e9f716c8524a528624c4724a840 Mon Sep 17 00:00:00 2001 From: peter <20213436+petera2c@users.noreply.github.com> Date: Sun, 31 May 2026 10:14:05 -0500 Subject: [PATCH 3/4] Footer position top --- .../propDefinitions/simpleTableProps.ts | 9 ++++ .../angular/src/lib/SimpleTableComponent.ts | 2 + packages/core/src/core/SimpleTableVanilla.ts | 4 ++ packages/core/src/core/dom/DOMManager.ts | 41 ++++++++++++++++++- packages/core/src/index.ts | 2 + packages/core/src/styles/base.css | 5 +++ packages/core/src/types/FooterPosition.ts | 4 ++ packages/core/src/types/SimpleTableConfig.ts | 3 ++ packages/core/src/types/SimpleTableProps.ts | 2 + .../tests/24-FooterRendererTests.stories.ts | 35 +++++++++++++++- 10 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/types/FooterPosition.ts diff --git a/apps/marketing/src/constants/propDefinitions/simpleTableProps.ts b/apps/marketing/src/constants/propDefinitions/simpleTableProps.ts index 0949e395d..823057e59 100644 --- a/apps/marketing/src/constants/propDefinitions/simpleTableProps.ts +++ b/apps/marketing/src/constants/propDefinitions/simpleTableProps.ts @@ -240,6 +240,15 @@ initialSortDirection="asc"`, type: "boolean", example: `hideFooter={true}`, }, + { + key: "footerPosition", + name: "footerPosition", + required: false, + description: + 'Placement of the pagination footer (built-in or footerRenderer). Use "top" to render above the table body; default is "bottom".', + type: '"top" | "bottom"', + example: `footerPosition="top"`, + }, { key: "quickFilter", name: "quickFilter", diff --git a/packages/angular/src/lib/SimpleTableComponent.ts b/packages/angular/src/lib/SimpleTableComponent.ts index 407881428..62acb936e 100644 --- a/packages/angular/src/lib/SimpleTableComponent.ts +++ b/packages/angular/src/lib/SimpleTableComponent.ts @@ -87,6 +87,7 @@ export class SimpleTableComponent implements OnInit, OnChanges, OnDestroy { @Input() columnBorders?: SimpleTableAngularProps["columnBorders"]; @Input() rowButtons?: SimpleTableAngularProps["rowButtons"]; @Input() hideFooter?: SimpleTableAngularProps["hideFooter"]; + @Input() footerPosition?: SimpleTableAngularProps["footerPosition"]; @Input() initialSortColumn?: SimpleTableAngularProps["initialSortColumn"]; @Input() initialSortDirection?: SimpleTableAngularProps["initialSortDirection"]; @Input() expandAll?: SimpleTableAngularProps["expandAll"]; @@ -194,6 +195,7 @@ export class SimpleTableComponent implements OnInit, OnChanges, OnDestroy { if (this.columnBorders !== undefined) props.columnBorders = this.columnBorders; if (this.rowButtons !== undefined) props.rowButtons = this.rowButtons; if (this.hideFooter !== undefined) props.hideFooter = this.hideFooter; + if (this.footerPosition !== undefined) props.footerPosition = this.footerPosition; if (this.initialSortColumn !== undefined) props.initialSortColumn = this.initialSortColumn; if (this.initialSortDirection !== undefined) props.initialSortDirection = this.initialSortDirection; diff --git a/packages/core/src/core/SimpleTableVanilla.ts b/packages/core/src/core/SimpleTableVanilla.ts index f9c9a665f..dc3999bd1 100644 --- a/packages/core/src/core/SimpleTableVanilla.ts +++ b/packages/core/src/core/SimpleTableVanilla.ts @@ -1165,6 +1165,10 @@ export class SimpleTableVanilla { this.domManager.updateTheme(config.theme); } + if (config.footerPosition !== undefined) { + this.domManager.syncFooterPosition(this.config.footerPosition); + } + if (config.customTheme !== undefined) { const previousTheme = this.customTheme; this.customTheme = TableInitializer.mergeCustomTheme(this.config); diff --git a/packages/core/src/core/dom/DOMManager.ts b/packages/core/src/core/dom/DOMManager.ts index 513e00b2d..d58d9396f 100644 --- a/packages/core/src/core/dom/DOMManager.ts +++ b/packages/core/src/core/dom/DOMManager.ts @@ -78,6 +78,11 @@ export class DOMManager { const footerContainer = document.createElement("div"); footerContainer.id = "st-footer-container"; + const footerAtTop = config.footerPosition === "top"; + if (footerAtTop) { + rootElement.classList.add("st-footer-position-top"); + } + const ariaLiveRegion = document.createElement("div"); ariaLiveRegion.setAttribute("aria-live", "polite"); ariaLiveRegion.setAttribute("aria-atomic", "true"); @@ -88,8 +93,13 @@ export class DOMManager { contentWrapper.appendChild(content); - wrapperContainer.appendChild(contentWrapper); - wrapperContainer.appendChild(footerContainer); + if (footerAtTop) { + wrapperContainer.appendChild(footerContainer); + wrapperContainer.appendChild(contentWrapper); + } else { + wrapperContainer.appendChild(contentWrapper); + wrapperContainer.appendChild(footerContainer); + } rootElement.appendChild(wrapperContainer); rootElement.appendChild(ariaLiveRegion); @@ -117,6 +127,33 @@ export class DOMManager { root.className = `${classes} theme-${theme}`; } + /** Reorders footer vs content when `footerPosition` changes after mount. */ + syncFooterPosition(footerPosition: SimpleTableConfig["footerPosition"]): void { + if (!this.elements) return; + + const { rootElement, wrapperContainer, contentWrapper, footerContainer } = this.elements; + const atTop = footerPosition === "top"; + rootElement.classList.toggle("st-footer-position-top", atTop); + + const scrollbar = wrapperContainer.querySelector(".st-horizontal-scrollbar-container"); + + if (atTop) { + if (footerContainer !== wrapperContainer.firstElementChild) { + wrapperContainer.insertBefore(footerContainer, contentWrapper); + } + if (scrollbar) { + wrapperContainer.appendChild(scrollbar); + } + return; + } + + if (scrollbar && scrollbar.parentElement === wrapperContainer) { + wrapperContainer.insertBefore(footerContainer, scrollbar); + } else if (footerContainer !== wrapperContainer.lastElementChild) { + wrapperContainer.appendChild(footerContainer); + } + } + getElements(): DOMElements | null { return this.elements; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index af8a972ba..82101a566 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -59,6 +59,7 @@ import type HeaderDropdownProps from "./types/HeaderDropdownProps"; import type { HeaderDropdown } from "./types/HeaderDropdownProps"; import type { RowButtonProps } from "./types/RowButton"; import type FooterRendererProps from "./types/FooterRendererProps"; +import type { FooterPosition } from "./types/FooterPosition"; import type { LoadingStateRenderer, ErrorStateRenderer, @@ -117,6 +118,7 @@ export type { ExportValueProps, FilterCondition, FooterRendererProps, + FooterPosition, GetRowId, GetRowIdParams, IconsConfig, diff --git a/packages/core/src/styles/base.css b/packages/core/src/styles/base.css index e3b3a454b..cdf857a73 100644 --- a/packages/core/src/styles/base.css +++ b/packages/core/src/styles/base.css @@ -968,6 +968,11 @@ input { gap: var(--st-spacing-medium); } +.simple-table-root.st-footer-position-top .st-footer { + border-top: none; + border-bottom: var(--st-border-width) solid var(--st-border-color); +} + .st-footer-info { display: flex; align-items: center; diff --git a/packages/core/src/types/FooterPosition.ts b/packages/core/src/types/FooterPosition.ts new file mode 100644 index 000000000..f4a39a744 --- /dev/null +++ b/packages/core/src/types/FooterPosition.ts @@ -0,0 +1,4 @@ +/** Where the pagination footer (default or `footerRenderer`) is placed in the table chrome. */ +export type FooterPosition = "top" | "bottom"; + +export default FooterPosition; diff --git a/packages/core/src/types/SimpleTableConfig.ts b/packages/core/src/types/SimpleTableConfig.ts index a28414c0f..7a22fabce 100644 --- a/packages/core/src/types/SimpleTableConfig.ts +++ b/packages/core/src/types/SimpleTableConfig.ts @@ -23,6 +23,7 @@ import { ColumnEditorConfig } from "./ColumnEditorConfig"; import { VanillaIconsConfig } from "./IconsConfig"; import { QuickFilterConfig } from "./QuickFilterTypes"; import { AnimationsConfig } from "./AnimationsConfig"; +import type { FooterPosition } from "./FooterPosition"; export interface SimpleTableConfig { animations?: AnimationsConfig; @@ -48,6 +49,8 @@ export interface SimpleTableConfig { externalFilterHandling?: boolean; externalSortHandling?: boolean; footerRenderer?: (props: FooterRendererProps) => HTMLElement | string | null; + /** Placement of the pagination footer. Default `"bottom"`. */ + footerPosition?: FooterPosition; headerDropdown?: VanillaHeaderDropdown; height?: string | number; hideFooter?: boolean; diff --git a/packages/core/src/types/SimpleTableProps.ts b/packages/core/src/types/SimpleTableProps.ts index d7bd19d40..3cfcd0e30 100644 --- a/packages/core/src/types/SimpleTableProps.ts +++ b/packages/core/src/types/SimpleTableProps.ts @@ -24,6 +24,7 @@ import { ColumnEditorConfig } from "./ColumnEditorConfig"; import { IconsConfig } from "./IconsConfig"; import { QuickFilterConfig } from "./QuickFilterTypes"; import { AnimationsConfig } from "./AnimationsConfig"; +import type { FooterPosition } from "./FooterPosition"; export interface SimpleTableProps { animations?: AnimationsConfig; // Cell animation configuration (FLIP-style on sort and programmatic column reorder). Defaults: enabled=true, duration=240ms, easing=cubic-bezier(0.2, 0.8, 0.2, 1). @@ -49,6 +50,7 @@ export interface SimpleTableProps { externalFilterHandling?: boolean; // Flag to let consumer handle filter logic completely externalSortHandling?: boolean; // Flag to let consumer handle sort logic completely footerRenderer?: (props: FooterRendererProps) => HTMLElement | string | null; // Custom footer renderer + footerPosition?: FooterPosition; // Pagination footer placement (default "bottom") headerDropdown?: HeaderDropdown; // Custom dropdown component for headers height?: string | number; // Height of the table hideFooter?: boolean; // Flag for hiding the footer diff --git a/packages/core/stories/tests/24-FooterRendererTests.stories.ts b/packages/core/stories/tests/24-FooterRendererTests.stories.ts index 5a441bf23..00540c7ba 100644 --- a/packages/core/stories/tests/24-FooterRendererTests.stories.ts +++ b/packages/core/stories/tests/24-FooterRendererTests.stories.ts @@ -1,6 +1,6 @@ /** * FOOTER RENDERER TESTS - * Tests for table footer: default footer with pagination, hideFooter. + * Tests for table footer: default footer with pagination, hideFooter, footerPosition. */ import type { Meta } from "@storybook/html"; @@ -16,7 +16,7 @@ const meta: Meta = { docs: { description: { component: - "Tests for table footer: default footer with pagination info and navigation, hideFooter.", + "Tests for table footer: default footer with pagination info and navigation, hideFooter, footerPosition top.", }, }, }, @@ -71,6 +71,37 @@ export const FooterShowsCorrectRange = { }, }; +export const FooterPositionTop = { + render: () => { + const { wrapper } = renderVanillaTable(headers, createData(25), { + getRowId: (p) => String(p.row?.id), + height: "300px", + shouldPaginate: true, + rowsPerPage: 10, + footerPosition: "top", + }); + return wrapper; + }, + play: async ({ canvasElement }: { canvasElement: HTMLElement }) => { + await waitForTable(); + const root = canvasElement.querySelector(".simple-table-root"); + expect(root?.classList.contains("st-footer-position-top")).toBe(true); + + const wrapperContainer = canvasElement.querySelector(".st-wrapper-container"); + expect(wrapperContainer?.firstElementChild?.id).toBe("st-footer-container"); + + const footer = canvasElement.querySelector(".st-footer"); + expect(footer).toBeTruthy(); + expect(footer?.querySelector(".st-footer-pagination")).toBeTruthy(); + + const header = canvasElement.querySelector(".st-header-container"); + const footerContainer = canvasElement.querySelector("#st-footer-container"); + expect(footerContainer && header && footerContainer.compareDocumentPosition(header)).toBe( + Node.DOCUMENT_POSITION_FOLLOWING, + ); + }, +}; + export const HideFooterHidesFooter = { render: () => { const { wrapper } = renderVanillaTable(headers, createData(10), { From 3315efb75be45b42204f7882d33c43535a8f7392 Mon Sep 17 00:00:00 2001 From: peter <20213436+petera2c@users.noreply.github.com> Date: Sun, 31 May 2026 10:28:23 -0500 Subject: [PATCH 4/4] Row position className --- .../docs-pages/FooterRendererContent.tsx | 17 ++++++++++++++ apps/marketing/src/constants/changelog.ts | 22 +++++++++++++++++++ packages/angular/package.json | 2 +- packages/core/package.json | 2 +- packages/core/src/utils/bodyCell/styling.ts | 4 ++++ .../core/src/utils/nestedGridRowRenderer.ts | 2 +- packages/core/src/utils/stateRowRenderer.ts | 2 +- packages/react/package.json | 2 +- packages/solid/package.json | 2 +- packages/svelte/package.json | 2 +- packages/vue/package.json | 2 +- 11 files changed, 51 insertions(+), 8 deletions(-) diff --git a/apps/marketing/src/components/pages/docs-pages/FooterRendererContent.tsx b/apps/marketing/src/components/pages/docs-pages/FooterRendererContent.tsx index 585a2d5d8..9bd65a07c 100644 --- a/apps/marketing/src/components/pages/docs-pages/FooterRendererContent.tsx +++ b/apps/marketing/src/components/pages/docs-pages/FooterRendererContent.tsx @@ -99,6 +99,23 @@ const FooterRendererContent = () => { footer.

+

+ Set{" "} + + footerPosition="top" + {" "} + to render the footer above the table body instead of below it. This applies to both the + default footer and a custom{" "} + + footerRenderer + + , and defaults to{" "} + + "bottom" + + . +

+ diff --git a/apps/marketing/src/constants/changelog.ts b/apps/marketing/src/constants/changelog.ts index 02862afb6..6dcbd7aa5 100644 --- a/apps/marketing/src/constants/changelog.ts +++ b/apps/marketing/src/constants/changelog.ts @@ -10,6 +10,27 @@ export interface ChangelogEntry { link?: string; }[]; } +export const v3_6_3: ChangelogEntry = { + version: "3.6.3", + date: "2026-05-31", + title: "footerPosition prop", + description: + 'New footerPosition prop renders the pagination footer (built-in or footerRenderer) above the table body when set to "top".', + changes: [ + { + type: "feature", + description: + 'New footerPosition prop ("top" | "bottom", default "bottom") controls placement of the pagination footer.', + link: "/docs/footer-renderer", + }, + { + type: "feature", + description: + "Added a st-row-position-{position} class to every rendered row (body cells, state rows, and nested-grid rows), letting consumers style any specific row via CSS (e.g. .st-row-position-3 { ... }).", + }, + ], +}; + export const v3_6_2: ChangelogEntry = { version: "3.6.2", date: "2026-05-16", @@ -1743,6 +1764,7 @@ export const v1_4_4: ChangelogEntry = { // Array of all changelog entries (newest first) export const CHANGELOG_ENTRIES: ChangelogEntry[] = [ + v3_6_3, v3_6_2, v3_6_0, v3_5_3, diff --git a/packages/angular/package.json b/packages/angular/package.json index 5e4b31a4d..b0a02b717 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@simple-table/angular", - "version": "3.6.2", + "version": "3.6.3", "main": "dist/cjs/index.js", "module": "dist/index.es.js", "types": "dist/types/angular/src/index.d.ts", diff --git a/packages/core/package.json b/packages/core/package.json index b80a13410..21bc8c3ab 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "simple-table-core", - "version": "3.6.2", + "version": "3.6.3", "main": "dist/cjs/index.js", "module": "dist/index.es.js", "types": "dist/src/index.d.ts", diff --git a/packages/core/src/utils/bodyCell/styling.ts b/packages/core/src/utils/bodyCell/styling.ts index e7f6ca3eb..5df473ac7 100644 --- a/packages/core/src/utils/bodyCell/styling.ts +++ b/packages/core/src/utils/bodyCell/styling.ts @@ -111,6 +111,10 @@ const calculateBodyCellClasses = (cell: AbsoluteBodyCell, context: CellRenderCon // Build class names return [ "st-cell", + // Stable, scroll-independent row position so consumers can target any row + // (e.g. `.st-row-position-3`). Uses the absolute table position rather than + // the virtualized slice index, which changes as rows are reused on scroll. + `st-row-position-${cell.tableRow.position}`, depth > 0 && header.expandable ? `st-cell-depth-${depth}` : "", isIndividuallySelected ? isInitialFocused diff --git a/packages/core/src/utils/nestedGridRowRenderer.ts b/packages/core/src/utils/nestedGridRowRenderer.ts index 348041ae1..def73e265 100644 --- a/packages/core/src/utils/nestedGridRowRenderer.ts +++ b/packages/core/src/utils/nestedGridRowRenderer.ts @@ -91,7 +91,7 @@ export function createNestedGridRow( }); const rowElement = document.createElement("div"); - rowElement.className = "st-row st-nested-grid-row"; + rowElement.className = `st-row st-nested-grid-row st-row-position-${tableRow.position}`; rowElement.dataset.index = String(tableRow.position); rowElement.style.position = "absolute"; rowElement.style.left = "0"; diff --git a/packages/core/src/utils/stateRowRenderer.ts b/packages/core/src/utils/stateRowRenderer.ts index 71fe32831..544319629 100644 --- a/packages/core/src/utils/stateRowRenderer.ts +++ b/packages/core/src/utils/stateRowRenderer.ts @@ -52,7 +52,7 @@ export const createStateRow = ( } const rowElement = document.createElement("div"); - rowElement.className = "st-row st-state-row"; + rowElement.className = `st-row st-state-row st-row-position-${position}`; rowElement.dataset.index = String(context.index); rowElement.style.width = "100%"; diff --git a/packages/react/package.json b/packages/react/package.json index 6a7a47bc8..6d8996f3e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@simple-table/react", - "version": "3.6.2", + "version": "3.6.3", "main": "dist/cjs/index.js", "module": "dist/index.es.js", "types": "dist/types/react/src/index.d.ts", diff --git a/packages/solid/package.json b/packages/solid/package.json index db935c013..01a2d7e71 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -1,6 +1,6 @@ { "name": "@simple-table/solid", - "version": "3.6.2", + "version": "3.6.3", "main": "dist/cjs/index.js", "module": "dist/index.es.js", "types": "dist/types/solid/src/index.d.ts", diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 77033d882..f776563eb 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -1,6 +1,6 @@ { "name": "@simple-table/svelte", - "version": "3.6.2", + "version": "3.6.3", "main": "dist/cjs/index.js", "module": "dist/index.es.js", "types": "dist/types/index.d.ts", diff --git a/packages/vue/package.json b/packages/vue/package.json index 741fbf1f1..b7a9df33e 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@simple-table/vue", - "version": "3.6.2", + "version": "3.6.3", "main": "dist/cjs/index.js", "module": "dist/index.es.js", "types": "dist/types/vue/src/index.d.ts",