diff --git a/NOTICE.md b/NOTICE.md index 77d41919ed..67dc235843 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -70,7 +70,7 @@ This product includes the following bundled third-party software: - ignore v7.0.6 [MIT] (used by: @nvidia-elements/pi) Copyright: kael -- lit v3.3.3 [BSD-3-Clause] (used by: @nvidia-elements/code, @nvidia-elements/core, @nvidia-elements/markdown, @nvidia-elements/media, @nvidia-elements/monaco, @nvidia-elements/plot, @nvidia-elements/scene) +- lit v3.3.3 [BSD-3-Clause] (used by: @nvidia-elements/code, @nvidia-elements/core, @nvidia-elements/markdown, @nvidia-elements/media, @nvidia-elements/monaco, @nvidia-elements/plot, @nvidia-elements/scene, @nvidia-elements/viewport) Copyright: Google LLC - lit-html v3.3.3 [BSD-3-Clause] (used by: @nvidia-elements/code, @nvidia-elements/core, @nvidia-elements/monaco) diff --git a/commitlint.config.js b/commitlint.config.js index 3e5b47dc95..371c4a5b43 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -37,7 +37,8 @@ export default { 'markdown', 'media', 'monaco', - 'scene' + 'scene', + 'viewport' ] ] } diff --git a/knip.config.js b/knip.config.js index 0f19fee932..ec6d506878 100644 --- a/knip.config.js +++ b/knip.config.js @@ -236,10 +236,23 @@ export default { ], project: [...PROJECT_FILES, ...SOURCE_FILES] }, + 'projects/viewport': { + entry: [ + ...PACKAGE_FILES, + ...SOURCE_INDEX, + ...DEFINE_ENTRIES, + ...EXAMPLE_ENTRIES, + ...TEST_ENTRIES, + ...TEST_VARIANT_ENTRIES, + ...VITE_CONFIGS, + ...VITEST_CONFIGS + ], + project: [...PROJECT_FILES, ...SOURCE_FILES] + }, 'projects/site': { entry: [...PACKAGE_FILES, 'src/**/*.{js,ts}', ...VITEST_CONFIGS], project: [...PACKAGE_FILES, 'src/**/*.{js,ts}', ...VITEST_CONFIGS], - ignoreDependencies: ['@nvidia-elements/plot', '@nvidia-elements/scene'] // temporary + ignoreDependencies: ['@nvidia-elements/plot', '@nvidia-elements/scene', '@nvidia-elements/viewport'] }, 'projects/styles': { entry: [ diff --git a/package.json b/package.json index 342b03ae33..98e5e6fcbe 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "./projects/pi:ci", "./projects/plot:ci", "./projects/scene:ci", + "./projects/viewport:ci", "./projects/site:ci", "./projects/starters:ci", "./projects/starters/angular:ci", @@ -145,6 +146,7 @@ "./projects/monaco:test:lighthouse", "./projects/plot:test:lighthouse", "./projects/scene:test:lighthouse", + "./projects/viewport:test:lighthouse", "./projects/site:test:lighthouse" ], "output": [ @@ -172,7 +174,8 @@ "release:media", "release:monaco", "release:pi", - "release:styles" + "release:styles", + "release:viewport" ] }, "release:themes": { @@ -245,6 +248,13 @@ "release:themes" ] }, + "release:viewport": { + "command": "cd projects/viewport && HUSKY=0 pnpm exec semantic-release", + "dependencies": [ + "release:core", + "release:themes" + ] + }, "format": { "command": "prettier --config prettier.config.js --check --cache .", "files": [ @@ -305,6 +315,7 @@ "./projects/media:lint:fix", "./projects/monaco:lint:fix", "./projects/scene:lint:fix", + "./projects/viewport:lint:fix", "./projects/site:lint:fix" ] }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d2f49ecd2..a5c09660fe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1734,6 +1734,9 @@ importers: '@nvidia-elements/themes': specifier: workspace:* version: link:../themes + '@nvidia-elements/viewport': + specifier: workspace:* + version: link:../viewport '@vitest/browser': specifier: 'catalog:' version: 5.0.0(vite@8.2.2(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.1(postcss@8.5.26))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.0) @@ -2715,6 +2718,70 @@ importers: specifier: 'catalog:' version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@26.4.1)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-istanbul@5.0.0)(@vitest/ui@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.1(postcss@8.5.26))(terser@5.48.0)(yaml@2.9.0)) + projects/viewport: + dependencies: + '@nvidia-elements/core': + specifier: workspace:^ + version: link:../core + '@nvidia-elements/themes': + specifier: workspace:^ + version: link:../themes + lit: + specifier: catalog:publish + version: 3.3.3 + devDependencies: + '@eslint/js': + specifier: 'catalog:' + version: 10.0.1(eslint@10.11.0(jiti@2.7.0)(supports-color@10.2.2)) + '@internals/eslint': + specifier: workspace:* + version: link:../internals/eslint + '@internals/testing': + specifier: workspace:* + version: link:../internals/testing + '@internals/vite': + specifier: workspace:* + version: link:../internals/vite + '@nvidia-elements/lint': + specifier: workspace:* + version: link:../lint + '@nvidia-elements/styles': + specifier: workspace:* + version: link:../styles + '@vitest/browser': + specifier: 'catalog:' + version: 5.0.0(vite@8.2.2(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.1(postcss@8.5.26))(terser@5.48.0)(yaml@2.9.0))(vitest@5.0.0) + '@vitest/coverage-istanbul': + specifier: 'catalog:' + version: 5.0.0(vitest@5.0.0) + axe-core: + specifier: 'catalog:' + version: 4.11.0 + eslint: + specifier: 10.11.0 + version: 10.11.0(jiti@2.7.0)(supports-color@10.2.2) + publint: + specifier: 'catalog:' + version: 0.3.24 + stylelint: + specifier: 'catalog:' + version: 17.14.1(supports-color@10.2.2)(typescript@6.0.3) + stylelint-config-standard: + specifier: 'catalog:' + version: 40.0.0(stylelint@17.14.1(supports-color@10.2.2)(typescript@6.0.3)) + typescript: + specifier: 'catalog:' + version: 6.0.3 + typescript-7: + specifier: 'catalog:' + version: typescript@7.0.2 + vite: + specifier: 'catalog:' + version: 8.2.2(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.1(postcss@8.5.26))(terser@5.48.0)(yaml@2.9.0) + vitest: + specifier: 'catalog:' + version: 5.0.0(@opentelemetry/api@1.9.1)(@types/node@26.5.0)(@vitest/browser-playwright@5.0.0)(@vitest/coverage-istanbul@5.0.0)(@vitest/ui@5.0.0)(jsdom@30.0.1)(vite@8.2.2(@types/node@26.5.0)(esbuild@0.28.2)(jiti@2.7.0)(sass@1.101.0)(sugarss@5.0.1(postcss@8.5.26))(terser@5.48.0)(yaml@2.9.0)) + packages: 3d-force-graph@1.79.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 21d6956a34..5eebe93e8f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -35,6 +35,7 @@ packages: - projects/forms - projects/plot - projects/scene + - projects/viewport - projects/styles - projects/themes - projects/internals/design diff --git a/projects/core/.visual/viewport-crosses.dark.png b/projects/core/.visual/viewport-crosses.dark.png deleted file mode 100644 index 1684609ed0..0000000000 --- a/projects/core/.visual/viewport-crosses.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:48bcd15fd04da7b051abe45e13091fbde555288671024f3b24db04a729f3ef9e -size 3941 diff --git a/projects/core/.visual/viewport-crosses.png b/projects/core/.visual/viewport-crosses.png deleted file mode 100644 index fdb15b0d7e..0000000000 --- a/projects/core/.visual/viewport-crosses.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:552759fa2e17bb6c3640fcf7469be4ad1bbaae8eb29ff001984a0c26c2758804 -size 3905 diff --git a/projects/core/.visual/viewport-dots.dark.png b/projects/core/.visual/viewport-dots.dark.png deleted file mode 100644 index 94ff41d22c..0000000000 --- a/projects/core/.visual/viewport-dots.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3281787e6ab1995abe5f7dcc8aec6b73db967515f666263153fb914db40043c9 -size 3997 diff --git a/projects/core/.visual/viewport-dots.png b/projects/core/.visual/viewport-dots.png deleted file mode 100644 index 874487955a..0000000000 --- a/projects/core/.visual/viewport-dots.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:22fc3b1ad55c7d499011c97dfdde247ae37587b08896f8e39f9ce37c37a1ba4c -size 3978 diff --git a/projects/core/.visual/viewport-minimap-custom.dark.png b/projects/core/.visual/viewport-minimap-custom.dark.png deleted file mode 100644 index 1bb4504d12..0000000000 --- a/projects/core/.visual/viewport-minimap-custom.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:44762dc3f8b5385a9da084b8f5aa5aee4a3e3ef885dfbfdadac82a8200c905d8 -size 5162 diff --git a/projects/core/.visual/viewport-minimap-custom.png b/projects/core/.visual/viewport-minimap-custom.png deleted file mode 100644 index a4b3490538..0000000000 --- a/projects/core/.visual/viewport-minimap-custom.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3210794387f365ca867f9cbd221e45513b48ff6d117916e97dadafe9a083268d -size 5258 diff --git a/projects/core/.visual/viewport-minimap.dark.png b/projects/core/.visual/viewport-minimap.dark.png deleted file mode 100644 index f3e0313809..0000000000 --- a/projects/core/.visual/viewport-minimap.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a2f4a9494a842136f7eb2ffc23415ed0961f75bdf3ff65a30ab4c5e194f2fb14 -size 4573 diff --git a/projects/core/.visual/viewport-minimap.png b/projects/core/.visual/viewport-minimap.png deleted file mode 100644 index de265d1240..0000000000 --- a/projects/core/.visual/viewport-minimap.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d0c04c7f2dba83f903cb29ed067f0a2d94e58ba65e2105881bb658291fc2ebe8 -size 4629 diff --git a/projects/core/.visual/viewport.dark.png b/projects/core/.visual/viewport.dark.png deleted file mode 100644 index 053545141c..0000000000 --- a/projects/core/.visual/viewport.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e5d596b575ceaf1d57da0dc1a6eec0307775cbdfb9c7b2591de0f5c143004083 -size 3722 diff --git a/projects/core/.visual/viewport.png b/projects/core/.visual/viewport.png deleted file mode 100644 index 57f4848f60..0000000000 --- a/projects/core/.visual/viewport.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75916012a4a87e021c1e43e3529862ccae321c58772205cf0550314714fa05b1 -size 3681 diff --git a/projects/core/package.json b/projects/core/package.json index b07c21d131..8954ea3fe1 100644 --- a/projects/core/package.json +++ b/projects/core/package.json @@ -951,18 +951,6 @@ "types": "./dist/tree/define.d.ts", "default": "./dist/tree/define.js" }, - "./viewport": { - "types": "./dist/viewport/index.d.ts", - "default": "./dist/viewport/index.js" - }, - "./viewport/index.js": { - "types": "./dist/viewport/index.d.ts", - "default": "./dist/viewport/index.js" - }, - "./viewport/define.js": { - "types": "./dist/viewport/define.d.ts", - "default": "./dist/viewport/define.js" - }, "./week": { "types": "./dist/week/index.d.ts", "default": "./dist/week/index.js" diff --git a/projects/core/src/bundle.ts b/projects/core/src/bundle.ts index 7beaf3af91..fb8c672ed1 100644 --- a/projects/core/src/bundle.ts +++ b/projects/core/src/bundle.ts @@ -74,7 +74,6 @@ import '@nvidia-elements/core/toggletip/define.js'; import '@nvidia-elements/core/toolbar/define.js'; import '@nvidia-elements/core/tooltip/define.js'; import '@nvidia-elements/core/tree/define.js'; -import '@nvidia-elements/core/viewport/define.js'; import '@nvidia-elements/core/week/define.js'; export * from '@nvidia-elements/core/accordion'; @@ -147,7 +146,6 @@ export * from '@nvidia-elements/core/toggletip'; export * from '@nvidia-elements/core/toolbar'; export * from '@nvidia-elements/core/tooltip'; export * from '@nvidia-elements/core/tree'; -export * from '@nvidia-elements/core/viewport'; export * from '@nvidia-elements/core/week'; globalThis.NVE_ELEMENTS.state.bundle = true; diff --git a/projects/core/src/index.test.lighthouse.ts b/projects/core/src/index.test.lighthouse.ts index e414d24e27..3ed158ce18 100644 --- a/projects/core/src/index.test.lighthouse.ts +++ b/projects/core/src/index.test.lighthouse.ts @@ -99,7 +99,6 @@ describe('lighthouse report', () => { import '@nvidia-elements/core/toolbar/define.js'; import '@nvidia-elements/core/tooltip/define.js'; import '@nvidia-elements/core/tree/define.js'; - import '@nvidia-elements/core/viewport/define.js'; import '@nvidia-elements/core/week/define.js'; ` diff --git a/projects/core/src/viewport/viewport.test.ssr.ts b/projects/core/src/viewport/viewport.test.ssr.ts deleted file mode 100644 index 203d019ed9..0000000000 --- a/projects/core/src/viewport/viewport.test.ssr.ts +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -import { html } from 'lit'; -import { describe, expect, it } from 'vitest'; -import { ssrRunner } from '@internals/vite'; -import { Viewport } from '@nvidia-elements/core/viewport'; -import '@nvidia-elements/core/viewport/define.js'; - -describe(Viewport.metadata.tag, () => { - it('should render content layers and assign viewport companions on the server', async () => { - const result = await ssrRunner.render(html` - - - -
content
-
- `); - - expect(result.includes('shadowroot="open"')).toBe(true); - expect(result.includes('slot name="background"')).toBe(true); - expect(result.includes('slot name="overlay"')).toBe(true); - expect(result.includes('class="plane"')).toBe(true); - const gridlinesTag = result.match(/]*>/)?.[0]; - expect(gridlinesTag).toBeDefined(); - expect(gridlinesTag).toContain('slot="background"'); - expect(gridlinesTag).toContain('aria-hidden="true"'); - const minimapTag = result.match(/]*>/)?.[0]; - expect(minimapTag).toBeDefined(); - expect(minimapTag).toContain('slot="overlay"'); - expect(minimapTag).toContain('aria-hidden="true"'); - }); -}); diff --git a/projects/internals/metadata/package.json b/projects/internals/metadata/package.json index 99980efbe7..d581824453 100644 --- a/projects/internals/metadata/package.json +++ b/projects/internals/metadata/package.json @@ -118,6 +118,8 @@ "../../media/dist/custom-elements.json", "../../monaco/package.json", "../../monaco/dist/custom-elements.json", + "../../viewport/package.json", + "../../viewport/dist/custom-elements.json", "../../patterns/package.json", "../../patterns/dist/custom-elements.json", "../../plot/dist/custom-elements.json", @@ -168,6 +170,10 @@ { "script": "../../scene:build", "cascade": false + }, + { + "script": "../../viewport:build", + "cascade": false } ] }, @@ -250,6 +256,11 @@ "../../monaco/coverage/unit/summary.json", "../../monaco/coverage/visual/summary.json", "../../monaco/coverage/ssr/summary.json", + "../../viewport/coverage/unit/coverage-summary.json", + "../../viewport/coverage/unit/summary.json", + "../../viewport/coverage/axe/summary.json", + "../../viewport/coverage/visual/summary.json", + "../../viewport/coverage/ssr/summary.json", "../tools/coverage/unit/coverage-summary.json", "../tools/coverage/unit/summary.json" ], @@ -313,6 +324,22 @@ "script": "../../monaco:test:coverage", "cascade": false }, + { + "script": "../../viewport:test:coverage", + "cascade": false + }, + { + "script": "../../viewport:test:axe", + "cascade": false + }, + { + "script": "../../viewport:test:ssr", + "cascade": false + }, + { + "script": "../../viewport:test:visual", + "cascade": false + }, { "script": "../../cli:test:coverage", "cascade": false @@ -343,7 +370,8 @@ "../../forms/dist/**/*.examples.json", "../../markdown/dist/**/*.examples.json", "../../media/dist/**/*.examples.json", - "../../scene/dist/**/*.examples.json" + "../../scene/dist/**/*.examples.json", + "../../viewport/dist/**/*.examples.json" ], "output": [ "static/examples.json" @@ -385,6 +413,10 @@ "script": "../../scene:build", "cascade": false }, + { + "script": "../../viewport:build", + "cascade": false + }, { "script": "../patterns:build", "cascade": false @@ -408,7 +440,8 @@ "clean": false, "files": [ "src/tasks/lighthouse.ts", - "src/tasks/lighthouse.utils.ts" + "src/tasks/lighthouse.utils.ts", + "../../viewport/.lighthouse/dist/report.json" ], "output": [ "static/lighthouse.json" diff --git a/projects/internals/metadata/src/tasks/api.utils.ts b/projects/internals/metadata/src/tasks/api.utils.ts index 0242f85f59..b0712ceaa3 100644 --- a/projects/internals/metadata/src/tasks/api.utils.ts +++ b/projects/internals/metadata/src/tasks/api.utils.ts @@ -136,7 +136,8 @@ export async function getApi(): Promise<{ '../../../../media', '../../../../monaco', '../../../../plot', - '../../../../scene' + '../../../../scene', + '../../../../viewport' ]; const types = getElementsStandardAPIProperties(); diff --git a/projects/internals/metadata/src/tasks/lighthouse.utils.test.ts b/projects/internals/metadata/src/tasks/lighthouse.utils.test.ts index e5252de1bc..ed26e12838 100644 --- a/projects/internals/metadata/src/tasks/lighthouse.utils.test.ts +++ b/projects/internals/metadata/src/tasks/lighthouse.utils.test.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { existsSync, readFileSync } from 'node:fs'; import { beforeEach, describe, expect, it } from 'vitest'; import { type LighthouseScores, generateLighthouseReport, getLighthouseReport } from './lighthouse.utils.js'; @@ -44,4 +45,12 @@ describe('Lighthouse Metadata', () => { expect(report.created).toBeDefined(); expect(report['@nvidia-elements/core']).not.toBe(undefined); }); + + it('should preserve viewport component scores from the package report', async () => { + const reportPath = new URL('../../../../viewport/.lighthouse/dist/report.json', import.meta.url); + const expected = existsSync(reportPath) ? JSON.parse(readFileSync(reportPath, 'utf8')) : null; + const report = await generateLighthouseReport(); + + expect(report).toHaveProperty('@nvidia-elements/viewport', expected); + }); }); diff --git a/projects/internals/metadata/src/tasks/lighthouse.utils.ts b/projects/internals/metadata/src/tasks/lighthouse.utils.ts index 112901e9d6..268be55afc 100644 --- a/projects/internals/metadata/src/tasks/lighthouse.utils.ts +++ b/projects/internals/metadata/src/tasks/lighthouse.utils.ts @@ -18,6 +18,7 @@ export interface LighthouseScores { '@nvidia-elements/markdown': { [key: string]: LighthouseElementReport }; '@nvidia-elements/media': { [key: string]: LighthouseElementReport }; '@nvidia-elements/monaco': { [key: string]: LighthouseElementReport }; + '@nvidia-elements/viewport': { [key: string]: LighthouseElementReport }; } interface LighthouseElementReport { @@ -59,7 +60,8 @@ export async function generateLighthouseReport(): Promise { '@nvidia-elements/forms': await loadLighthouseReport('../../../../forms'), '@nvidia-elements/markdown': await loadLighthouseReport('../../../../markdown'), '@nvidia-elements/media': await loadLighthouseReport('../../../../media'), - '@nvidia-elements/monaco': await loadLighthouseReport('../../../../monaco') + '@nvidia-elements/monaco': await loadLighthouseReport('../../../../monaco'), + '@nvidia-elements/viewport': await loadLighthouseReport('../../../../viewport') }; } diff --git a/projects/internals/metadata/src/tasks/tests.utils.test.ts b/projects/internals/metadata/src/tasks/tests.utils.test.ts index da960e4680..7291391647 100644 --- a/projects/internals/metadata/src/tasks/tests.utils.test.ts +++ b/projects/internals/metadata/src/tasks/tests.utils.test.ts @@ -325,6 +325,7 @@ describe('generateTestSummary', () => { '@nvidia-elements/markdown', '@nvidia-elements/media', '@nvidia-elements/monaco', + '@nvidia-elements/viewport', '@nvidia-elements/pi', '@internals/metadata', '@internals/patterns', diff --git a/projects/internals/metadata/src/tasks/tests.utils.ts b/projects/internals/metadata/src/tasks/tests.utils.ts index 1cb4ac6ce8..13401e7cce 100644 --- a/projects/internals/metadata/src/tasks/tests.utils.ts +++ b/projects/internals/metadata/src/tasks/tests.utils.ts @@ -103,6 +103,7 @@ export async function generateTestSummary(): Promise { '@nvidia-elements/markdown': await getTestReport('@nvidia-elements/markdown', '../../../../markdown'), '@nvidia-elements/media': await getTestReport('@nvidia-elements/media', '../../../../media'), '@nvidia-elements/monaco': await getTestReport('@nvidia-elements/monaco', '../../../../monaco'), + '@nvidia-elements/viewport': await getTestReport('@nvidia-elements/viewport', '../../../../viewport'), '@nvidia-elements/pi': await getTestReport('@nvidia-elements/pi', '../../../../pi'), '@internals/metadata': await getTestReport('@internals/metadata', '../../../../internals/metadata'), '@internals/patterns': await getTestReport('@internals/patterns', '../../../../internals/patterns'), diff --git a/projects/internals/metadata/static/adoption.json b/projects/internals/metadata/static/adoption.json index ede0c1f81c..96983ec1b9 100644 --- a/projects/internals/metadata/static/adoption.json +++ b/projects/internals/metadata/static/adoption.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:630324999d28fb2d962f39ee7867877ca3b17175a5a017b0029ff86dab7e860e -size 261417 +oid sha256:9d01ba96d7bb402c40adeb9e21ddd3ce921f9f1254dcffd10e27efa2dfa3c619 +size 409449 diff --git a/projects/internals/metadata/static/lighthouse.json b/projects/internals/metadata/static/lighthouse.json index 3fc5b80a93..e13ca1e9d1 100644 --- a/projects/internals/metadata/static/lighthouse.json +++ b/projects/internals/metadata/static/lighthouse.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:626431290e683873d47f224cb668634c11956b60aed788299f40d4a8aaf82869 -size 67679 +oid sha256:643742940aca2a120418515e31c05ab99a316089baba12f972c605f00063280a +size 71920 diff --git a/projects/internals/metadata/static/releases.json b/projects/internals/metadata/static/releases.json index fea0234cc0..d88b9164a8 100644 --- a/projects/internals/metadata/static/releases.json +++ b/projects/internals/metadata/static/releases.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:62136236308eb17156d2ce572a205e19cb510a48e06e4743f287b279de19b020 -size 23960 +oid sha256:434936d6c3004f25b64e5b7e795b76a377f9b5751e3764e01055ef694675980b +size 28279 diff --git a/projects/internals/metadata/static/tests.json b/projects/internals/metadata/static/tests.json index 7eede6f5c3..cb3add4cd3 100644 --- a/projects/internals/metadata/static/tests.json +++ b/projects/internals/metadata/static/tests.json @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ce71d876f17db545219fbb9699b717b944774b1839fcc2aae481684d11f84cd4 -size 2947869 +oid sha256:5ba2a4628bf25704b6531c072cded88d3ab4a027b30e551c0799aedd62352856 +size 3377501 diff --git a/projects/site/eleventy.config.js b/projects/site/eleventy.config.js index d6bd63eb57..3e34d45ea8 100644 --- a/projects/site/eleventy.config.js +++ b/projects/site/eleventy.config.js @@ -240,7 +240,8 @@ export default function (eleventyConfig) { 'src/docs/code/*.md', 'src/docs/monaco/*.md', 'src/docs/media/*.md', - 'src/docs/markdown/index.md' + 'src/docs/markdown/index.md', + 'src/docs/viewport/*.md' ]) .filter(page => page.data.tag); }); diff --git a/projects/site/package.json b/projects/site/package.json index ee15bd38da..0eed1b5eca 100644 --- a/projects/site/package.json +++ b/projects/site/package.json @@ -106,6 +106,8 @@ "../plot/dist/**/*.examples.json", "../scene/dist/**/*.js", "../scene/dist/**/*.examples.json", + "../viewport/dist/**/*.js", + "../viewport/dist/**/*.examples.json", "../internals/design/dist/DESIGN.md", "../internals/metadata/static/**", "../internals/patterns/dist/**/*.js", @@ -173,6 +175,10 @@ { "script": "../internals/tools:build", "cascade": false + }, + { + "script": "../viewport:build", + "cascade": false } ], "output": [] @@ -346,6 +352,7 @@ "@nvidia-elements/scene": "workspace:*", "@nvidia-elements/styles": "workspace:*", "@nvidia-elements/themes": "workspace:*", + "@nvidia-elements/viewport": "workspace:*", "@vitest/browser": "catalog:", "@vitest/coverage-istanbul": "catalog:", "compare-versions": "6.1.1", diff --git a/projects/site/src/_11ty/layouts/common.js b/projects/site/src/_11ty/layouts/common.js index 08617b8716..381c6043bf 100644 --- a/projects/site/src/_11ty/layouts/common.js +++ b/projects/site/src/_11ty/layouts/common.js @@ -240,7 +240,7 @@ export const renderDocsNav = data => /* html */ ` DESIGN.md - + Elements Accordion Alert @@ -370,7 +370,12 @@ export const renderDocsNav = data => /* html */ ` Toolbar Tooltip Tree - Viewport + + Viewport + Viewport + Gridlines + Minimap + Week diff --git a/projects/site/src/_11ty/layouts/metadata.test.ts b/projects/site/src/_11ty/layouts/metadata.test.ts index f822354d45..f091b2f984 100644 --- a/projects/site/src/_11ty/layouts/metadata.test.ts +++ b/projects/site/src/_11ty/layouts/metadata.test.ts @@ -500,6 +500,17 @@ describe('renderBaseHead', () => { }); describe('renderDocsNav', () => { + it.each( + ['/docs/viewport/', '/docs/viewport/gridlines/', '/docs/viewport/minimap/'].flatMap(component => + ['', 'api/', 'examples/'].map(tab => `${component}${tab}`) + ) + )('should keep the elements and viewport groups expanded on %s', url => { + const html = renderDocsNav({ page: { url } }); + + expect(html).toMatch(/\s*Elements<\/a>/); + expect(html).toMatch(/\s*Viewport<\/a>/); + }); + it('should expose DESIGN.md in getting started navigation', () => { const html = renderDocsNav({ page: { url: '/docs/design-md/' } }); diff --git a/projects/site/src/docs/elements/_tabs/api.11ty.js b/projects/site/src/docs/elements/_tabs/api.11ty.js index 02c09c1ecd..7377015da0 100644 --- a/projects/site/src/docs/elements/_tabs/api.11ty.js +++ b/projects/site/src/docs/elements/_tabs/api.11ty.js @@ -17,6 +17,7 @@ export const data = { }, permalink: data => { const filePath = data.component.filePathStem; + if (filePath.includes('/viewport/')) return `${data.component.url}api/`; let dir = 'elements'; if (filePath.includes('/code/')) dir = 'code'; else if (filePath.includes('/monaco/')) dir = 'monaco'; diff --git a/projects/site/src/docs/elements/_tabs/examples.11ty.js b/projects/site/src/docs/elements/_tabs/examples.11ty.js index 222aab548a..e706868b85 100644 --- a/projects/site/src/docs/elements/_tabs/examples.11ty.js +++ b/projects/site/src/docs/elements/_tabs/examples.11ty.js @@ -7,7 +7,7 @@ import { exampleDocShortcode } from '../../../_11ty/shortcodes/example-doc.js'; const { examples, elements } = siteData; /** - * @typedef {{component: {filePathStem: string, fileSlug: string, data: {tag?: string, title: string, hideExamplesTab?: boolean, page: {fileSlug: string}}}, page: {fileSlug: string}, tag?: string, title?: string, isExamplesTab?: boolean, noindex?: boolean}} ExamplesPageData + * @typedef {{component: {url: string, filePathStem: string, fileSlug: string, data: {tag?: string, title: string, hideExamplesTab?: boolean, page: {fileSlug: string}}}, page: {fileSlug: string}, tag?: string, title?: string, isExamplesTab?: boolean, noindex?: boolean}} ExamplesPageData */ // Initialize markdown parser and metadata service @@ -33,6 +33,7 @@ export const data = { // Generate URLs in the format /docs/elements/{component-name}/examples/ or package-specific component docs paths. permalink: (/** @type {ExamplesPageData} */ data) => { const filePath = data.component.filePathStem; + if (filePath.includes('/viewport/')) return `${data.component.url}examples/`; let dir = 'elements'; if (filePath.includes('/code/')) dir = 'code'; else if (filePath.includes('/monaco/')) dir = 'monaco'; @@ -119,7 +120,7 @@ export async function render(data) { const params = new URLSearchParams(window.location.search); const cyclingExample = document.querySelector('#cycling-example'); const exampleSelector = document.querySelector('.example-selector'); - const exampleSelectorItems = Array.from(exampleSelector.querySelectorAll('nve-menu-item')); + const exampleSelectorItems = Array.from(exampleSelector?.querySelectorAll('nve-menu-item') ?? []); const examples = ${JSON.stringify(exampleTemplates.map(i => ({ id: i.id, template: md.utils.escapeHtml(i.template) })))}; const exampleId = params.get('example') ?? examples[0].id; const example = examples.find(e => e.id === exampleId); @@ -131,10 +132,11 @@ export async function render(data) { if (example) { cyclingExample.source = unescapeHtml(example.template); - exampleSelectorItems.find(item => item.value === exampleId).selected = true; + const selectedItem = exampleSelectorItems.find(item => item.value === exampleId); + if (selectedItem) selectedItem.selected = true; } - exampleSelector.addEventListener('click', (event) => { + exampleSelector?.addEventListener('click', (event) => { const example = examples.find(e => e.id === event.target.value); if (example) { exampleSelectorItems.forEach(item => item.selected = false); diff --git a/projects/site/src/docs/elements/viewport.md b/projects/site/src/docs/elements/viewport.md deleted file mode 100644 index 597251d6aa..0000000000 --- a/projects/site/src/docs/elements/viewport.md +++ /dev/null @@ -1,53 +0,0 @@ ---- -{ - title: 'Viewport', - description: 'Use the NVIDIA Elements viewport to navigate arbitrary spatial content with pan, zoom, fitting, grid lines, and programmatic controls.', - layout: 'docs.11ty.js', - tag: 'nve-viewport', - associatedElements: ['nve-viewport-gridlines', 'nve-viewport-minimap'] -} ---- - -## Installation - -{% install 'nve-viewport' %} - -## Panning - -{% api 'nve-viewport', 'property', 'behaviorPan' %} - -## Zooming - -{% api 'nve-viewport', 'property', 'behaviorZoom' %} - -## Customizable Minimap - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'Minimap' '{ "inline": false, "height": "420px" }' %} - -## Interactive Content - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'InteractiveContent' '{ "inline": false, "height": "420px" }' %} - -## Panning with Space - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'PanWithSpace' '{ "inline": false, "height": "420px" }' %} - -## Invoker Commands - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'Commands' '{ "inline": false, "height": "420px" }' %} - -## Programmatic Navigation - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'ProgrammaticNavigation' '{ "inline": false, "height": "420px" }' %} - -## Dot Grid - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'Dots' '{ "inline": false, "height": "420px" }' %} - -## Cross Grid - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'Crosses' '{ "inline": false, "height": "420px" }' %} - -## Customizable Background - -{% example '@nvidia-elements/core/viewport/viewport.examples.json' 'Background' '{ "inline": false, "height": "420px" }' %} diff --git a/projects/site/src/docs/viewport/gridlines.md b/projects/site/src/docs/viewport/gridlines.md new file mode 100644 index 0000000000..ee1d590158 --- /dev/null +++ b/projects/site/src/docs/viewport/gridlines.md @@ -0,0 +1,26 @@ +--- +{ + title: 'Gridlines', + description: 'Add lines, dots, or crosses behind NVIDIA Elements viewport content.', + layout: 'docs.11ty.js', + tag: 'nve-viewport-gridlines' +} +--- + +## Installation + +{% install 'nve-viewport-gridlines' %} + +## Grid Spacing + +{% api 'nve-viewport-gridlines', 'property', 'step' %} + +{% api 'nve-viewport-gridlines', 'property', 'targetSpacing' %} + +## Dot Grid + +{% example '@nvidia-elements/viewport/gridlines/gridlines.examples.json' 'Dots' '{ "inline": false, "height": "420px" }' %} + +## Cross Grid + +{% example '@nvidia-elements/viewport/gridlines/gridlines.examples.json' 'Crosses' '{ "inline": false, "height": "420px" }' %} diff --git a/projects/site/src/docs/viewport/index.md b/projects/site/src/docs/viewport/index.md new file mode 100644 index 0000000000..02cb1367fb --- /dev/null +++ b/projects/site/src/docs/viewport/index.md @@ -0,0 +1,40 @@ +--- +{ + title: 'Viewport', + description: 'Use the NVIDIA Elements viewport to navigate spatial content with pan, zoom, fitting, and programmatic controls.', + layout: 'docs.11ty.js', + tag: 'nve-viewport' +} +--- + +## Installation + +{% install 'nve-viewport' %} + +## Panning + +{% api 'nve-viewport', 'property', 'behaviorPan' %} + +## Zooming + +{% api 'nve-viewport', 'property', 'behaviorZoom' %} + +## Interactive Content + +{% example '@nvidia-elements/viewport/viewport/viewport.examples.json' 'InteractiveContent' '{ "inline": false, "height": "420px" }' %} + +## Panning with Space + +{% example '@nvidia-elements/viewport/viewport/viewport.examples.json' 'PanWithSpace' '{ "inline": false, "height": "420px" }' %} + +## Invoker Commands + +{% example '@nvidia-elements/viewport/viewport/viewport.examples.json' 'Commands' '{ "inline": false, "height": "420px" }' %} + +## Programmatic Navigation + +{% example '@nvidia-elements/viewport/viewport/viewport.examples.json' 'ProgrammaticNavigation' '{ "inline": false, "height": "420px" }' %} + +## Customizable Background + +{% example '@nvidia-elements/viewport/viewport/viewport.examples.json' 'Background' '{ "inline": false, "height": "420px" }' %} diff --git a/projects/site/src/docs/viewport/minimap.md b/projects/site/src/docs/viewport/minimap.md new file mode 100644 index 0000000000..427b05459b --- /dev/null +++ b/projects/site/src/docs/viewport/minimap.md @@ -0,0 +1,16 @@ +--- +{ + title: 'Minimap', + description: 'Show an overview of NVIDIA Elements viewport content and navigate its visible region.', + layout: 'docs.11ty.js', + tag: 'nve-viewport-minimap' +} +--- + +## Installation + +{% install 'nve-viewport-minimap' %} + +## Custom Preview + +{% example '@nvidia-elements/viewport/minimap/minimap.examples.json' 'Default' '{ "inline": false, "height": "420px" }' %} diff --git a/projects/site/src/docs/whats-new/09-2026.md b/projects/site/src/docs/whats-new/09-2026.md index d9120d9a3d..69151c0fe8 100644 --- a/projects/site/src/docs/whats-new/09-2026.md +++ b/projects/site/src/docs/whats-new/09-2026.md @@ -25,7 +25,7 @@ September adds a pan-and-zoom surface for spatial interfaces, more flexible navi ### Navigate Spatial Content with Viewport -The new [Viewport](/docs/elements/viewport/) gives diagrams, graphs, topology maps, and other canvas-style content a shared pan-and-zoom surface. Enable pointer, wheel, keyboard, and pinch navigation with `behavior-pan` and `behavior-zoom`, or set `behavior-pan="space"` to require holding Space before dragging. The `autofit` attribute frames content after the first layout, while `nve-viewport-gridlines` and `nve-viewport-minimap` help people keep their place. Toolbar buttons can control the viewport through Invoker commands such as `--zoom-in` and `--zoom-to-fit`. Viewport is not yet marked stable, so expect API refinements. +The new [Viewport](/docs/viewport/) gives diagrams, graphs, topology maps, and other canvas-style content a shared pan-and-zoom surface. Enable pointer, wheel, keyboard, and pinch navigation with `behavior-pan` and `behavior-zoom`, or set `behavior-pan="space"` to require holding Space before dragging. The `autofit` attribute frames content after the first layout, while `nve-viewport-gridlines` and `nve-viewport-minimap` help people keep their place. Toolbar buttons can control the viewport through Invoker commands such as `--zoom-in` and `--zoom-to-fit`. Viewport is not yet marked stable, so expect API refinements. ![Zoomed spatial content in Viewport with grid lines and a minimap](/static/video/releases/09-2026-viewport.webp) diff --git a/projects/viewport/.gitignore b/projects/viewport/.gitignore new file mode 100644 index 0000000000..5f030ac5b4 --- /dev/null +++ b/projects/viewport/.gitignore @@ -0,0 +1,4 @@ +dist/ +node_modules/ +coverage/ +.DS_Store diff --git a/projects/viewport/.visual/gridlines-crosses.dark.png b/projects/viewport/.visual/gridlines-crosses.dark.png new file mode 100644 index 0000000000..1c97faafca --- /dev/null +++ b/projects/viewport/.visual/gridlines-crosses.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d36575bde607a5592d2374262cb759df1e74a9e8137386b2372f42cf437a9f9 +size 4158 diff --git a/projects/viewport/.visual/gridlines-crosses.png b/projects/viewport/.visual/gridlines-crosses.png new file mode 100644 index 0000000000..30cdb56f59 --- /dev/null +++ b/projects/viewport/.visual/gridlines-crosses.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:48ba4563c892e62e59a0ba90178e5ad7d48579ecf45ff530d1ae41ea4f4ed603 +size 4128 diff --git a/projects/viewport/.visual/gridlines-dots.dark.png b/projects/viewport/.visual/gridlines-dots.dark.png new file mode 100644 index 0000000000..f1e3fe2f7c --- /dev/null +++ b/projects/viewport/.visual/gridlines-dots.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc585ed2215ae44d39fe0c46ded50e10b0ff32697be595eeea448afd75a5125e +size 4220 diff --git a/projects/viewport/.visual/gridlines-dots.png b/projects/viewport/.visual/gridlines-dots.png new file mode 100644 index 0000000000..c82c8bb56c --- /dev/null +++ b/projects/viewport/.visual/gridlines-dots.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f631073773047ad2195db77829bd110b5aa34f00136a2c893e7ee01eca0a3f06 +size 4204 diff --git a/projects/viewport/.visual/gridlines.dark.png b/projects/viewport/.visual/gridlines.dark.png new file mode 100644 index 0000000000..d377d70852 --- /dev/null +++ b/projects/viewport/.visual/gridlines.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77d918a32b6c3f4798f67f87825ce605eb0996a133d0ec4748178ef35beb0e2c +size 3923 diff --git a/projects/viewport/.visual/gridlines.png b/projects/viewport/.visual/gridlines.png new file mode 100644 index 0000000000..68b6747d65 --- /dev/null +++ b/projects/viewport/.visual/gridlines.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ec9c73de0381d5d24a8c77a7f1ee409e9942beb034062d61a3fe524b7e63324 +size 3900 diff --git a/projects/viewport/.visual/minimap-custom.dark.png b/projects/viewport/.visual/minimap-custom.dark.png new file mode 100644 index 0000000000..55d5fd1495 --- /dev/null +++ b/projects/viewport/.visual/minimap-custom.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27621a3e520ecec474af02ae0bfb7ab0a1883146bcf4fb4eb37d0cd07588cd5b +size 5358 diff --git a/projects/viewport/.visual/minimap-custom.png b/projects/viewport/.visual/minimap-custom.png new file mode 100644 index 0000000000..9bb76e300c --- /dev/null +++ b/projects/viewport/.visual/minimap-custom.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1adcc311765fc0c5de4930861d217d732eeba131571cb5bbd72a293df694ee9f +size 5456 diff --git a/projects/viewport/.visual/minimap.dark.png b/projects/viewport/.visual/minimap.dark.png new file mode 100644 index 0000000000..e1266870dc --- /dev/null +++ b/projects/viewport/.visual/minimap.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2425f795fdb8b6a3db84dc1dd28ca69b85539f1a6f40148614f76a83a12f5f01 +size 4763 diff --git a/projects/viewport/.visual/minimap.png b/projects/viewport/.visual/minimap.png new file mode 100644 index 0000000000..2cc56375e4 --- /dev/null +++ b/projects/viewport/.visual/minimap.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22137b71d9ea2ff5d3d8da16784752b97262a7d6d3ff73d871d1f82fbe2a39e5 +size 4818 diff --git a/projects/viewport/.visual/viewport.dark.png b/projects/viewport/.visual/viewport.dark.png new file mode 100644 index 0000000000..77c682aa08 --- /dev/null +++ b/projects/viewport/.visual/viewport.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd9b845cbcff343ea947d3fb42729bc6c36e2210f9be3d29b1784e0f787606c6 +size 3498 diff --git a/projects/viewport/.visual/viewport.png b/projects/viewport/.visual/viewport.png new file mode 100644 index 0000000000..b7089e1d4b --- /dev/null +++ b/projects/viewport/.visual/viewport.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1702da5f0893c3d67df34dad4cf9741151d759b1956806684fc2f6b220459b43 +size 3503 diff --git a/projects/viewport/CHANGELOG.md b/projects/viewport/CHANGELOG.md new file mode 100644 index 0000000000..98eff1b99e --- /dev/null +++ b/projects/viewport/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.0 + +Initial release. diff --git a/projects/viewport/DEVELOPMENT.md b/projects/viewport/DEVELOPMENT.md new file mode 100644 index 0000000000..fbe4901d53 --- /dev/null +++ b/projects/viewport/DEVELOPMENT.md @@ -0,0 +1,18 @@ +# Development + +| Command | Description | +| -------------------------- | ----------------------------------------- | +| `pnpm run build` | Build the library | +| `pnpm run build:watch` | Build the library with file watching | +| `pnpm run dev` | Start development mode with file watching | +| `pnpm run lint` | Lint source files | +| `pnpm run lint:fix` | Fix source lint issues | +| `pnpm run test` | Run unit tests | +| `pnpm run test:watch` | Run unit tests in watch mode | +| `pnpm run test:axe` | Run accessibility tests | +| `pnpm run test:coverage` | Run unit tests with coverage | +| `pnpm run test:types` | Type-check source, tests, and examples | +| `pnpm run test:visual` | Run visual regression tests | +| `pnpm run test:ssr` | Run server-side rendering tests | +| `pnpm run test:lighthouse` | Run Lighthouse performance tests | +| `pnpm run ci` | Run the project CI pipeline | diff --git a/projects/viewport/NOTICE.md b/projects/viewport/NOTICE.md new file mode 100644 index 0000000000..2dca287339 --- /dev/null +++ b/projects/viewport/NOTICE.md @@ -0,0 +1,44 @@ +NOTICE + +Elements - NVIDIA Elements Design System +Copyright 2024-2026 NVIDIA Corporation + +This project includes the following bundled third-party software: + +- lit v3.3.3 [BSD-3-Clause] + Copyright: Google LLC + +============================================================================== +BSD-3-Clause +============================================================================== + +The following bundled components are provided under the BSD-3-Clause license: + +lit v3.3.3 - Copyright Google LLC + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +For license details, see: BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause diff --git a/projects/viewport/README.md b/projects/viewport/README.md new file mode 100644 index 0000000000..86d81652a4 --- /dev/null +++ b/projects/viewport/README.md @@ -0,0 +1,89 @@ +# @nvidia-elements/viewport + +NVIDIA Design System and UI Agent Harness for AI/ML Factories, Robotics, and Autonomous Vehicles. + +Web Components for navigating spatial content with pan, zoom, content fitting, background grids, and a minimap. + +- [Documentation](https://NVIDIA.github.io/elements/docs/viewport/) +- [Changelog](https://NVIDIA.github.io/elements/docs/changelog/) +- [GitHub Repo](https://github.com/NVIDIA/elements) +- [npm](https://www.npmjs.com/package/@nvidia-elements/viewport) + +## Components + +| Component | Tag | Description | +| ----------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------- | +| [Viewport](https://NVIDIA.github.io/elements/docs/viewport/) | `nve-viewport` | A spatial surface with optional pan, zoom, and content fitting. | +| [Gridlines](https://NVIDIA.github.io/elements/docs/viewport/gridlines/) | `nve-viewport-gridlines` | A background grid with lines, dots, or crosses that adapts to zoom. | +| [Minimap](https://NVIDIA.github.io/elements/docs/viewport/minimap/) | `nve-viewport-minimap` | A content overview with a visible region indicator and direct navigation. | + +## Getting Started + +Install the package and its peer dependencies: + +```shell +npm install @nvidia-elements/viewport @nvidia-elements/core @nvidia-elements/themes +``` + +Load the base theme in your application's stylesheet: + +```css +@import '@nvidia-elements/themes/index.css'; +``` + +## Usage + +Register each component your application uses through its own entrypoint: + +```javascript +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; +import '@nvidia-elements/viewport/minimap/define.js'; +``` + +```html + + + + + + + + + + +``` + +Place spatial content in the default slot. Gridlines and Minimap must be direct children of the viewport; they use the `background` and `overlay` slots automatically. + +`behavior-pan` and `behavior-zoom` enable user navigation. Use `behavior-pan="space"` to require Space for primary pointer panning when content handles its own drag interactions. `autofit` frames the initial content, and `fit-inset` controls the space around it. + +Gridlines supports `lines`, `dots`, and `crosses` patterns. Minimap draws rectangles for the viewport's content elements; provide content in its `preview` slot to replace them with a custom preview. + +## Programmatic Navigation + +Use `reveal()` to frame a content region and `fitContents()` to frame all content: + +```javascript +const viewport = document.querySelector('nve-viewport'); + +viewport.reveal({ x: 0, y: 0, width: 900, height: 600 }, { animated: true }); +viewport.fitContents({ animated: true }); +``` + +Listen for `viewportchange` to observe committed transform changes. The event detail contains the current `x`, `y`, and `scale` values. + +## NVIDIA Elements Skill + +Install the Elements agent skill with the open [skills](https://www.skills.sh/nvidia/elements/elements) CLI: + +```shell +npx skills add https://github.com/nvidia/elements --skill elements +``` diff --git a/projects/viewport/eslint.config.js b/projects/viewport/eslint.config.js new file mode 100644 index 0000000000..ee96dbce97 --- /dev/null +++ b/projects/viewport/eslint.config.js @@ -0,0 +1,5 @@ +import { elementsRecommended } from '@nvidia-elements/lint/eslint'; +import { browserTypescriptConfig, libraryConfig, litConfig, jsonConfig } from '@internals/eslint'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [...elementsRecommended, ...browserTypescriptConfig, ...libraryConfig, ...litConfig, ...jsonConfig]; diff --git a/projects/viewport/package.json b/projects/viewport/package.json new file mode 100644 index 0000000000..2afb08431f --- /dev/null +++ b/projects/viewport/package.json @@ -0,0 +1,391 @@ +{ + "name": "@nvidia-elements/viewport", + "version": "0.0.0", + "release": { + "extends": "../../release.config.js" + }, + "description": "A viewport for spatial content with pan and zoom navigation.", + "keywords": [ + "nvidia", + "web-components", + "custom-elements", + "ui-components", + "design-system" + ], + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/NVIDIA/elements.git" + }, + "customElements": "dist/custom-elements.json", + "contributes": { + "html": { + "customData": [ + "./dist/data.html.json" + ] + }, + "snippets": [ + { + "language": "html", + "path": "./dist/data.snippets.json" + } + ] + }, + "files": [ + "README.md", + "CHANGELOG.md", + "NOTICE.md", + "package.json", + "dist/**/*" + ], + "exports": { + "./package.json": "./package.json", + "./custom-elements.json": "./dist/custom-elements.json", + "./data.html.json": "./dist/data.html.json", + "./*.examples.json": "./dist/*.examples.json", + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./index.js": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./gridlines": { + "types": "./dist/gridlines/index.d.ts", + "default": "./dist/gridlines/index.js" + }, + "./gridlines/define.js": { + "types": "./dist/gridlines/define.d.ts", + "default": "./dist/gridlines/define.js" + }, + "./minimap": { + "types": "./dist/minimap/index.d.ts", + "default": "./dist/minimap/index.js" + }, + "./minimap/define.js": { + "types": "./dist/minimap/define.d.ts", + "default": "./dist/minimap/define.js" + }, + "./viewport": { + "types": "./dist/viewport/index.d.ts", + "default": "./dist/viewport/index.js" + }, + "./viewport/define.js": { + "types": "./dist/viewport/define.d.ts", + "default": "./dist/viewport/define.js" + } + }, + "sideEffects": [ + "./src/**/define.ts", + "./**/define.js" + ], + "scripts": { + "ci": "wireit", + "dev": "wireit", + "build": "wireit", + "build:watch": "wireit", + "lint": "wireit", + "lint:fix": "wireit", + "test": "wireit", + "test:axe": "wireit", + "test:coverage": "wireit", + "test:types": "wireit", + "test:ssr": "wireit", + "test:visual": "wireit", + "test:watch": "wireit", + "test:lighthouse": "wireit" + }, + "dependencies": { + "lit": "catalog:publish" + }, + "peerDependencies": { + "@nvidia-elements/core": "workspace:^", + "@nvidia-elements/themes": "workspace:^" + }, + "devDependencies": { + "@eslint/js": "catalog:", + "@internals/eslint": "workspace:*", + "@internals/testing": "workspace:*", + "@internals/vite": "workspace:*", + "@nvidia-elements/lint": "workspace:*", + "@nvidia-elements/styles": "workspace:*", + "@vitest/browser": "catalog:", + "@vitest/coverage-istanbul": "catalog:", + "axe-core": "catalog:", + "eslint": "catalog:", + "lit": "catalog:", + "publint": "catalog:", + "stylelint": "catalog:", + "stylelint-config-standard": "catalog:", + "typescript": "catalog:", + "typescript-7": "catalog:", + "vite": "catalog:", + "vitest": "catalog:" + }, + "wireit": { + "dev": { + "command": "NODE_ENV=development vite build --watch", + "service": true, + "dependencies": [ + "../core:build", + "../internals/vite:ci" + ] + }, + "ci": { + "dependencies": [ + "build", + "lint", + "publint", + "test:axe", + "test:ssr", + "test:coverage", + "test:types", + "test:visual" + ] + }, + "publint": { + "command": "publint", + "dependencies": [ + "build" + ], + "files": [ + "package.json" + ], + "output": [] + }, + "build": { + "command": "NODE_ENV=production vite build", + "files": [ + "src/**", + "!src/**/*.test.ts", + "!src/**/*.test.lighthouse.ts", + "!src/**/*.test.axe.ts", + "!src/**/*.test.ssr.ts", + "!src/**/__screenshots__/**", + "package.json", + "tsconfig.json", + "tsconfig.lib.json", + "vite.config.ts" + ], + "output": [ + "dist/**" + ], + "dependencies": [ + "../internals/vite:ci", + { + "script": "../themes:build", + "cascade": false + }, + { + "script": "../core:build", + "cascade": false + } + ] + }, + "build:watch": { + "command": "vite build --watch", + "service": true, + "dependencies": [ + "build" + ] + }, + "test": { + "command": "vitest run", + "files": [ + "src/**", + "!src/**/*.test.axe.ts", + "!src/**/*.test.lighthouse.ts", + "!src/**/*.test.ssr.ts", + "!src/**/*.test.visual.ts", + "!src/**/__screenshots__/**", + "tsconfig.json", + "vite.config.ts", + "vitest.config.ts" + ], + "output": [], + "dependencies": [ + "build", + "../internals/testing:build", + "../internals/vite:ci", + "../core:build" + ] + }, + "test:coverage": { + "command": "vitest run --coverage", + "files": [ + "src/**", + "!src/**/*.test.axe.ts", + "!src/**/*.test.lighthouse.ts", + "!src/**/*.test.ssr.ts", + "!src/**/*.test.visual.ts", + "!src/**/__screenshots__/**", + "tsconfig.json", + "vite.config.ts", + "vitest.config.ts" + ], + "output": [ + "coverage/unit/**" + ], + "dependencies": [ + "build", + "../internals/testing:build", + "../internals/vite:ci", + "../core:build" + ] + }, + "test:types": { + "command": "node ./node_modules/typescript-7/bin/tsc --project tsconfig.json --noEmit", + "files": [ + "src/**/*.ts", + "tsconfig.json", + "../core/dist/**/*.d.ts", + "../forms/dist/**/*.d.ts", + "../internals/testing/dist/**/*.d.ts", + "../internals/vite/src/**/*.d.ts" + ], + "output": [], + "dependencies": [ + { + "script": "../core:build", + "cascade": false + }, + { + "script": "../internals/testing:build", + "cascade": false + } + ] + }, + "test:watch": { + "command": "vitest --watch", + "service": true + }, + "test:lighthouse": { + "command": "playwright-lock 'vitest run --config=vitest.lighthouse.ts'", + "files": [ + "src/index.test.lighthouse.ts", + "src/**/*.test.lighthouse.ts", + "vitest.lighthouse.html", + "vitest.lighthouse.ts" + ], + "output": [ + ".lighthouse" + ], + "dependencies": [ + "build", + "../internals/vite:ci" + ], + "env": { + "NODE_ENV": "production" + } + }, + "test:visual": { + "command": "vitest run --config=vitest.visual.ts", + "clean": false, + "files": [ + "dist/**/*.js", + "src/**/*.test.visual.ts", + "vitest.visual.html", + "vitest.visual.ts" + ], + "output": [ + ".visual/*.png", + "coverage/visual/**" + ], + "dependencies": [ + "../internals/vite:ci", + { + "script": "build", + "cascade": false + } + ] + }, + "test:axe": { + "command": "vitest run --config=vitest.axe.ts", + "files": [ + "dist/**/*.js", + "src/**/*.test.axe.ts", + "tsconfig.json", + "vitest.axe.ts" + ], + "output": [ + "coverage/axe/**" + ], + "dependencies": [ + "../internals/testing:build", + "../internals/vite:ci", + { + "script": "build", + "cascade": false + } + ], + "env": { + "NODE_ENV": "production" + } + }, + "test:ssr": { + "command": "vitest run --config=vitest.ssr.ts", + "files": [ + "dist/**/*.js", + "src/**/*.test.ssr.ts", + "tsconfig.json", + "vitest.ssr.ts", + "../themes/dist/**/*.css" + ], + "output": [ + "coverage/ssr/**" + ], + "dependencies": [ + "../internals/vite:ci", + { + "script": "build", + "cascade": false + }, + { + "script": "../themes:build", + "cascade": false + } + ], + "env": { + "NODE_ENV": "production" + } + }, + "lint": { + "dependencies": [ + "lint:eslint", + "lint:style" + ] + }, + "lint:eslint": { + "command": "eslint -c ./eslint.config.js --color --cache --cache-location .eslintcache/", + "files": [ + "src/**/*.ts", + "eslint.config.js" + ], + "output": [], + "dependencies": [ + "../internals/eslint:ci", + "../lint:build" + ] + }, + "lint:style": { + "command": "stylelint 'src/**/*.css' --config=../../stylelint.config.mjs", + "files": [ + "src/**/*.css", + "../../stylelint.config.mjs" + ], + "output": [] + }, + "lint:fix": { + "command": "eslint -c ./eslint.config.js --fix", + "dependencies": [ + "../internals/eslint:ci", + "../lint:build" + ] + } + }, + "publishConfig": { + "provenance": true + }, + "license": "Apache-2.0" +} diff --git a/projects/viewport/src/declarations.d.ts b/projects/viewport/src/declarations.d.ts new file mode 100644 index 0000000000..5db409c190 --- /dev/null +++ b/projects/viewport/src/declarations.d.ts @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +declare module '*.css' { + const content: string; + export default content; +} + +declare module '*.css?inline' { + const content: string; + export = content; +} diff --git a/projects/core/src/viewport/define.ts b/projects/viewport/src/gridlines/define.ts similarity index 61% rename from projects/core/src/viewport/define.ts rename to projects/viewport/src/gridlines/define.ts index 937b2158a7..2218eed0e4 100644 --- a/projects/core/src/viewport/define.ts +++ b/projects/viewport/src/gridlines/define.ts @@ -2,16 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import { define } from '@nvidia-elements/core/internal'; -import { Viewport, ViewportGridlines, ViewportMinimap } from '@nvidia-elements/core/viewport'; +import { ViewportGridlines } from '@nvidia-elements/viewport/gridlines'; +import '@nvidia-elements/viewport/viewport/define.js'; -define(Viewport); define(ViewportGridlines); -define(ViewportMinimap); declare global { interface HTMLElementTagNameMap { - 'nve-viewport': Viewport; 'nve-viewport-gridlines': ViewportGridlines; - 'nve-viewport-minimap': ViewportMinimap; } } diff --git a/projects/viewport/src/gridlines/gridlines-registration.test.ts b/projects/viewport/src/gridlines/gridlines-registration.test.ts new file mode 100644 index 0000000000..36c4187194 --- /dev/null +++ b/projects/viewport/src/gridlines/gridlines-registration.test.ts @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; + +describe('gridlines registration', () => { + let fixture: HTMLElement; + + beforeEach(async () => { + fixture = await createFixture(); + }); + + afterEach(() => { + removeFixture(fixture); + }); + + it('renders existing markup when the gridlines entrypoint loads before the viewport entrypoint', async () => { + fixture.innerHTML = ` + + + + `; + expect(customElements.get('nve-viewport')).toBeUndefined(); + + await import(/* @vite-ignore */ new URL('../../dist/gridlines/define.js', import.meta.url).href); + + expect(customElements.get('nve-viewport')).toBeDefined(); + expect(customElements.get('nve-viewport-minimap')).toBeUndefined(); + const viewport = fixture.querySelector('nve-viewport'); + const gridlines = fixture.querySelector('nve-viewport-gridlines'); + if (!viewport || !gridlines) { + throw new Error('Viewport and gridlines are missing.'); + } + await elementIsStable(viewport); + await elementIsStable(gridlines); + await expect + .poll(() => gridlines.shadowRoot?.querySelector('[data-gridline-pattern]')) + .toBeInstanceOf(SVGPatternElement); + + viewport.scale = 2; + await elementIsStable(viewport); + expect(gridlines.style.getPropertyValue('--_scale')).toBe('2'); + }); +}); diff --git a/projects/core/src/viewport/viewport-gridlines.css b/projects/viewport/src/gridlines/gridlines.css similarity index 100% rename from projects/core/src/viewport/viewport-gridlines.css rename to projects/viewport/src/gridlines/gridlines.css diff --git a/projects/viewport/src/gridlines/gridlines.examples.ts b/projects/viewport/src/gridlines/gridlines.examples.ts new file mode 100644 index 0000000000..02b66ea24e --- /dev/null +++ b/projects/viewport/src/gridlines/gridlines.examples.ts @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; + +export default { + title: 'Elements/Viewport/Gridlines', + component: 'nve-viewport-gridlines' +}; + +/** + * @summary Use grid lines as spatial reference points while navigating viewport content. + */ +export const Default = { + render: () => html` + + + + + + + + + + + ` +}; + +/** + * @summary Optionally configure viewport grid lines to use dots instead of lines. + */ +export const Dots = { + render: () => html` + + + + + + + + + + + ` +}; + +/** + * @summary Use crosses as spatial reference points to help users judge positions while keeping the content between them unobstructed. + */ +export const Crosses = { + render: () => html` + + + + + + + + + + + ` +}; diff --git a/projects/viewport/src/gridlines/gridlines.test.axe.ts b/projects/viewport/src/gridlines/gridlines.test.axe.ts new file mode 100644 index 0000000000..6f5f0748b6 --- /dev/null +++ b/projects/viewport/src/gridlines/gridlines.test.axe.ts @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import { afterEach, describe, expect, it } from 'vitest'; +import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; +import { runAxe } from '@internals/testing/axe'; +import { ViewportGridlines } from '@nvidia-elements/viewport/gridlines'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; + +describe(ViewportGridlines.metadata.tag, () => { + let fixture: HTMLElement; + + afterEach(() => removeFixture(fixture)); + + it.each(['lines', 'dots', 'crosses'] as const)('should pass axe check with %s', async pattern => { + fixture = await createFixture(html` + + + + + `); + const viewport = fixture.querySelector('nve-viewport'); + const gridlines = fixture.querySelector('nve-viewport-gridlines'); + if (!viewport || !gridlines) throw new Error('Gridlines fixture is missing.'); + await elementIsStable(viewport); + await elementIsStable(gridlines); + + const results = await runAxe(['nve-viewport']); + expect(results.violations.length).toBe(0); + }); +}); diff --git a/projects/viewport/src/gridlines/gridlines.test.lighthouse.ts b/projects/viewport/src/gridlines/gridlines.test.lighthouse.ts new file mode 100644 index 0000000000..45f7965b9b --- /dev/null +++ b/projects/viewport/src/gridlines/gridlines.test.lighthouse.ts @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; +import { lighthouseRunner } from '@internals/vite'; + +describe('gridlines lighthouse report', () => { + test('gridlines should meet lighthouse benchmarks', async () => { + const report = await lighthouseRunner.getReport( + 'nve-viewport-gridlines', + /* html */ ` + + +
content
+
+ + ` + ); + + expect(report.scores.performance).toBe(100); + expect(report.scores.accessibility).toBe(100); + expect(report.scores.bestPractices).toBe(100); + expect(report.payload.javascript.kb).toBeLessThan(25.1); + }); +}); diff --git a/projects/viewport/src/gridlines/gridlines.test.ssr.ts b/projects/viewport/src/gridlines/gridlines.test.ssr.ts new file mode 100644 index 0000000000..0bdd40d573 --- /dev/null +++ b/projects/viewport/src/gridlines/gridlines.test.ssr.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import { describe, expect, it } from 'vitest'; +import { ssrRunner } from '@internals/vite'; +import { ViewportGridlines } from '@nvidia-elements/viewport/gridlines'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; + +describe(ViewportGridlines.metadata.tag, () => { + it('should render in the background slot on the server', async () => { + const result = await ssrRunner.render(html` + + +
content
+
+ `); + + const tag = result.match(/]*>/)?.[0]; + expect(tag).toBeDefined(); + expect(tag).toContain('slot="background"'); + expect(tag).toContain('aria-hidden="true"'); + expect(result.match(/shadowroot="open"/g)).toHaveLength(2); + }); +}); diff --git a/projects/core/src/viewport/viewport-gridlines.test.ts b/projects/viewport/src/gridlines/gridlines.test.ts similarity index 95% rename from projects/core/src/viewport/viewport-gridlines.test.ts rename to projects/viewport/src/gridlines/gridlines.test.ts index 2d40b2d24f..058ec749ed 100644 --- a/projects/core/src/viewport/viewport-gridlines.test.ts +++ b/projects/viewport/src/gridlines/gridlines.test.ts @@ -4,9 +4,11 @@ import { html } from 'lit'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; -import { Viewport, ViewportGridlines } from '@nvidia-elements/core/viewport'; -import { selectGridlineInterval } from './viewport-gridlines.utils.js'; -import '@nvidia-elements/core/viewport/define.js'; +import type { Viewport } from '@nvidia-elements/viewport/viewport'; +import { ViewportGridlines } from '@nvidia-elements/viewport/gridlines'; +import { selectGridlineInterval } from './gridlines.utils.js'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; class TestResizeObserver implements ResizeObserver { static readonly instances: TestResizeObserver[] = []; @@ -36,8 +38,11 @@ describe(ViewportGridlines.metadata.tag, () => { `); - viewport = fixture.querySelector(Viewport.metadata.tag); - gridlines = fixture.querySelector(ViewportGridlines.metadata.tag); + const viewportElement = fixture.querySelector('nve-viewport'); + const gridlinesElement = fixture.querySelector('nve-viewport-gridlines'); + if (!viewportElement || !gridlinesElement) throw new Error('Viewport fixture is missing.'); + viewport = viewportElement; + gridlines = gridlinesElement; await elementIsStable(viewport); await elementIsStable(gridlines); }); @@ -48,6 +53,10 @@ describe(ViewportGridlines.metadata.tag, () => { vi.unstubAllGlobals(); }); + it('registers without unrelated viewport components', () => { + expect(customElements.get('nve-viewport-minimap')).toBeUndefined(); + }); + it('defines reflected properties with normalized defaults', async () => { expect(customElements.get(ViewportGridlines.metadata.tag)).toBe(ViewportGridlines); expect({ diff --git a/projects/viewport/src/gridlines/gridlines.test.visual.ts b/projects/viewport/src/gridlines/gridlines.test.visual.ts new file mode 100644 index 0000000000..ddfbdf0deb --- /dev/null +++ b/projects/viewport/src/gridlines/gridlines.test.visual.ts @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; +import { visualRunner } from '@internals/vite'; + +describe('gridlines visual', () => { + test('gridlines should match visual baseline', async () => { + const report = await visualRunner.render('gridlines', template()); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); + + test('gridlines should match visual baseline dark theme', async () => { + const report = await visualRunner.render('gridlines.dark', template('dark')); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); + + test('gridlines dots should match visual baseline', async () => { + const report = await visualRunner.render('gridlines-dots', template('', 'dots')); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); + + test('gridlines dots should match visual baseline dark theme', async () => { + const report = await visualRunner.render('gridlines-dots.dark', template('dark', 'dots')); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); + + test('gridlines crosses should match visual baseline', async () => { + const report = await visualRunner.render('gridlines-crosses', template('', 'crosses')); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); + + test('gridlines crosses should match visual baseline dark theme', async () => { + const report = await visualRunner.render('gridlines-crosses.dark', template('dark', 'crosses')); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); +}); + +function template(theme: '' | 'dark' = '', pattern: 'lines' | 'dots' | 'crosses' = 'lines') { + return /* html */ ` + + + + +
•︎•︎•︎•︎•︎
+
•︎•︎•︎
+
+ + `; +} diff --git a/projects/core/src/viewport/viewport-gridlines.ts b/projects/viewport/src/gridlines/gridlines.ts similarity index 95% rename from projects/core/src/viewport/viewport-gridlines.ts rename to projects/viewport/src/gridlines/gridlines.ts index 8a2f264c40..c1e951a113 100644 --- a/projects/core/src/viewport/viewport-gridlines.ts +++ b/projects/viewport/src/gridlines/gridlines.ts @@ -5,14 +5,10 @@ import { html, LitElement, nothing, svg, type PropertyValues } from 'lit'; import { property } from 'lit/decorators/property.js'; import { styleMap } from 'lit/directives/style-map.js'; import { finiteOr, hostAttr, positiveFiniteOr, useStyles } from '@nvidia-elements/core/internal'; -import styles from './viewport-gridlines.css?inline'; -import { - createGridlineCoverage, - gridlineCoverageNeedsUpdate, - selectGridlineInterval -} from './viewport-gridlines.utils.js'; -import { Viewport } from './viewport.js'; -import type { ViewportRect } from './viewport.types.js'; +import styles from './gridlines.css?inline'; +import { createGridlineCoverage, gridlineCoverageNeedsUpdate, selectGridlineInterval } from './gridlines.utils.js'; +import { Viewport } from '@nvidia-elements/viewport/viewport'; +import type { ViewportRect } from '@nvidia-elements/viewport/viewport'; const DEFAULT_STEP = 10; const DEFAULT_TARGET_SPACING = 64; @@ -30,9 +26,9 @@ interface ProjectionSyncOptions { /** * @element nve-viewport-gridlines * @description Renders origin-stable, zoom-adaptive gridline patterns behind viewport content. - * @documentation https://nvidia.github.io/elements/docs/elements/viewport/ + * @documentation https://nvidia.github.io/elements/docs/viewport/gridlines/ * @since 0.0.0 - * @entrypoint \@nvidia-elements/core/viewport + * @entrypoint \@nvidia-elements/viewport/gridlines * @cssprop --color - Sets the gridline pattern color. * @cssprop --line-width - Sets the approximate stroke width in CSS pixels. * @cssprop --dot-radius - Sets the approximate dot radius in CSS pixels. diff --git a/projects/core/src/viewport/viewport-gridlines.utils.ts b/projects/viewport/src/gridlines/gridlines.utils.ts similarity index 97% rename from projects/core/src/viewport/viewport-gridlines.utils.ts rename to projects/viewport/src/gridlines/gridlines.utils.ts index 9c28f6e5a4..51acf8976c 100644 --- a/projects/core/src/viewport/viewport-gridlines.utils.ts +++ b/projects/viewport/src/gridlines/gridlines.utils.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import type { ViewportRect } from './viewport.types.js'; +import type { ViewportRect } from '@nvidia-elements/viewport/viewport'; const MIN_RETAINED_SPACING_FACTOR = 0.5; const MAX_RETAINED_SPACING_FACTOR = 1.5; diff --git a/projects/viewport/src/gridlines/index.ts b/projects/viewport/src/gridlines/index.ts new file mode 100644 index 0000000000..3e9ad65d54 --- /dev/null +++ b/projects/viewport/src/gridlines/index.ts @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './gridlines.js'; diff --git a/projects/viewport/src/index.test.lighthouse.ts b/projects/viewport/src/index.test.lighthouse.ts new file mode 100644 index 0000000000..9586b2f6a2 --- /dev/null +++ b/projects/viewport/src/index.test.lighthouse.ts @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { expect, test, describe } from 'vitest'; +import { lighthouseRunner } from '@internals/vite'; + +describe('lighthouse report', () => { + test('@nvidia-elements/viewport JS Bundles should remain within compressed bundle limits', async () => { + const report = await lighthouseRunner.getReport('bundles', /* html */` + + `); + + expect(report.payload.javascript.kb).toBeLessThan(2); + }); +}); diff --git a/projects/viewport/src/index.test.ts b/projects/viewport/src/index.test.ts new file mode 100644 index 0000000000..e6faa980d7 --- /dev/null +++ b/projects/viewport/src/index.test.ts @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, it, expect } from 'vitest'; +import { VERSION } from './index.js'; + +describe('@nvidia-elements/viewport', () => { + it('should export VERSION', () => { + expect(VERSION).toBe('0.0.0'); + }); +}); diff --git a/projects/viewport/src/index.ts b/projects/viewport/src/index.ts new file mode 100644 index 0000000000..f8f476a942 --- /dev/null +++ b/projects/viewport/src/index.ts @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +export const VERSION = '0.0.0'; diff --git a/projects/viewport/src/minimap/define.ts b/projects/viewport/src/minimap/define.ts new file mode 100644 index 0000000000..44b1c12737 --- /dev/null +++ b/projects/viewport/src/minimap/define.ts @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { define } from '@nvidia-elements/core/internal'; +import { ViewportMinimap } from '@nvidia-elements/viewport/minimap'; +import '@nvidia-elements/viewport/viewport/define.js'; + +define(ViewportMinimap); + +declare global { + interface HTMLElementTagNameMap { + 'nve-viewport-minimap': ViewportMinimap; + } +} diff --git a/projects/viewport/src/minimap/index.ts b/projects/viewport/src/minimap/index.ts new file mode 100644 index 0000000000..c2f27af724 --- /dev/null +++ b/projects/viewport/src/minimap/index.ts @@ -0,0 +1,4 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +export * from './minimap.js'; diff --git a/projects/viewport/src/minimap/minimap-registration.test.ts b/projects/viewport/src/minimap/minimap-registration.test.ts new file mode 100644 index 0000000000..bb70551008 --- /dev/null +++ b/projects/viewport/src/minimap/minimap-registration.test.ts @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; + +describe('minimap registration', () => { + let fixture: HTMLElement; + + beforeEach(async () => { + fixture = await createFixture(); + }); + + afterEach(() => { + removeFixture(fixture); + }); + + it('renders existing markup when the minimap entrypoint loads before the viewport entrypoint', async () => { + fixture.innerHTML = ` + +
content
+ +
+ `; + expect(customElements.get('nve-viewport')).toBeUndefined(); + + await import(/* @vite-ignore */ new URL('../../dist/minimap/define.js', import.meta.url).href); + + expect(customElements.get('nve-viewport')).toBeDefined(); + expect(customElements.get('nve-viewport-gridlines')).toBeUndefined(); + const viewport = fixture.querySelector('nve-viewport'); + const minimap = fixture.querySelector('nve-viewport-minimap'); + if (!viewport || !minimap) { + throw new Error('Viewport and minimap are missing.'); + } + await elementIsStable(viewport); + await elementIsStable(minimap); + await expect.poll(() => minimap.shadowRoot?.querySelector('[data-visible-region]')).toBeInstanceOf(SVGRectElement); + expect(minimap.shadowRoot?.querySelector('[data-item-key]')).toBeInstanceOf(SVGRectElement); + expect(minimap.matches(':state(pan-eligible)')).toBe(true); + }); +}); diff --git a/projects/core/src/viewport/viewport-minimap.css b/projects/viewport/src/minimap/minimap.css similarity index 100% rename from projects/core/src/viewport/viewport-minimap.css rename to projects/viewport/src/minimap/minimap.css diff --git a/projects/viewport/src/minimap/minimap.examples.ts b/projects/viewport/src/minimap/minimap.examples.ts new file mode 100644 index 0000000000..d877f1965d --- /dev/null +++ b/projects/viewport/src/minimap/minimap.examples.ts @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/minimap/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; + +export default { + title: 'Elements/Viewport/Minimap', + component: 'nve-viewport-minimap' +}; + +/** + * @summary Provide custom minimap content to replace the default bounding boxes with a simplified representation of the viewport content. + */ +export const Default = { + render: () => html` + + + + + + + + + + + + + + + + + + + ` +}; diff --git a/projects/viewport/src/minimap/minimap.test.axe.ts b/projects/viewport/src/minimap/minimap.test.axe.ts new file mode 100644 index 0000000000..28c2bf2dc8 --- /dev/null +++ b/projects/viewport/src/minimap/minimap.test.axe.ts @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import { afterEach, describe, expect, it } from 'vitest'; +import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; +import { runAxe } from '@internals/testing/axe'; +import { ViewportMinimap } from '@nvidia-elements/viewport/minimap'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/minimap/define.js'; + +describe(ViewportMinimap.metadata.tag, () => { + let fixture: HTMLElement; + + afterEach(() => removeFixture(fixture)); + + it('should pass axe check with interactive viewport content', async () => { + fixture = await createFixture(html` + + +
+ + +
+
+ `); + const viewport = fixture.querySelector('nve-viewport'); + const minimap = fixture.querySelector('nve-viewport-minimap'); + if (!viewport || !minimap) throw new Error('Minimap fixture is missing.'); + await elementIsStable(viewport); + await elementIsStable(minimap); + + const results = await runAxe(['nve-viewport']); + expect(results.violations.length).toBe(0); + }); +}); diff --git a/projects/viewport/src/minimap/minimap.test.lighthouse.ts b/projects/viewport/src/minimap/minimap.test.lighthouse.ts new file mode 100644 index 0000000000..4f60b4ea1e --- /dev/null +++ b/projects/viewport/src/minimap/minimap.test.lighthouse.ts @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; +import { lighthouseRunner } from '@internals/vite'; + +describe('minimap lighthouse report', () => { + test('minimap should meet lighthouse benchmarks', async () => { + const report = await lighthouseRunner.getReport( + 'nve-viewport-minimap', + /* html */ ` + + +
content
+
+ + ` + ); + + expect(report.scores.performance).toBe(100); + expect(report.scores.accessibility).toBe(100); + expect(report.scores.bestPractices).toBe(100); + expect(report.payload.javascript.kb).toBeLessThan(25.1); + }); +}); diff --git a/projects/viewport/src/minimap/minimap.test.ssr.ts b/projects/viewport/src/minimap/minimap.test.ssr.ts new file mode 100644 index 0000000000..b280c5077b --- /dev/null +++ b/projects/viewport/src/minimap/minimap.test.ssr.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import { describe, expect, it } from 'vitest'; +import { ssrRunner } from '@internals/vite'; +import { ViewportMinimap } from '@nvidia-elements/viewport/minimap'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/minimap/define.js'; + +describe(ViewportMinimap.metadata.tag, () => { + it('should render in the overlay slot on the server', async () => { + const result = await ssrRunner.render(html` + + +
content
+
+ `); + + const tag = result.match(/]*>/)?.[0]; + expect(tag).toBeDefined(); + expect(tag).toContain('slot="overlay"'); + expect(tag).toContain('aria-hidden="true"'); + expect(result.match(/shadowroot="open"/g)).toHaveLength(2); + }); +}); diff --git a/projects/core/src/viewport/viewport-minimap.test.ts b/projects/viewport/src/minimap/minimap.test.ts similarity index 98% rename from projects/core/src/viewport/viewport-minimap.test.ts rename to projects/viewport/src/minimap/minimap.test.ts index 1b6cad078f..b8e6c335e0 100644 --- a/projects/core/src/viewport/viewport-minimap.test.ts +++ b/projects/viewport/src/minimap/minimap.test.ts @@ -6,12 +6,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; import { Viewport, - ViewportMinimap, type ViewportPanDetail, type ViewportPanEndDetail, type ViewportZoomDetail -} from '@nvidia-elements/core/viewport'; -import '@nvidia-elements/core/viewport/define.js'; +} from '@nvidia-elements/viewport/viewport'; +import { ViewportMinimap } from '@nvidia-elements/viewport/minimap'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/minimap/define.js'; describe(ViewportMinimap.metadata.tag, () => { let fixture: HTMLElement; @@ -46,6 +47,10 @@ describe(ViewportMinimap.metadata.tag, () => { vi.restoreAllMocks(); }); + it('registers without unrelated viewport components', () => { + expect(customElements.get('nve-viewport-gridlines')).toBeUndefined(); + }); + it('defines the associated element and assigns the fixed overlay slot without a tab stop', () => { expect(customElements.get(ViewportMinimap.metadata.tag)).toBe(ViewportMinimap); expect(minimap.slot).toBe('overlay'); @@ -184,7 +189,9 @@ describe(ViewportMinimap.metadata.tag, () => { await flushProjection(minimap); expect(itemKeys(minimap)).toEqual([]); - expect(minimap.shadowRoot?.querySelector('slot[name="preview"]')?.assignedElements()).toEqual([preview]); + expect(minimap.shadowRoot?.querySelector('slot[name="preview"]')?.assignedElements()).toEqual([ + preview + ]); expect(preview.querySelectorAll('circle, path')).toHaveLength(2); expect(previewProjection(minimap).scale).toBeCloseTo(0.2); expect(visibleRegionRect(minimap).x).toBeCloseTo(16); diff --git a/projects/core/src/viewport/viewport.test.visual.ts b/projects/viewport/src/minimap/minimap.test.visual.ts similarity index 51% rename from projects/core/src/viewport/viewport.test.visual.ts rename to projects/viewport/src/minimap/minimap.test.visual.ts index 4bb0077145..4b539500d3 100644 --- a/projects/core/src/viewport/viewport.test.visual.ts +++ b/projects/viewport/src/minimap/minimap.test.visual.ts @@ -4,58 +4,28 @@ import { describe, expect, test } from 'vitest'; import { visualRunner } from '@internals/vite'; -describe('viewport visual', () => { - test('viewport should match visual baseline', async () => { - const report = await visualRunner.render('viewport', template()); +describe('minimap visual', () => { + test('minimap should match visual baseline', async () => { + const report = await visualRunner.render('minimap', minimapTemplate(), { waitFor: waitForMinimap }); expect(report.maxDiffPercentage).toBeLessThan(1); }); - test('viewport should match visual baseline dark theme', async () => { - const report = await visualRunner.render('viewport.dark', template('dark')); - expect(report.maxDiffPercentage).toBeLessThan(1); - }); - - test('viewport dots should match visual baseline', async () => { - const report = await visualRunner.render('viewport-dots', template('', 'dots')); - expect(report.maxDiffPercentage).toBeLessThan(1); - }); - - test('viewport dots should match visual baseline dark theme', async () => { - const report = await visualRunner.render('viewport-dots.dark', template('dark', 'dots')); - expect(report.maxDiffPercentage).toBeLessThan(1); - }); - - test('viewport crosses should match visual baseline', async () => { - const report = await visualRunner.render('viewport-crosses', template('', 'crosses')); - expect(report.maxDiffPercentage).toBeLessThan(1); - }); - - test('viewport crosses should match visual baseline dark theme', async () => { - const report = await visualRunner.render('viewport-crosses.dark', template('dark', 'crosses')); - expect(report.maxDiffPercentage).toBeLessThan(1); - }); - - test('viewport minimap should match visual baseline', async () => { - const report = await visualRunner.render('viewport-minimap', minimapTemplate(), { waitFor: waitForMinimap }); - expect(report.maxDiffPercentage).toBeLessThan(1); - }); - - test('viewport minimap should match visual baseline dark theme', async () => { - const report = await visualRunner.render('viewport-minimap.dark', minimapTemplate('dark'), { + test('minimap should match visual baseline dark theme', async () => { + const report = await visualRunner.render('minimap.dark', minimapTemplate('dark'), { waitFor: waitForMinimap }); expect(report.maxDiffPercentage).toBeLessThan(1); }); - test('viewport minimap custom should match visual baseline', async () => { - const report = await visualRunner.render('viewport-minimap-custom', minimapTemplate('', 'custom'), { + test('minimap custom should match visual baseline', async () => { + const report = await visualRunner.render('minimap-custom', minimapTemplate('', 'custom'), { waitFor: waitForMinimap }); expect(report.maxDiffPercentage).toBeLessThan(1); }); - test('viewport minimap custom should match visual baseline dark theme', async () => { - const report = await visualRunner.render('viewport-minimap-custom.dark', minimapTemplate('dark', 'custom'), { + test('minimap custom should match visual baseline dark theme', async () => { + const report = await visualRunner.render('minimap-custom.dark', minimapTemplate('dark', 'custom'), { waitFor: waitForMinimap }); expect(report.maxDiffPercentage).toBeLessThan(1); @@ -66,33 +36,6 @@ async function waitForMinimap(waitForFunction: (...args: unknown[]) => Promise document.querySelector('nve-viewport-minimap')?.hasAttribute('data-visual-ready')); } -function template(theme: '' | 'dark' = '', pattern: 'lines' | 'dots' | 'crosses' = 'lines') { - return /* html */ ` - - - - -
•︎•︎•︎•︎•︎
-
•︎•︎•︎
-
- - `; -} - function minimapTemplate(theme: '' | 'dark' = '', preview: 'automatic' | 'custom' = 'automatic') { const content = preview === 'custom' @@ -121,7 +64,9 @@ function minimapTemplate(theme: '' | 'dark' = '', preview: 'automatic' | 'custom : ''; return /* html */ ` diff --git a/projects/core/src/viewport/viewport-minimap.ts b/projects/viewport/src/minimap/minimap.ts similarity index 98% rename from projects/core/src/viewport/viewport-minimap.ts rename to projects/viewport/src/minimap/minimap.ts index 2ae5dcfd1e..d07ebc1e96 100644 --- a/projects/core/src/viewport/viewport-minimap.ts +++ b/projects/viewport/src/minimap/minimap.ts @@ -15,22 +15,22 @@ import { type WheelGestureInput, useStyles } from '@nvidia-elements/core/internal'; -import styles from './viewport-minimap.css?inline'; +import styles from './minimap.css?inline'; import { createViewportMinimapProjection, projectViewportMinimapRect, unprojectViewportMinimapPoint, type ViewportMinimapProjection -} from './viewport-minimap.utils.js'; -import { anchoredTransform } from './viewport-projection.utils.js'; -import { Viewport } from './viewport.js'; +} from './minimap.utils.js'; +import { anchoredTransform } from '../viewport/viewport-projection.utils.js'; +import { Viewport } from '@nvidia-elements/viewport/viewport'; import type { ViewportPanEndRequest, ViewportPanUpdateProposal, ViewportPanSession, ViewportRect, ViewportTransform -} from './viewport.types.js'; +} from '@nvidia-elements/viewport/viewport'; const DRAG_THRESHOLD = 3; @@ -61,9 +61,9 @@ interface AutomaticPreviewItem extends ViewportRect { /** * @element nve-viewport-minimap * @description Provides a simplified overview and direct navigation for its parent viewport. - * @documentation https://nvidia.github.io/elements/docs/elements/viewport/ + * @documentation https://nvidia.github.io/elements/docs/viewport/minimap/ * @since 0.0.0 - * @entrypoint \@nvidia-elements/core/viewport + * @entrypoint \@nvidia-elements/viewport/minimap * @cssprop --background - Color of the surface behind preview geometry. * @cssprop --color - Color of automatically drawn root rectangles. * @cssprop --indicator-color - Color of the current visible-region indicator. diff --git a/projects/core/src/viewport/viewport-minimap.utils.test.ts b/projects/viewport/src/minimap/minimap.utils.test.ts similarity index 74% rename from projects/core/src/viewport/viewport-minimap.utils.test.ts rename to projects/viewport/src/minimap/minimap.utils.test.ts index 51406c651b..f9183c8195 100644 --- a/projects/core/src/viewport/viewport-minimap.utils.test.ts +++ b/projects/viewport/src/minimap/minimap.utils.test.ts @@ -2,11 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { describe, expect, it } from 'vitest'; -import { - createViewportMinimapProjection, - hasPositiveFiniteRect, - projectViewportMinimapRect -} from './viewport-minimap.utils.js'; +import { createViewportMinimapProjection, hasPositiveFiniteRect, projectViewportMinimapRect } from './minimap.utils.js'; describe('viewport minimap projection', () => { it('accepts finite coordinates with positive dimensions', () => { @@ -25,6 +21,23 @@ describe('viewport minimap projection', () => { expect(hasPositiveFiniteRect(rect)).toBe(false); }); + it.each([ + { size: { width: 0, height: 144 }, visible: { x: 0, y: 0, width: 400, height: 300 } }, + { size: { width: 192, height: 0 }, visible: { x: 0, y: 0, width: 400, height: 300 } }, + { size: { width: 192, height: 144 }, visible: { x: 0, y: 0, width: 0, height: 300 } }, + { size: { width: 192, height: 144 }, visible: { x: 0, y: 0, width: 400, height: 0 } } + ])('does not project an empty minimap or visible area', ({ size, visible }) => { + expect(createViewportMinimapProjection([], visible, size)).toBeUndefined(); + }); + + it('ignores invalid content bounds while projecting the visible area', () => { + const visible = { x: 0, y: 0, width: 400, height: 300 }; + const size = { width: 192, height: 144 }; + expect(createViewportMinimapProjection([{ x: Number.NaN, y: 0, width: 10, height: 10 }], visible, size)).toEqual( + createViewportMinimapProjection([], visible, size) + ); + }); + it('includes content bounds and the visible rectangle in its padded extent', () => { const projection = createViewportMinimapProjection( [{ x: 50, y: 0, width: 50, height: 100 }], diff --git a/projects/core/src/viewport/viewport-minimap.utils.ts b/projects/viewport/src/minimap/minimap.utils.ts similarity index 97% rename from projects/core/src/viewport/viewport-minimap.utils.ts rename to projects/viewport/src/minimap/minimap.utils.ts index 10b51a07cb..2a70cfaa42 100644 --- a/projects/core/src/viewport/viewport-minimap.utils.ts +++ b/projects/viewport/src/minimap/minimap.utils.ts @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import type { ViewportPoint, ViewportRect } from './viewport.types.js'; +import type { ViewportPoint, ViewportRect } from '@nvidia-elements/viewport/viewport'; const PROJECTION_PADDING = 0.1; diff --git a/projects/viewport/src/viewport/define.ts b/projects/viewport/src/viewport/define.ts new file mode 100644 index 0000000000..fa45555178 --- /dev/null +++ b/projects/viewport/src/viewport/define.ts @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { define } from '@nvidia-elements/core/internal'; +import { Viewport } from '@nvidia-elements/viewport/viewport'; + +define(Viewport); + +declare global { + interface HTMLElementTagNameMap { + 'nve-viewport': Viewport; + } +} diff --git a/projects/core/src/viewport/index.ts b/projects/viewport/src/viewport/index.ts similarity index 89% rename from projects/core/src/viewport/index.ts rename to projects/viewport/src/viewport/index.ts index ac7b50f45e..8338b1f01c 100644 --- a/projects/core/src/viewport/index.ts +++ b/projects/viewport/src/viewport/index.ts @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from './viewport.js'; -export * from './viewport-gridlines.js'; -export * from './viewport-minimap.js'; export type { ViewportAnimationOptions, ViewportDiscretePanDetail, diff --git a/projects/core/src/viewport/viewport-animation.controller.test.ts b/projects/viewport/src/viewport/viewport-animation.controller.test.ts similarity index 100% rename from projects/core/src/viewport/viewport-animation.controller.test.ts rename to projects/viewport/src/viewport/viewport-animation.controller.test.ts diff --git a/projects/core/src/viewport/viewport-animation.controller.ts b/projects/viewport/src/viewport/viewport-animation.controller.ts similarity index 100% rename from projects/core/src/viewport/viewport-animation.controller.ts rename to projects/viewport/src/viewport/viewport-animation.controller.ts diff --git a/projects/core/src/viewport/viewport-autofit.controller.test.ts b/projects/viewport/src/viewport/viewport-autofit.controller.test.ts similarity index 100% rename from projects/core/src/viewport/viewport-autofit.controller.test.ts rename to projects/viewport/src/viewport/viewport-autofit.controller.test.ts diff --git a/projects/core/src/viewport/viewport-autofit.controller.ts b/projects/viewport/src/viewport/viewport-autofit.controller.ts similarity index 100% rename from projects/core/src/viewport/viewport-autofit.controller.ts rename to projects/viewport/src/viewport/viewport-autofit.controller.ts diff --git a/projects/core/src/viewport/viewport-fitting.utils.test.ts b/projects/viewport/src/viewport/viewport-fitting.utils.test.ts similarity index 100% rename from projects/core/src/viewport/viewport-fitting.utils.test.ts rename to projects/viewport/src/viewport/viewport-fitting.utils.test.ts diff --git a/projects/core/src/viewport/viewport-fitting.utils.ts b/projects/viewport/src/viewport/viewport-fitting.utils.ts similarity index 100% rename from projects/core/src/viewport/viewport-fitting.utils.ts rename to projects/viewport/src/viewport/viewport-fitting.utils.ts diff --git a/projects/core/src/viewport/viewport-gesture-navigation.controller.test.ts b/projects/viewport/src/viewport/viewport-gesture-navigation.controller.test.ts similarity index 99% rename from projects/core/src/viewport/viewport-gesture-navigation.controller.test.ts rename to projects/viewport/src/viewport/viewport-gesture-navigation.controller.test.ts index aa29ec02b0..68d32c61e1 100644 --- a/projects/core/src/viewport/viewport-gesture-navigation.controller.test.ts +++ b/projects/viewport/src/viewport/viewport-gesture-navigation.controller.test.ts @@ -175,7 +175,9 @@ describe('ViewportGestureNavigationController', () => { ); expect(sessions).toHaveLength(1); expect(sessions[0]?.update).toHaveBeenCalledWith(expect.objectContaining({ next: { scale: 1, x: -10, y: 0 } })); - expect(vi.mocked(sessions[0]?.update).mock.calls[0]?.[0]).not.toHaveProperty('source'); + const session = sessions[0]; + if (!session) throw new Error('Expected a pan session.'); + expect(vi.mocked(session.update).mock.calls[0]?.[0]).not.toHaveProperty('source'); expect(sessions[0]?.end).toHaveBeenCalledWith(expect.objectContaining({ interrupted: false, reason: 'up' })); expect(host.matches(':state(panning)')).toBe(false); expect(target.dispatchEvent(pointerEvent('click', { buttons: 0, pointerId: 1 }))).toBe(false); diff --git a/projects/core/src/viewport/viewport-gesture-navigation.controller.ts b/projects/viewport/src/viewport/viewport-gesture-navigation.controller.ts similarity index 100% rename from projects/core/src/viewport/viewport-gesture-navigation.controller.ts rename to projects/viewport/src/viewport/viewport-gesture-navigation.controller.ts diff --git a/projects/core/src/viewport/viewport-navigation.controller.test.ts b/projects/viewport/src/viewport/viewport-navigation.controller.test.ts similarity index 98% rename from projects/core/src/viewport/viewport-navigation.controller.test.ts rename to projects/viewport/src/viewport/viewport-navigation.controller.test.ts index a2c03375f3..668477e74f 100644 --- a/projects/core/src/viewport/viewport-navigation.controller.test.ts +++ b/projects/viewport/src/viewport/viewport-navigation.controller.test.ts @@ -286,7 +286,9 @@ describe('ViewportNavigationController', () => { }); it('rejects disabled input and resumes once after reconnecting', () => { - const command = (): void => host.dispatchEvent(new CommandEvent('command', { command: '--pan-right' })); + const command = (): void => { + host.dispatchEvent(new CommandEvent('command', { command: '--pan-right' })); + }; command(); expect(host.requestPan).not.toHaveBeenCalled(); diff --git a/projects/core/src/viewport/viewport-navigation.controller.ts b/projects/viewport/src/viewport/viewport-navigation.controller.ts similarity index 100% rename from projects/core/src/viewport/viewport-navigation.controller.ts rename to projects/viewport/src/viewport/viewport-navigation.controller.ts diff --git a/projects/core/src/viewport/viewport-navigation.types.ts b/projects/viewport/src/viewport/viewport-navigation.types.ts similarity index 100% rename from projects/core/src/viewport/viewport-navigation.types.ts rename to projects/viewport/src/viewport/viewport-navigation.types.ts diff --git a/projects/core/src/viewport/viewport-projection.utils.test.ts b/projects/viewport/src/viewport/viewport-projection.utils.test.ts similarity index 100% rename from projects/core/src/viewport/viewport-projection.utils.test.ts rename to projects/viewport/src/viewport/viewport-projection.utils.test.ts diff --git a/projects/core/src/viewport/viewport-projection.utils.ts b/projects/viewport/src/viewport/viewport-projection.utils.ts similarity index 100% rename from projects/core/src/viewport/viewport-projection.utils.ts rename to projects/viewport/src/viewport/viewport-projection.utils.ts diff --git a/projects/core/src/viewport/viewport.css b/projects/viewport/src/viewport/viewport.css similarity index 100% rename from projects/core/src/viewport/viewport.css rename to projects/viewport/src/viewport/viewport.css diff --git a/projects/core/src/viewport/viewport.examples.ts b/projects/viewport/src/viewport/viewport.examples.ts similarity index 77% rename from projects/core/src/viewport/viewport.examples.ts rename to projects/viewport/src/viewport/viewport.examples.ts index fef54ded11..d194875bda 100644 --- a/projects/core/src/viewport/viewport.examples.ts +++ b/projects/viewport/src/viewport/viewport.examples.ts @@ -8,7 +8,9 @@ import '@nvidia-elements/core/button-group/define.js'; import '@nvidia-elements/core/card/define.js'; import '@nvidia-elements/core/input/define.js'; import '@nvidia-elements/core/toolbar/define.js'; -import '@nvidia-elements/core/viewport/define.js'; +import '@nvidia-elements/viewport/viewport/define.js'; +import '@nvidia-elements/viewport/gridlines/define.js'; +import '@nvidia-elements/viewport/minimap/define.js'; export default { title: 'Elements/Viewport', @@ -42,38 +44,6 @@ export const Default = { ` }; -/** - * @summary Provide custom minimap content to replace the default bounding boxes with a simplified representation of the viewport content. - */ -export const Minimap = { - render: () => html` - - - - - - - - - - - - - - - - - - - ` -}; /** * @summary Place native and Elements controls inside a viewport without losing their standard interactions. @@ -328,55 +298,7 @@ export const ProgrammaticNavigation = { ` }; -/** - * @summary Optionally configure viewport grid lines to use dots instead of lines. - */ -export const Dots = { - render: () => html` - - - - - - - - - - ` -}; - -/** - * @summary Use crosses as spatial reference points to help users judge positions while keeping the content between them unobstructed. - */ -export const Crosses = { - render: () => html` - - - - - - - - - - - ` -}; /** * @summary Place custom content in the background slot to provide a spatial reference without affecting content fitting. diff --git a/projects/core/src/viewport/viewport.test.axe.ts b/projects/viewport/src/viewport/viewport.test.axe.ts similarity index 77% rename from projects/core/src/viewport/viewport.test.axe.ts rename to projects/viewport/src/viewport/viewport.test.axe.ts index 55d4a86325..0b3ee314ce 100644 --- a/projects/core/src/viewport/viewport.test.axe.ts +++ b/projects/viewport/src/viewport/viewport.test.axe.ts @@ -5,8 +5,8 @@ import { html } from 'lit'; import { afterEach, describe, expect, it } from 'vitest'; import { createFixture, elementIsStable, removeFixture } from '@internals/testing'; import { runAxe } from '@internals/testing/axe'; -import { Viewport } from '@nvidia-elements/core/viewport'; -import '@nvidia-elements/core/viewport/define.js'; +import { Viewport } from '@nvidia-elements/viewport/viewport'; +import '@nvidia-elements/viewport/viewport/define.js'; describe(Viewport.metadata.tag, () => { let fixture: HTMLElement; @@ -16,15 +16,14 @@ describe(Viewport.metadata.tag, () => { it('should pass axe check with interactive slotted content', async () => { fixture = await createFixture(html` - -
`); - const element = fixture.querySelector(Viewport.metadata.tag); + const element = fixture.querySelector('nve-viewport'); + if (!element) throw new Error('Viewport fixture is missing.'); await elementIsStable(element); const results = await runAxe([Viewport.metadata.tag]); diff --git a/projects/core/src/viewport/viewport.test.lighthouse.ts b/projects/viewport/src/viewport/viewport.test.lighthouse.ts similarity index 84% rename from projects/core/src/viewport/viewport.test.lighthouse.ts rename to projects/viewport/src/viewport/viewport.test.lighthouse.ts index 53cfdf94a5..fe88bbe714 100644 --- a/projects/core/src/viewport/viewport.test.lighthouse.ts +++ b/projects/viewport/src/viewport/viewport.test.lighthouse.ts @@ -10,12 +10,10 @@ describe('viewport lighthouse report', () => { 'nve-viewport', /* html */ ` -
content
-
` ); diff --git a/projects/viewport/src/viewport/viewport.test.ssr.ts b/projects/viewport/src/viewport/viewport.test.ssr.ts new file mode 100644 index 0000000000..166aa02774 --- /dev/null +++ b/projects/viewport/src/viewport/viewport.test.ssr.ts @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { html } from 'lit'; +import { describe, expect, it } from 'vitest'; +import { ssrRunner } from '@internals/vite'; +import { Viewport } from '@nvidia-elements/viewport/viewport'; +import '@nvidia-elements/viewport/viewport/define.js'; + +describe(Viewport.metadata.tag, () => { + it('should render content layers on the server', async () => { + const result = await ssrRunner.render(html` + +
content
+
+ `); + + expect(result.includes('shadowroot="open"')).toBe(true); + expect(result.includes('slot name="background"')).toBe(true); + expect(result.includes('slot name="overlay"')).toBe(true); + expect(result.includes('class="plane"')).toBe(true); + expect(result).toContain('
content
'); + }); +}); diff --git a/projects/core/src/viewport/viewport.test.ts b/projects/viewport/src/viewport/viewport.test.ts similarity index 99% rename from projects/core/src/viewport/viewport.test.ts rename to projects/viewport/src/viewport/viewport.test.ts index 39181ba3bd..ec9da18b91 100644 --- a/projects/core/src/viewport/viewport.test.ts +++ b/projects/viewport/src/viewport/viewport.test.ts @@ -15,8 +15,8 @@ import { type ViewportTransform, type ViewportZoomDetail, type ViewportZoomProposal -} from '@nvidia-elements/core/viewport'; -import '@nvidia-elements/core/viewport/define.js'; +} from '@nvidia-elements/viewport/viewport'; +import '@nvidia-elements/viewport/viewport/define.js'; describe(Viewport.metadata.tag, () => { let element: Viewport; @@ -28,7 +28,9 @@ describe(Viewport.metadata.tag, () => { vi.fn(() => ({ matches: false })) ); fixture = await createFixture(html``); - element = fixture.querySelector(Viewport.metadata.tag); + const viewport = fixture.querySelector('nve-viewport'); + if (!viewport) throw new Error('Viewport fixture is missing.'); + element = viewport; await elementIsStable(element); }); @@ -38,6 +40,11 @@ describe(Viewport.metadata.tag, () => { vi.unstubAllGlobals(); }); + it('registers without unrelated viewport components', () => { + expect(customElements.get('nve-viewport-gridlines')).toBeUndefined(); + expect(customElements.get('nve-viewport-minimap')).toBeUndefined(); + }); + describe('transform, projection, and rendering', () => { it('defines transform-only defaults', () => { expect(customElements.get(Viewport.metadata.tag)).toBe(Viewport); @@ -670,7 +677,7 @@ describe(Viewport.metadata.tag, () => { element.dispatchEvent(pointerEvent('pointerdown', { pointerId: 1 })); element.dispatchEvent(pointerEvent('pointermove', { clientX: 10, pointerId: 1 })); element.addEventListener('pan', event => { - if (event.detail.source === 'wheel') event.preventDefault(); + if (event instanceof CustomEvent && event.detail.source === 'wheel') event.preventDefault(); }); element.dispatchEvent(wheelEvent({ deltaX: 30 })); @@ -707,8 +714,8 @@ describe(Viewport.metadata.tag, () => { element.behaviorZoom = true; await elementIsStable(element); vi.spyOn(element, 'setPointerCapture').mockImplementation(() => {}); - const zoom = vi.fn((event: CustomEvent) => { - if (event.detail.source === 'pinch') event.preventDefault(); + const zoom = vi.fn((event: Event) => { + if (event instanceof CustomEvent && event.detail.source === 'pinch') event.preventDefault(); }); const pan = vi.fn(); element.addEventListener('zoom', zoom); diff --git a/projects/viewport/src/viewport/viewport.test.visual.ts b/projects/viewport/src/viewport/viewport.test.visual.ts new file mode 100644 index 0000000000..c295f325ae --- /dev/null +++ b/projects/viewport/src/viewport/viewport.test.visual.ts @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; +import { visualRunner } from '@internals/vite'; + +describe('viewport visual', () => { + test('viewport should match visual baseline', async () => { + const report = await visualRunner.render('viewport', template()); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); + + test('viewport should match visual baseline dark theme', async () => { + const report = await visualRunner.render('viewport.dark', template('dark')); + expect(report.maxDiffPercentage).toBeLessThan(1); + }); +}); + +function template(theme: '' | 'dark' = '') { + return /* html */ ` + + + +
•︎•︎•︎•︎•︎
+
•︎•︎•︎
+
+ + `; +} diff --git a/projects/core/src/viewport/viewport.ts b/projects/viewport/src/viewport/viewport.ts similarity index 99% rename from projects/core/src/viewport/viewport.ts rename to projects/viewport/src/viewport/viewport.ts index 1bb9de6231..96ecd3fad8 100644 --- a/projects/core/src/viewport/viewport.ts +++ b/projects/viewport/src/viewport/viewport.ts @@ -58,9 +58,9 @@ const BEHAVIOR_PAN_CONVERTER = { /** * @element nve-viewport * @description A viewport provides a spatial surface for arbitrary content, with optional pan and zoom navigation. - * @documentation https://nvidia.github.io/elements/docs/elements/viewport/ + * @documentation https://nvidia.github.io/elements/docs/viewport/ * @since 0.0.0 - * @entrypoint \@nvidia-elements/core/viewport + * @entrypoint \@nvidia-elements/viewport/viewport * @slot - Content positioned in the viewport's infinite content space. * @slot background - Custom content rendered behind viewport content and excluded from content fitting * @slot overlay - Interactive content fixed above the transformed plane and excluded from content fitting diff --git a/projects/core/src/viewport/viewport.types.ts b/projects/viewport/src/viewport/viewport.types.ts similarity index 100% rename from projects/core/src/viewport/viewport.types.ts rename to projects/viewport/src/viewport/viewport.types.ts diff --git a/projects/viewport/tsconfig.json b/projects/viewport/tsconfig.json new file mode 100644 index 0000000000..8c341efb7a --- /dev/null +++ b/projects/viewport/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "noUncheckedSideEffectImports": true, + "skipLibCheck": true, + "module": "es2022", + "target": "es2022", + "lib": ["es2022", "dom", "dom.iterable"], + "typeRoots": ["./node_modules/@types"], + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "dist/", + "rootDir": "./", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "experimentalDecorators": true, + "useDefineForClassFields": false, + "isolatedModules": true, + "strict": true, + "strictPropertyInitialization": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "paths": { + "@nvidia-elements/viewport": ["./src/index.js"], + "@nvidia-elements/viewport/*": ["./src/*"], + "@nvidia-elements/viewport/*.js": ["./src/*.js"] + } + }, + "include": ["src/**/*.ts"] +} diff --git a/projects/viewport/tsconfig.lib.json b/projects/viewport/tsconfig.lib.json new file mode 100644 index 0000000000..af21f6f61c --- /dev/null +++ b/projects/viewport/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": [] + }, + "exclude": [ + "**/*.test.ts", + "**/*.test.axe.ts", + "**/*.test.ssr.ts", + "**/*.test.visual.ts", + "**/*.test.lighthouse.ts", + "**/*.examples.ts" + ] +} diff --git a/projects/viewport/vite.config.ts b/projects/viewport/vite.config.ts new file mode 100644 index 0000000000..eeec18347b --- /dev/null +++ b/projects/viewport/vite.config.ts @@ -0,0 +1,13 @@ +import { resolve } from 'path'; +import { UserConfig, defineConfig, mergeConfig } from 'vite'; +import { libraryBuildConfig } from '@internals/vite'; + +export default defineConfig(() => { + const config: UserConfig = { + resolve: { + alias: { '@nvidia-elements/viewport': resolve(import.meta.dirname, './src') } + } + }; + + return mergeConfig(libraryBuildConfig, config); +}); diff --git a/projects/viewport/vitest.axe.ts b/projects/viewport/vitest.axe.ts new file mode 100644 index 0000000000..01d7a43bce --- /dev/null +++ b/projects/viewport/vitest.axe.ts @@ -0,0 +1,20 @@ +import { resolve } from 'path'; +import { mergeConfig } from 'vitest/config'; +import { libraryAxeTestConfig } from '@internals/vite/configs/axe.js'; + +export default mergeConfig(libraryAxeTestConfig, { + root: import.meta.dirname, + resolve: { + alias: { '@nvidia-elements/viewport': resolve(import.meta.dirname, './src') } + }, + test: { + include: ['./src/**/*.test.axe.ts'], + outputFile: { + junit: './coverage/axe/junit.xml' + }, + coverage: { + provider: 'istanbul', + reportsDirectory: './coverage/axe' + } + } +}); diff --git a/projects/viewport/vitest.config.ts b/projects/viewport/vitest.config.ts new file mode 100644 index 0000000000..29df52f6ab --- /dev/null +++ b/projects/viewport/vitest.config.ts @@ -0,0 +1,23 @@ +import { resolve } from 'path'; +import { mergeConfig } from 'vitest/config'; +import { libraryTestConfig } from '@internals/vite/configs/test.js'; + +const config = mergeConfig(libraryTestConfig, { + root: import.meta.dirname, + resolve: { + alias: { '@nvidia-elements/viewport': resolve(import.meta.dirname, './src') } + }, + test: { + include: ['./src/**/*.test.ts'], + coverage: { + thresholds: { + lines: 90, + branches: 90, + functions: 90, + statements: 90 + } + } + } +}); + +export default config; diff --git a/projects/viewport/vitest.lighthouse.html b/projects/viewport/vitest.lighthouse.html new file mode 100644 index 0000000000..ca07455545 --- /dev/null +++ b/projects/viewport/vitest.lighthouse.html @@ -0,0 +1,19 @@ + + + + + + + + Lighthouse + + + +

Lighthouse Test

+ + diff --git a/projects/viewport/vitest.lighthouse.ts b/projects/viewport/vitest.lighthouse.ts new file mode 100644 index 0000000000..58bf11a01d --- /dev/null +++ b/projects/viewport/vitest.lighthouse.ts @@ -0,0 +1,11 @@ +import { mergeConfig } from 'vitest/config'; +import { libraryLighthouseTestConfig } from '@internals/vite/configs/lighthouse.js'; + +export default mergeConfig(libraryLighthouseTestConfig, { + test: { + include: ['src/**/*.test.lighthouse.ts'], + outputFile: { + junit: './coverage/lighthouse/junit.xml' + } + } +}); diff --git a/projects/viewport/vitest.ssr.ts b/projects/viewport/vitest.ssr.ts new file mode 100644 index 0000000000..3de60fdde3 --- /dev/null +++ b/projects/viewport/vitest.ssr.ts @@ -0,0 +1,15 @@ +import { resolve } from 'path'; +import { mergeConfig } from 'vitest/config'; +import { libraryLitSSRTestConfig } from '@internals/vite/configs/ssr.js'; + +export default mergeConfig(libraryLitSSRTestConfig, { + resolve: { + alias: { '@nvidia-elements/viewport': resolve(import.meta.dirname, './dist') } + }, + test: { + include: ['./src/**/*.test.ssr.ts'], + outputFile: { + junit: './coverage/ssr/junit.xml' + } + } +}); diff --git a/projects/viewport/vitest.visual.html b/projects/viewport/vitest.visual.html new file mode 100644 index 0000000000..9f772650a6 --- /dev/null +++ b/projects/viewport/vitest.visual.html @@ -0,0 +1,35 @@ + + + + + + + + Visual + + + + diff --git a/projects/viewport/vitest.visual.ts b/projects/viewport/vitest.visual.ts new file mode 100644 index 0000000000..58fed0d07f --- /dev/null +++ b/projects/viewport/vitest.visual.ts @@ -0,0 +1,12 @@ +import { mergeConfig } from 'vitest/config'; +import { libraryVisualTestConfig } from '@internals/vite/configs/visual.js'; + +export default mergeConfig(libraryVisualTestConfig, { + root: import.meta.dirname, + test: { + include: ['src/**/*.test.visual.ts'], + outputFile: { + junit: './coverage/visual/junit.xml' + } + } +});