Skip to content

Commit 3eec7a7

Browse files
authored
Merge branch 'main' into ts7
2 parents ef9f5a8 + 301ace7 commit 3eec7a7

6 files changed

Lines changed: 87 additions & 79 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
if-no-files-found: ignore
5454

5555
- name: Upload coverage
56-
uses: codecov/codecov-action@v5
56+
uses: codecov/codecov-action@v6
5757
with:
5858
token: ${{ secrets.CODECOV_TOKEN }}
5959

AGENTS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ website/ # demo site (Vite + TanStack Router)
4848
- **Light/dark mode** — handled via CSS `light-dark()` + `color-scheme`, not JS.
4949
- **Accessibility first** — ARIA attributes (e.g. `aria-colindex`, `aria-rowindex`, `aria-selected`, roles) are required. Tests query by role.
5050
- **Formatting** — oxfmt (not Prettier). **Linting** — ESLint (must pass with zero warnings).
51-
- **Build**Rolldown bundles library to `lib/`; `ecij` plugin prefixes classes with `rdg-{version}-` (dots→dashes) to avoid cross-version conflicts.
51+
- **Build**tsdown bundles library to `lib/`; `ecij` plugin prefixes classes with `rdg-{version}-` (dots→dashes) to avoid cross-version conflicts.
5252

5353
## Testing
5454

55-
- Browser tests use `vitest/browser` + Playwright. `test/setupBrowser.ts` configures `page.render()` via `vitest-browser-react` and registers custom locators via `locators.extend()` — prefer `page.getGrid()`, `page.getCell({ name })`, `page.getRow()`, `page.getHeaderCell()`, `page.getSelectedCell()`, etc. over raw `page.getByRole()`.
56-
- Test helpers in `test/browser/utils.tsx`: `setup()`, `getRowWithCell()`, `getCellsAtRowIndex()`, `validateCellPosition()`, `scrollGrid()`, `tabIntoGrid()`, `testCount()`, `testRowCount()`.
55+
- Browser tests use `vitest/browser` + Playwright. `test/setupBrowser.ts` configures `page.render()` via `vitest-browser-react` and registers custom locators via `locators.extend()` — prefer `page.getGrid()`, `page.getCell({ name })`, `page.getRow()`, `page.getHeaderCell()`, `page.getActiveCell()`, etc. over raw `page.getByRole()`.
56+
- Test helpers in `test/browser/utils.tsx`: `setup()`, `getRowWithCell()`, `getCellsAtRowIndex()`, `validateCellPosition()`, `scrollGrid()`, `safeTab()`, `testCount()`, `testRowCount()`.
5757
- `test/failOnConsole.ts` fails tests on unexpected console warnings/errors.
5858
- **Never run visual regression tests** — screenshots are environment-dependent so visual regression tests must run in CI only.
5959

eslint.config.js

