Skip to content

Commit ed656fe

Browse files
authored
Merge branch 'main' into externaldimensions
2 parents 278e419 + 09ebcb8 commit ed656fe

17 files changed

Lines changed: 124 additions & 154 deletions

.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

.npmrc

Lines changed: 0 additions & 1 deletion
This file was deleted.

.vscode/extensions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"recommendations": ["dbaeumer.vscode-eslint", "oxc.oxc-vscode"]
2+
"recommendations": ["dbaeumer.vscode-eslint", "oxc.oxc-vscode", "typescriptteam.native-preview"]
33
}

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"editor.formatOnSaveMode": "file",
1111
"js/ts.tsdk.promptToUseWorkspaceVersion": true,
1212
"js/ts.tsdk.path": "node_modules/typescript/lib",
13+
"js/ts.experimental.useTsgo": true,
1314
"files.readonlyInclude": {
1415
"**/routeTree.gen.ts": true
1516
},

AGENTS.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# AGENTS.md
22

3-
## Quick ref
3+
## Commands
44

55
```shell
66
npm install # setup (requires Node.js ≥ 22 for `node --run`)
77
node --run build # library → lib/
8-
node --run typecheck # tsc --build
8+
node --run typecheck # tsgo --build
99
node --run eslint # eslint --max-warnings 0
1010
node --run eslint:fix # eslint --fix
1111
node --run format # oxfmt
@@ -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: 66 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
import eslintReact from '@eslint-react/eslint-plugin';
22
import markdown from '@eslint/markdown';
33
import vitest from '@vitest/eslint-plugin';
4-
import jestDom from 'eslint-plugin-jest-dom';
5-
import reactDom from 'eslint-plugin-react-dom';
64
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';
105
import sonarjs from 'eslint-plugin-sonarjs';
11-
import testingLibrary from 'eslint-plugin-testing-library';
126
import { defineConfig, globalIgnores } from 'eslint/config';
137
import tseslint from 'typescript-eslint';
148

@@ -29,10 +23,6 @@ export default defineConfig([
2923
// @ts-expect-error
3024
'react-hooks': reactHooks,
3125
'@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,
3626
sonarjs,
3727
'@typescript-eslint': tseslint.plugin
3828
},
@@ -288,7 +278,7 @@ export default defineConfig([
288278
// https://www.eslint-react.xyz/docs/rules/overview
289279
/*
290280
// copy all the rules from the rules table for easy pasting
291-
function getRules(id, prefix) {
281+
function getRules(id) {
292282
return (
293283
Iterator.from(
294284
document
@@ -298,38 +288,33 @@ function getRules(id, prefix) {
298288
.querySelectorAll('tr a')
299289
)
300290
// map link to rule declaration
301-
.map((a) => `'@eslint-react/${prefix}${a.textContent}': 1,`)
291+
.map((a) => `'@eslint-react/${a.getAttribute('href')}': 1,`)
302292
);
303293
}
304294
copy(
305295
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/'),
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'),
311302
])
312303
.flatMap((x) => x)
313304
.toArray()
314305
.join('\n')
315306
);
316307
*/
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,
322308
'@eslint-react/component-hook-factories': 1,
323309
'@eslint-react/error-boundaries': 1,
324310
'@eslint-react/exhaustive-deps': 1,
325-
'@eslint-react/immutability': 0,
311+
'@eslint-react/immutability': 1,
326312
'@eslint-react/no-access-state-in-setstate': 1,
327313
'@eslint-react/no-array-index-key': 0,
328314
'@eslint-react/no-children-count': 1,
329315
'@eslint-react/no-children-for-each': 1,
330316
'@eslint-react/no-children-map': 1,
331317
'@eslint-react/no-children-only': 1,
332-
'@eslint-react/no-children-prop': 1,
333318
'@eslint-react/no-children-to-array': 1,
334319
'@eslint-react/no-class-component': 1,
335320
'@eslint-react/no-clone-element': 1,
@@ -367,7 +352,6 @@ copy(
367352
'@eslint-react/no-unused-props': 1,
368353
'@eslint-react/no-unused-state': 1,
369354
'@eslint-react/no-use-context': 1,
370-
'@eslint-react/no-useless-fragment': [1, { allowExpressions: false }],
371355
'@eslint-react/prefer-destructuring-assignment': 1,
372356
'@eslint-react/prefer-namespace-import': 1,
373357
'@eslint-react/purity': 1,
@@ -378,32 +362,37 @@ copy(
378362
'@eslint-react/unsupported-syntax': 1,
379363
'@eslint-react/use-memo': 1,
380364
'@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,
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,
407396

408397
// SonarJS rules
409398
// https://github.com/SonarSource/SonarJS/blob/master/packages/jsts/src/rules/README.md#rules
@@ -893,7 +882,7 @@ copy(
893882
1,
894883
{ path: 'never', types: 'never', lib: 'never' }
895884
],
896-
'@typescript-eslint/unbound-method': 0,
885+
'@typescript-eslint/unbound-method': 0, // replaced by vitest/unbound-method
897886
'@typescript-eslint/unified-signatures': 0,
898887
'@typescript-eslint/use-unknown-in-catch-callback-variable': 1
899888
}
@@ -905,9 +894,7 @@ copy(
905894
files: ['test/**/*'],
906895

907896
plugins: {
908-
vitest,
909-
'jest-dom': jestDom,
910-
'testing-library': testingLibrary
897+
vitest
911898
},
912899

913900
rules: {
@@ -1022,55 +1009,12 @@ copy(
10221009
'vitest/require-test-timeout': 0,
10231010
'vitest/require-to-throw-message': 1,
10241011
'vitest/require-top-level-describe': 0,
1012+
'vitest/unbound-method': 0,
10251013
'vitest/valid-describe-callback': 1,
10261014
'vitest/valid-expect': [1, { alwaysAwait: true }],
10271015
'vitest/valid-expect-in-promise': 1,
10281016
'vitest/valid-title': 1,
1029-
'vitest/warn-todo': 1,
1030-
1031-
// https://github.com/testing-library/eslint-plugin-jest-dom#supported-rules
1032-
'jest-dom/prefer-checked': 1,
1033-
'jest-dom/prefer-empty': 1,
1034-
'jest-dom/prefer-enabled-disabled': 1,
1035-
'jest-dom/prefer-focus': 1,
1036-
'jest-dom/prefer-in-document': 1,
1037-
'jest-dom/prefer-required': 1,
1038-
'jest-dom/prefer-to-have-attribute': 1,
1039-
'jest-dom/prefer-to-have-class': 1,
1040-
'jest-dom/prefer-to-have-style': 1,
1041-
'jest-dom/prefer-to-have-text-content': 1,
1042-
'jest-dom/prefer-to-have-value': 1,
1043-
1044-
// eslint-plugin-testing-library Rules
1045-
// https://github.com/testing-library/eslint-plugin-testing-library#supported-rules
1046-
'testing-library/await-async-events': 0,
1047-
'testing-library/await-async-queries': 0,
1048-
'testing-library/await-async-utils': 0,
1049-
'testing-library/consistent-data-testid': 0,
1050-
'testing-library/no-await-sync-events': 0,
1051-
'testing-library/no-await-sync-queries': 0,
1052-
'testing-library/no-container': 1,
1053-
'testing-library/no-debugging-utils': 1,
1054-
'testing-library/no-dom-import': 1,
1055-
'testing-library/no-global-regexp-flag-in-query': 1,
1056-
'testing-library/no-manual-cleanup': 0,
1057-
'testing-library/no-node-access': 0,
1058-
'testing-library/no-promise-in-fire-event': 0,
1059-
'testing-library/no-render-in-lifecycle': 0,
1060-
'testing-library/no-test-id-queries': 0,
1061-
'testing-library/no-unnecessary-act': 1,
1062-
'testing-library/no-wait-for-multiple-assertions': 1,
1063-
'testing-library/no-wait-for-side-effects': 1,
1064-
'testing-library/no-wait-for-snapshot': 0,
1065-
'testing-library/prefer-explicit-assert': 1,
1066-
'testing-library/prefer-find-by': 1,
1067-
'testing-library/prefer-implicit-assert': 0,
1068-
'testing-library/prefer-presence-queries': 0,
1069-
'testing-library/prefer-query-by-disappearance': 1,
1070-
'testing-library/prefer-query-matchers': 0,
1071-
'testing-library/prefer-screen-queries': 0,
1072-
'testing-library/prefer-user-event': 1,
1073-
'testing-library/render-result-naming-convention': 0
1017+
'vitest/warn-todo': 1
10741018
}
10751019
},
10761020

@@ -1103,20 +1047,38 @@ copy(
11031047
name: 'markdown',
11041048
files: ['**/*.md'],
11051049
plugins: {
1106-
// @ts-expect-error
11071050
markdown
11081051
},
1109-
language: 'markdown/commonmark',
1052+
language: 'markdown/gfm',
11101053
rules: {
1054+
// `@eslint/markdown` rules
1055+
// https://github.com/eslint/markdown/blob/main/README.md#rules
1056+
/*
1057+
// copy all the rules from the rules table for easy pasting
1058+
copy(
1059+
Iterator.from(
1060+
document
1061+
// select rules table
1062+
.querySelector('.markdown-heading:has(> a[href="#rules"]) ~ markdown-accessiblity-table tbody')
1063+
// select all rule links
1064+
.querySelectorAll(':any-link')
1065+
)
1066+
// map link to rule declaration
1067+
.map((link) => `'markdown/${link.textContent}': 1,`)
1068+
.toArray()
1069+
.join('\n')
1070+
);
1071+
*/
11111072
'markdown/fenced-code-language': 1,
1073+
'markdown/fenced-code-meta': 0,
11121074
'markdown/heading-increment': 1,
11131075
'markdown/no-bare-urls': 1,
11141076
'markdown/no-duplicate-definitions': 1,
1115-
'markdown/no-duplicate-headings': 0,
1077+
'markdown/no-duplicate-headings': [1, { checkSiblingsOnly: true }],
11161078
'markdown/no-empty-definitions': 1,
11171079
'markdown/no-empty-images': 1,
11181080
'markdown/no-empty-links': 1,
1119-
'markdown/no-html': 0,
1081+
'markdown/no-html': [1, { allowed: ['br', 'kbd'] }],
11201082
'markdown/no-invalid-label-refs': 1,
11211083
'markdown/no-missing-atx-heading-space': 1,
11221084
'markdown/no-missing-label-refs': 1,

0 commit comments

Comments
 (0)