Skip to content

Commit 31aafb1

Browse files
authored
Merge branch 'main' into untl
2 parents 39cc337 + 301ace7 commit 31aafb1

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
@@ -1,11 +1,7 @@
11
import eslintReact from '@eslint-react/eslint-plugin';
22
import markdown from '@eslint/markdown';
33
import vitest from '@vitest/eslint-plugin';
4-
import reactDom from 'eslint-plugin-react-dom';
54
import reactHooks from 'eslint-plugin-react-hooks';
6-
import reactNamingConvention from 'eslint-plugin-react-naming-convention';
7-
import reactRsc from 'eslint-plugin-react-rsc';
8-
import reactWebApi from 'eslint-plugin-react-web-api';
95
import sonarjs from 'eslint-plugin-sonarjs';
106
import { defineConfig, globalIgnores } from 'eslint/config';
117
import tseslint from 'typescript-eslint';
@@ -27,10 +23,6 @@ export default defineConfig([
2723
// @ts-expect-error
2824
'react-hooks': reactHooks,
2925
'@eslint-react': eslintReact,
30-
'@eslint-react/rsc': reactRsc,
31-
'@eslint-react/dom': reactDom,
32-
'@eslint-react/web-api': reactWebApi,
33-
'@eslint-react/naming-convention': reactNamingConvention,
3426
sonarjs,
3527
'@typescript-eslint': tseslint.plugin
3628
},
@@ -286,7 +278,7 @@ export default defineConfig([
286278
// https://www.eslint-react.xyz/docs/rules/overview
287279
/*
288280
// copy all the rules from the rules table for easy pasting
289-
function getRules(id, prefix) {
281+
function getRules(id) {
290282
return (
291283
Iterator.from(
292284
document
@@ -296,38 +288,33 @@ function getRules(id, prefix) {
296288
.querySelectorAll('tr a')
297289
)
298290
// map link to rule declaration
299-
.map((a) => `'@eslint-react/${prefix}${a.textContent}': 1,`)
291+
.map((a) => `'@eslint-react/${a.getAttribute('href')}': 1,`)
300292
);
301293
}
302294
copy(
303295
Iterator.from([
304-
getRules('x-rules', ''),
305-
getRules('rsc-rules', 'rsc/'),
306-
getRules('dom-rules', 'dom/'),
307-
getRules('web-api-rules', 'web-api/'),
308-
getRules('naming-convention-rules', 'naming-convention/'),
296+
getRules('x-rules'),
297+
getRules('jsx-rules'),
298+
getRules('rsc-rules'),
299+
getRules('dom-rules'),
300+
getRules('web-api-rules'),
301+
getRules('naming-convention-rules'),
309302
])
310303
.flatMap((x) => x)
311304
.toArray()
312305
.join('\n')
313306
);
314307
*/
315-
'@eslint-react/jsx-dollar': 1,
316-
'@eslint-react/jsx-key-before-spread': 1,
317-
'@eslint-react/jsx-no-comment-textnodes': 1,
318-
'@eslint-react/jsx-shorthand-boolean': 1,
319-
'@eslint-react/jsx-shorthand-fragment': 1,
320308
'@eslint-react/component-hook-factories': 1,
321309
'@eslint-react/error-boundaries': 1,
322310
'@eslint-react/exhaustive-deps': 1,
323-
'@eslint-react/immutability': 0,
311+
'@eslint-react/immutability': 1,
324312
'@eslint-react/no-access-state-in-setstate': 1,
325313
'@eslint-react/no-array-index-key': 0,
326314
'@eslint-react/no-children-count': 1,
327315
'@eslint-react/no-children-for-each': 1,
328316
'@eslint-react/no-children-map': 1,
329317
'@eslint-react/no-children-only': 1,
330-
'@eslint-react/no-children-prop': 1,
331318
'@eslint-react/no-children-to-array': 1,
332319
'@eslint-react/no-class-component': 1,
333320
'@eslint-react/no-clone-element': 1,
@@ -365,7 +352,6 @@ copy(
365352
'@eslint-react/no-unused-props': 1,
366353
'@eslint-react/no-unused-state': 1,
367354
'@eslint-react/no-use-context': 1,
368-
'@eslint-react/no-useless-fragment': [1, { allowExpressions: false }],
369355
'@eslint-react/prefer-destructuring-assignment': 1,
370356
'@eslint-react/prefer-namespace-import': 1,
371357
'@eslint-react/purity': 1,
@@ -376,32 +362,37 @@ copy(
376362
'@eslint-react/unsupported-syntax': 1,
377363
'@eslint-react/use-memo': 1,
378364
'@eslint-react/use-state': 1,
379-
'@eslint-react/rsc/function-definition': 1,
380-
'@eslint-react/dom/no-dangerously-set-innerhtml': 1,
381-
'@eslint-react/dom/no-dangerously-set-innerhtml-with-children': 1,
382-
'@eslint-react/dom/no-find-dom-node': 1,
383-
'@eslint-react/dom/no-flush-sync': 0,
384-
'@eslint-react/dom/no-hydrate': 1,
385-
'@eslint-react/dom/no-missing-button-type': 1,
386-
'@eslint-react/dom/no-missing-iframe-sandbox': 1,
387-
'@eslint-react/dom/no-namespace': 1,
388-
'@eslint-react/dom/no-render': 1,
389-
'@eslint-react/dom/no-render-return-value': 1,
390-
'@eslint-react/dom/no-script-url': 1,
391-
'@eslint-react/dom/no-string-style-prop': 1,
392-
'@eslint-react/dom/no-unknown-property': 0,
393-
'@eslint-react/dom/no-unsafe-iframe-sandbox': 1,
394-
'@eslint-react/dom/no-unsafe-target-blank': 1,
395-
'@eslint-react/dom/no-use-form-state': 1,
396-
'@eslint-react/dom/no-void-elements-with-children': 1,
397-
'@eslint-react/dom/prefer-namespace-import': 1,
398-
'@eslint-react/web-api/no-leaked-event-listener': 1,
399-
'@eslint-react/web-api/no-leaked-interval': 1,
400-
'@eslint-react/web-api/no-leaked-resize-observer': 1,
401-
'@eslint-react/web-api/no-leaked-timeout': 1,
402-
'@eslint-react/naming-convention/context-name': 1,
403-
'@eslint-react/naming-convention/id-name': 1,
404-
'@eslint-react/naming-convention/ref-name': 1,
365+
'@eslint-react/jsx-no-children-prop': 1,
366+
'@eslint-react/jsx-no-children-prop-with-children': 1,
367+
'@eslint-react/jsx-no-comment-textnodes': 1,
368+
'@eslint-react/jsx-no-useless-fragment': [1, { allowExpressions: false }],
369+
'@eslint-react/jsx-no-key-after-spread': 1,
370+
'@eslint-react/jsx-no-namespace': 1,
371+
'@eslint-react/rsc-function-definition': 1,
372+
'@eslint-react/dom-no-dangerously-set-innerhtml': 1,
373+
'@eslint-react/dom-no-dangerously-set-innerhtml-with-children': 1,
374+
'@eslint-react/dom-no-find-dom-node': 1,
375+
'@eslint-react/dom-no-flush-sync': 0,
376+
'@eslint-react/dom-no-hydrate': 1,
377+
'@eslint-react/dom-no-missing-button-type': 1,
378+
'@eslint-react/dom-no-missing-iframe-sandbox': 1,
379+
'@eslint-react/dom-no-render': 1,
380+
'@eslint-react/dom-no-render-return-value': 1,
381+
'@eslint-react/dom-no-script-url': 1,
382+
'@eslint-react/dom-no-string-style-prop': 1,
383+
'@eslint-react/dom-no-unknown-property': 1,
384+
'@eslint-react/dom-no-unsafe-iframe-sandbox': 1,
385+
'@eslint-react/dom-no-unsafe-target-blank': 1,
386+
'@eslint-react/dom-no-use-form-state': 1,
387+
'@eslint-react/dom-no-void-elements-with-children': 1,
388+
'@eslint-react/dom-prefer-namespace-import': 1,
389+
'@eslint-react/web-api-no-leaked-event-listener': 1,
390+
'@eslint-react/web-api-no-leaked-interval': 1,
391+
'@eslint-react/web-api-no-leaked-resize-observer': 1,
392+
'@eslint-react/web-api-no-leaked-timeout': 1,
393+
'@eslint-react/naming-convention-context-name': 1,
394+
'@eslint-react/naming-convention-id-name': 1,
395+
'@eslint-react/naming-convention-ref-name': 1,
405396

406397
// SonarJS rules
407398
// https://github.com/SonarSource/SonarJS/blob/master/packages/jsts/src/rules/README.md#rules
@@ -1055,20 +1046,38 @@ copy(
10551046
name: 'markdown',
10561047
files: ['**/*.md'],
10571048
plugins: {
1058-
// @ts-expect-error
10591049
markdown
10601050
},
1061-
language: 'markdown/commonmark',
1051+
language: 'markdown/gfm',
10621052
rules: {
1053+
// `@eslint/markdown` rules
1054+
// https://github.com/eslint/markdown/blob/main/README.md#rules
1055+
/*
1056+
// copy all the rules from the rules table for easy pasting
1057+
copy(
1058+
Iterator.from(
1059+
document
1060+
// select rules table
1061+
.querySelector('.markdown-heading:has(> a[href="#rules"]) ~ markdown-accessiblity-table tbody')
1062+
// select all rule links
1063+
.querySelectorAll(':any-link')
1064+
)
1065+
// map link to rule declaration
1066+
.map((link) => `'markdown/${link.textContent}': 1,`)
1067+
.toArray()
1068+
.join('\n')
1069+
);
1070+
*/
10631071
'markdown/fenced-code-language': 1,
1072+
'markdown/fenced-code-meta': 0,
10641073
'markdown/heading-increment': 1,
10651074
'markdown/no-bare-urls': 1,
10661075
'markdown/no-duplicate-definitions': 1,
1067-
'markdown/no-duplicate-headings': 0,
1076+
'markdown/no-duplicate-headings': [1, { checkSiblingsOnly: true }],
10681077
'markdown/no-empty-definitions': 1,
10691078
'markdown/no-empty-images': 1,
10701079
'markdown/no-empty-links': 1,
1071-
'markdown/no-html': 0,
1080+
'markdown/no-html': [1, { allowed: ['br', 'kbd'] }],
10721081
'markdown/no-invalid-label-refs': 1,
10731082
'markdown/no-missing-atx-heading-space': 1,
10741083
'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": "tsc --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",
@@ -66,13 +67,12 @@
6667
"eslint-plugin-sonarjs": "^4.0.2",
6768
"jspdf": "^4.2.0",
6869
"jspdf-autotable": "^5.0.7",
69-
"oxfmt": "0.42.0",
70-
"playwright": "~1.58.0",
70+
"oxfmt": "0.43.0",
71+
"playwright": "~1.59.0",
7172
"postcss": "^8.5.2",
7273
"react": "^19.2.4",
7374
"react-dom": "^19.2.4",
74-
"rolldown": "1.0.0-rc.5",
75-
"rolldown-plugin-dts": "^0.22.5",
75+
"tsdown": "^0.21.7",
7676
"typescript": "~6.0.1-rc",
7777
"typescript-eslint": "^8.57.0",
7878
"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)