Lines changed: 62 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import eslintReact from '@eslint-react/eslint-plugin';
22
import markdown from '@eslint/markdown';
33
import vitest from '@vitest/eslint-plugin';
44
import jestDom from 'eslint-plugin-jest-dom';
5-
import reactDom from 'eslint-plugin-react-dom';
65
import reactHooks from 'eslint-plugin-react-hooks';
7-
import reactNamingConvention from 'eslint-plugin-react-naming-convention';
8-
import reactRsc from 'eslint-plugin-react-rsc';
9-
import reactWebApi from 'eslint-plugin-react-web-api';
106
import sonarjs from 'eslint-plugin-sonarjs';
117
import testingLibrary from 'eslint-plugin-testing-library';
128
import { defineConfig, globalIgnores } from 'eslint/config';
@@ -29,10 +25,6 @@ export default defineConfig([
2925
// @ts-expect-error
3026
'react-hooks': reactHooks,
3127
'@eslint-react': eslintReact,
32-
'@eslint-react/rsc': reactRsc,
33-
'@eslint-react/dom': reactDom,
34-
'@eslint-react/web-api': reactWebApi,
35-
'@eslint-react/naming-convention': reactNamingConvention,
3628
sonarjs,
3729
'@typescript-eslint': tseslint.plugin
3830
},
@@ -288,7 +280,7 @@ export default defineConfig([
288280
// https://www.eslint-react.xyz/docs/rules/overview
289281
/*
290282
// copy all the rules from the rules table for easy pasting
291-
function getRules(id, prefix) {
283+
function getRules(id) {
292284
return (
293285
Iterator.from(
294286
document
@@ -298,38 +290,33 @@ function getRules(id, prefix) {
298290
.querySelectorAll('tr a')
299291
)
300292
// map link to rule declaration
301-
.map((a) => `'@eslint-react/${prefix}${a.textContent}': 1,`)
293+
.map((a) => `'@eslint-react/${a.getAttribute('href')}': 1,`)
302294
);
303295
}
304296
copy(
305297
Iterator.from([
306-
getRules('x-rules', ''),
307-
getRules('rsc-rules', 'rsc/'),
308-
getRules('dom-rules', 'dom/'),
309-
getRules('web-api-rules', 'web-api/'),
310-
getRules('naming-convention-rules', 'naming-convention/'),
298+
getRules('x-rules'),
299+
getRules('jsx-rules'),
300+
getRules('rsc-rules'),
301+
getRules('dom-rules'),
302+
getRules('web-api-rules'),
303+
getRules('naming-convention-rules'),
311304
])
312305
.flatMap((x) => x)
313306
.toArray()
314307
.join('\n')
315308
);
316309
*/
317-
'@eslint-react/jsx-dollar': 1,
318-
'@eslint-react/jsx-key-before-spread': 1,
319-
'@eslint-react/jsx-no-comment-textnodes': 1,
320-
'@eslint-react/jsx-shorthand-boolean': 1,
321-
'@eslint-react/jsx-shorthand-fragment': 1,
322310
'@eslint-react/component-hook-factories': 1,
323311
'@eslint-react/error-boundaries': 1,
324312
'@eslint-react/exhaustive-deps': 1,
325-
'@eslint-react/immutability': 0,
313+
'@eslint-react/immutability': 1,
326314
'@eslint-react/no-access-state-in-setstate': 1,
327315
'@eslint-react/no-array-index-key': 0,
328316
'@eslint-react/no-children-count': 1,
329317
'@eslint-react/no-children-for-each': 1,
330318
'@eslint-react/no-children-map': 1,
331319
'@eslint-react/no-children-only': 1,
332-
'@eslint-react/no-children-prop': 1,
333320
'@eslint-react/no-children-to-array': 1,
334321
'@eslint-react/no-class-component': 1,
335322
'@eslint-react/no-clone-element': 1,
@@ -367,7 +354,6 @@ copy(
367354
'@eslint-react/no-unused-props': 1,
368355
'@eslint-react/no-unused-state': 1,
369356
'@eslint-react/no-use-context': 1,
370-
'@eslint-react/no-useless-fragment': [1, { allowExpressions: false }],
371357
'@eslint-react/prefer-destructuring-assignment': 1,
372358
'@eslint-react/prefer-namespace-import': 1,
373359
'@eslint-react/purity': 1,
@@ -378,32 +364,37 @@ copy(
378364
'@eslint-react/unsupported-syntax': 1,
379365
'@eslint-react/use-memo': 1,
380366
'@eslint-react/use-state': 1,
381-
'@eslint-react/rsc/function-definition': 1,
382-
'@eslint-react/dom/no-dangerously-set-innerhtml': 1,
383-
'@eslint-react/dom/no-dangerously-set-innerhtml-with-children': 1,
384-
'@eslint-react/dom/no-find-dom-node': 1,
385-
'@eslint-react/dom/no-flush-sync': 0,
386-
'@eslint-react/dom/no-hydrate': 1,
387-
'@eslint-react/dom/no-missing-button-type': 1,
388-
'@eslint-react/dom/no-missing-iframe-sandbox': 1,
389-
'@eslint-react/dom/no-namespace': 1,
390-
'@eslint-react/dom/no-render': 1,
391-
'@eslint-react/dom/no-render-return-value': 1,
392-
'@eslint-react/dom/no-script-url': 1,
393-
'@eslint-react/dom/no-string-style-prop': 1,
394-
'@eslint-react/dom/no-unknown-property': 0,
395-
'@eslint-react/dom/no-unsafe-iframe-sandbox': 1,
396-
'@eslint-react/dom/no-unsafe-target-blank': 1,
397-
'@eslint-react/dom/no-use-form-state': 1,
398-
'@eslint-react/dom/no-void-elements-with-children': 1,
399-
'@eslint-react/dom/prefer-namespace-import': 1,
400-
'@eslint-react/web-api/no-leaked-event-listener': 1,
401-
'@eslint-react/web-api/no-leaked-interval': 1,
402-
'@eslint-react/web-api/no-leaked-resize-observer': 1,
403-
'@eslint-react/web-api/no-leaked-timeout': 1,
404-
'@eslint-react/naming-convention/context-name': 1,
405-
'@eslint-react/naming-convention/id-name': 1,
406-
'@eslint-react/naming-convention/ref-name': 1,
367+
'@eslint-react/jsx-no-children-prop': 1,
368+
'@eslint-react/jsx-no-children-prop-with-children': 1,
369+
'@eslint-react/jsx-no-comment-textnodes': 1,
370+
'@eslint-react/jsx-no-useless-fragment': [1, { allowExpressions: false }],
371+
'@eslint-react/jsx-no-key-after-spread': 1,
372+
'@eslint-react/jsx-no-namespace': 1,
373+
'@eslint-react/rsc-function-definition': 1,
374+
'@eslint-react/dom-no-dangerously-set-innerhtml': 1,
375+
'@eslint-react/dom-no-dangerously-set-innerhtml-with-children': 1,
376+
'@eslint-react/dom-no-find-dom-node': 1,
377+
'@eslint-react/dom-no-flush-sync': 0,
378+
'@eslint-react/dom-no-hydrate': 1,
379+
'@eslint-react/dom-no-missing-button-type': 1,
380+
'@eslint-react/dom-no-missing-iframe-sandbox': 1,
381+
'@eslint-react/dom-no-render': 1,
382+
'@eslint-react/dom-no-render-return-value': 1,
383+
'@eslint-react/dom-no-script-url': 1,
384+
'@eslint-react/dom-no-string-style-prop': 1,
385+
'@eslint-react/dom-no-unknown-property': 1,
386+
'@eslint-react/dom-no-unsafe-iframe-sandbox': 1,
387+
'@eslint-react/dom-no-unsafe-target-blank': 1,
388+
'@eslint-react/dom-no-use-form-state': 1,
389+
'@eslint-react/dom-no-void-elements-with-children': 1,
390+
'@eslint-react/dom-prefer-namespace-import': 1,
391+
'@eslint-react/web-api-no-leaked-event-listener': 1,
392+
'@eslint-react/web-api-no-leaked-interval': 1,
393+
'@eslint-react/web-api-no-leaked-resize-observer': 1,
394+
'@eslint-react/web-api-no-leaked-timeout': 1,
395+
'@eslint-react/naming-convention-context-name': 1,
396+
'@eslint-react/naming-convention-id-name': 1,
397+
'@eslint-react/naming-convention-ref-name': 1,
407398

408399
// SonarJS rules
409400
// https://github.com/SonarSource/SonarJS/blob/master/packages/jsts/src/rules/README.md#rules
@@ -1103,20 +1094,38 @@ copy(
11031094
name: 'markdown',
11041095
files: ['**/*.md'],
11051096
plugins: {
1106-
// @ts-expect-error
11071097
markdown
11081098
},
1109-
language: 'markdown/commonmark',
1099+
language: 'markdown/gfm',
11101100
rules: {
1101+
// `@eslint/markdown` rules
1102+
// https://github.com/eslint/markdown/blob/main/README.md#rules
1103+
/*
1104+
// copy all the rules from the rules table for easy pasting
1105+
copy(
1106+
Iterator.from(
1107+
document
1108+
// select rules table
1109+
.querySelector('.markdown-heading:has(> a[href="#rules"]) ~ markdown-accessiblity-table tbody')
1110+
// select all rule links
1111+
.querySelectorAll(':any-link')
1112+
)
1113+
// map link to rule declaration
1114+
.map((link) => `'markdown/${link.textContent}': 1,`)
1115+
.toArray()
1116+
.join('\n')
1117+
);
1118+
*/
11111119
'markdown/fenced-code-language': 1,
1120+
'markdown/fenced-code-meta': 0,
11121121
'markdown/heading-increment': 1,
11131122
'markdown/no-bare-urls': 1,
11141123
'markdown/no-duplicate-definitions': 1,
1115-
'markdown/no-duplicate-headings': 0,
1124+
'markdown/no-duplicate-headings': [1, { checkSiblingsOnly: true }],
11161125
'markdown/no-empty-definitions': 1,
11171126
'markdown/no-empty-images': 1,
11181127
'markdown/no-empty-links': 1,
1119-
'markdown/no-html': 0,
1128+
'markdown/no-html': [1, { allowed: ['br', 'kbd'] }],
11201129
'markdown/no-invalid-label-refs': 1,
11211130
'markdown/no-missing-atx-heading-space': 1,
11221131
'markdown/no-missing-label-refs': 1,

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"start": "vite serve --clearScreen false",
3636
"preview": "vite preview",
3737
"build:website": "vite build",
38-
"build": "rolldown -c",
38+
"build": "tsdown",
3939
"test": "vitest run --project browser --project node",
4040
"test:watch": "vitest watch --project browser --project node",
4141
"test:ci": "vitest run",
@@ -47,11 +47,12 @@
4747
"typecheck": "tsgo --build"
4848
},
4949
"devDependencies": {
50-
"@eslint-react/eslint-plugin": "^3.0.0",
51-
"@eslint/markdown": "^7.5.1",
50+
"@eslint-react/eslint-plugin": "^4.2.1",
51+
"@eslint/markdown": "^8.0.1",
5252
"@faker-js/faker": "^10.3.0",
5353
"@tanstack/react-router": "^1.166.7",
5454
"@tanstack/router-plugin": "^1.166.7",
55+
"@tsdown/css": "^0.21.7",
5556
"@types/node": "^25.5.0",
5657
"@types/react": "^19.2.14",
5758
"@types/react-dom": "^19.2.3",
@@ -69,13 +70,12 @@
6970
"eslint-plugin-testing-library": "^7.16.0",
7071
"jspdf": "^4.2.0",
7172
"jspdf-autotable": "^5.0.7",
72-
"oxfmt": "0.42.0",
73-
"playwright": "~1.58.0",
73+
"oxfmt": "0.43.0",
74+
"playwright": "~1.59.0",
7475
"postcss": "^8.5.2",
7576
"react": "^19.2.4",
7677
"react-dom": "^19.2.4",
77-
"rolldown": "1.0.0-rc.5",
78-
"rolldown-plugin-dts": "^0.22.5",
78+
"tsdown": "^0.21.7",
7979
"typescript": "~6.0.1-rc",
8080
"typescript-eslint": "^8.57.0",
8181
"vite": "^8.0.0",

tsconfig.vite.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
"lib": ["ESNext", "DOM"],
55
"skipLibCheck": true
66
},
7-
"include": ["package.json", "rolldown.config.ts", "vite.config.ts"]
7+
"include": ["package.json", "tsdown.config.ts", "vite.config.ts"]
88
}
Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
1-
import { isAbsolute } from 'node:path';
21
import { ecij } from 'ecij/plugin';
3-
import { defineConfig } from 'rolldown';
4-
import { dts } from 'rolldown-plugin-dts';
2+
import { defineConfig } from 'tsdown';
53

64
import pkg from './package.json' with { type: 'json' };
75

86
export default defineConfig({
9-
input: './src/index.ts',
10-
output: {
11-
dir: 'lib',
12-
cssEntryFileNames: 'styles.css',
13-
sourcemap: true,
14-
cleanDir: true
15-
},
7+
outDir: 'lib',
168
platform: 'neutral',
17-
external: (id) => !id.startsWith('.') && !isAbsolute(id),
9+
sourcemap: true,
10+
deps: {
11+
skipNodeModulesBundle: true
12+
},
13+
css: {
14+
fileName: 'styles.css'
15+
},
16+
dts: {
17+
build: true,
18+
tsconfig: './tsconfig.src.json'
19+
},
1820
plugins: [
1921
ecij({
2022
// We add the package version as prefix to avoid style conflicts
2123
// between multiple versions of RDG on the same page
2224
classPrefix: `rdg-${pkg.version.replaceAll('.', '-')}-`
23-
}),
24-
dts({
25-
tsconfig: './tsconfig.src.json'
2625
})
2726
]
2827
});

0 commit comments

Comments
 (0)