diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d6063c639..7b3e8022d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -14,6 +14,8 @@ For detailed information on issue and pull request statuses, process for testing - **Clone your Fork**: Clone your forked repository to your local machine using Git. This will create a local copy of the project you can work on. - **Create a Branch**: Create a new branch for your specific contribution. This helps keep your changes isolated and organized. +No contributor license agreement or sign-off is required. As stated in the [GitHub Terms of Service](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service#6-contributions-under-repository-license), your contribution is licensed under the project's [MIT License](../LICENSE), and by submitting it you agree that you have the right to license it under those terms. + ## Set up You will need at least [Node >= 24.0.0](https://nodejs.org/en) installed on your machine. @@ -30,6 +32,60 @@ npm ci npm start ``` +## Development workflow + +### Linting and formatting + +To scan the project for linting errors, run: + +```sh +npm run lint +``` + +To automatically fix most linting and formatting errors, run: + +```sh +npm run format +``` + +Linting and formatting also run in a pre-commit hook. + +### Type checks and consistency checks + +```sh +npm run check +``` + +This runs every `check-*` script: type checks for the sources, scripts and stories, the import-boundary check and the import-alias check. + +### Testing with Web Test Runner + +To run the suite of Web Test Runner tests, run: + +```sh +npm run test +``` + +To run the tests in watch mode, run: + +```sh +npm run test:watch +``` + +### Demoing with Storybook + +To start a local instance of Storybook for your component, run: + +```sh +npm run storybook +``` + +To build a production version of Storybook, run: + +```sh +npm run storybook:build +``` + ## Making Changes - **Code Style**: Follow the [existing code style conventions](./CODING_GUIDELINES.md) used in the project. This might involve specific formatting guidelines or linting tools. Refer to the project's codebase or any existing documentation for details. @@ -50,6 +106,40 @@ Each component directory holds a `spec.md` describing that component: its overvi Specifications do not carry ownership, approval or sign-off sections, and the revision history has no author column — `git` already records who changed what. Design hand-off links, such as Figma files, go under `### End-user experience` and are preserved across updates. A pull request that changes behavior without updating the affected specification is incomplete, and reviewers will ask for it. +- **Changelog**: Add an entry under `[Unreleased]` in [CHANGELOG.md](../CHANGELOG.md) for every user-visible change. The file follows [Keep a Changelog](https://keepachangelog.com/), so use the `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed` and `Security` categories. A fix for a vulnerability goes under `Security`, with a link to the advisory once it is published. + +## Accessibility + +Accessibility is a requirement, not a feature. See [ACCESSIBILITY.md](../ACCESSIBILITY.md) for the conformance target and the platform constraints that shape how the components expose semantics. + +- New and changed component specifications must audit the component with axe, on both its light DOM and its shadow DOM, in each state the specification exercises: `await expect(el).to.be.accessible()` and `await expect(el).shadowDom.to.be.accessible()`. +- Follow the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) pattern for the component's role, including its keyboard interaction. Add keyboard tests for it. +- Publish semantics through `ElementInternals` and ARIA element reflection rather than IDREF attributes, so that relations work across shadow boundaries. Use the controllers under `src/internals/controllers` instead of setting ARIA attributes by hand. +- Disable an axe rule only when it misreports semantics published through internals or element reflection, assert the real relation in the specification instead, and document the exception next to the shared options in `src/internals/testing/helpers.spec.ts`. +- Verify interactive changes by hand with a keyboard and a screen reader before requesting review. + +## Dependencies + +Runtime dependencies increase the install footprint and the attack surface of every application that uses the library, so they are added rarely and deliberately. + +- **Discuss first.** Open an issue or a discussion before adding a runtime dependency or an optional peer dependency. Prefer a small, focused implementation in `src/internals` over a package that does more than the component needs. +- **Licenses.** Runtime and peer dependencies must be licensed under MIT, BSD-2-Clause, BSD-3-Clause, ISC, Apache-2.0, 0BSD or an equivalent permissive license. Copyleft licenses (GPL, LGPL, AGPL, SSPL) are not accepted for anything that ships to consumers. Dual-licensed packages are accepted when one of the options is permissive. +- **Manifests.** A runtime dependency is declared in both `package.json` and the published manifest `scripts/_package.json`. Optional peer dependencies are declared with `peerDependenciesMeta.optional: true` in the published manifest. +- **Lockfile.** Commit `package-lock.json` changes together with the manifest change. Install with `npm ci`, never `npm install`, so the lockfile stays authoritative. +- **Updates.** Routine npm version bumps are done by maintainers in batches, so do not open a pull request only to bump a dependency. Pin a GitHub Action to a commit SHA with the version in a trailing comment when you add or update it. See [SECURITY.md](../SECURITY.md#dependencies) for how updates are raised. +- **Dev dependencies** follow the same license rules and are otherwise at the maintainers' discretion. + +## Security + +Never report a vulnerability in a public issue, discussion or pull request. Use [private vulnerability reporting](https://github.com/IgniteUI/igniteui-webcomponents/security/advisories/new) as described in [SECURITY.md](../SECURITY.md). + +When you contribute code, keep the following in mind: + +- Treat every value that reaches a component from the host page as untrusted. Render text as text; when a feature must render HTML, sanitize it and make the sanitizer replaceable, as the chat markdown renderer does. +- Do not add network requests, storage access or telemetry. The library's [privacy commitments](../PRIVACY.md) depend on this. +- Do not introduce `eval`, `new Function`, string-based timers or other string-to-code paths. +- Update [THREAT-MODEL.md](../THREAT-MODEL.md) when a change adds a new way for data to reach the DOM, a new network request, a new browser capability or a new release step. +- Changes to the workflows under `.github/workflows` or to the scripts that build and publish the package are reviewed for supply-chain impact. Keep job permissions minimal and actions pinned. ## Contributing Code diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..7173c50eb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 💬 Ask a question + url: https://github.com/IgniteUI/igniteui-webcomponents/discussions + about: Questions, ideas and general discussion belong in GitHub Discussions. + - name: 🗨️ Discord community + url: https://discord.gg/39MjrTRqds + about: Chat with other users and the team. + - name: 🏢 Infragistics support + url: https://www.infragistics.com/about-us/contact-us + about: Support for commercial Ignite UI products such as the Grids and Dock Manager. diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 000000000..9770e4ea3 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,17 @@ +# Support + +Where to go depending on what you need: + +| I want to... | Go to | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| Read the documentation | [Product documentation](https://www.infragistics.com/products/ignite-ui-web-components) and [Storybook](https://igniteui.github.io/igniteui-webcomponents) | +| Report a bug or an accessibility problem | [Bug report](https://github.com/IgniteUI/igniteui-webcomponents/issues/new?template=bug_report.yaml). For accessibility, see [ACCESSIBILITY.md](../ACCESSIBILITY.md). | +| Request a component or feature | [Component request](https://github.com/IgniteUI/igniteui-webcomponents/issues/new?template=component.md) | +| Ask a question or share an idea | [GitHub Discussions](https://github.com/IgniteUI/igniteui-webcomponents/discussions) | +| Chat with the community | [Discord](https://discord.gg/39MjrTRqds) | +| Report a security vulnerability | [Private vulnerability reporting](https://github.com/IgniteUI/igniteui-webcomponents/security/advisories/new). See [SECURITY.md](../SECURITY.md). Never open a public issue for this. | +| Get help with a commercial Ignite UI product | [Infragistics support](https://www.infragistics.com/about-us/contact-us) | + +Before opening an issue, search the existing [issues](https://github.com/IgniteUI/igniteui-webcomponents/issues) and [discussions](https://github.com/IgniteUI/igniteui-webcomponents/discussions). A bug report with a minimal reproduction, for example on StackBlitz, is resolved much faster than one without. + +Only the [latest major version](../SECURITY.md#supported-versions) receives new fixes; the previous major receives critical security fixes only. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 384c100dd..293c06e7b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -31,4 +31,6 @@ Closes # - [ ] My code follows the project's coding standards - [ ] I have tested my changes locally - [ ] I have updated documentation if needed +- [ ] I have added a `CHANGELOG.md` entry under `[Unreleased]` - [ ] Breaking changes are documented in the description +- [ ] I have read the [contributing guidelines](https://github.com/IgniteUI/igniteui-webcomponents/blob/master/.github/CONTRIBUTING.md), including the accessibility, dependency and security rules diff --git a/.github/workflows/gh-pages-deploy.yml b/.github/workflows/gh-pages-deploy.yml index d29e7269e..35dad948d 100644 --- a/.github/workflows/gh-pages-deploy.yml +++ b/.github/workflows/gh-pages-deploy.yml @@ -19,14 +19,14 @@ jobs: url: ${{ steps.build-publish.outputs.page_url }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' - id: build-publish - uses: bitovi/github-actions-storybook-to-github-pages@v1.0.4 + uses: bitovi/github-actions-storybook-to-github-pages@ddd9d35f670cceedd2bfc444be681001b0709730 # v1.0.4 with: path: storybook-static install_command: npm ci diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index f203e78f8..a7378ac79 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -25,8 +25,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7.0.1 - - uses: actions/setup-node@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: '24' cache: 'npm' @@ -48,9 +48,9 @@ jobs: # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v7.0.1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: ${{ matrix.node-version }} cache: 'npm' @@ -62,7 +62,7 @@ jobs: - name: Restore the Playwright browsers id: playwright-cache - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }} @@ -81,7 +81,7 @@ jobs: - name: Publish to coveralls.io if: matrix.node-version == '24.x' - uses: coverallsapp/github-action@v2 + uses: coverallsapp/github-action@8d6379e14d29928660c4ba802d8e85393440b329 # v2.3.8 with: github-token: ${{ github.token }} fail-on-error: false diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000..64af962b6 --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,56 @@ +# OpenSSF Scorecard: checks the repository's supply-chain posture (pinned +# dependencies, token permissions, branch protection, code review, and so on) +# and publishes the result to the Scorecard API, which powers the README badge. +# +# The publishing API rejects results from workflows that do not follow its +# rules: no workflow-level env or write permissions, and only the allow-listed +# actions in this job. Keep it as it is. +# https://github.com/ossf/scorecard-action#workflow-restrictions +name: OpenSSF Scorecard + +permissions: read-all + +on: + push: + branches: [master] + # Branch-protection settings change outside of pushes. + branch_protection_rule: + schedule: + # Saturdays at 01:30 UTC. + - cron: '30 1 * * 6' + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + # Upload the SARIF to code scanning. + security-events: write + # OIDC token for publish_results. + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Run analysis + uses: ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc # v2.4.4 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: Upload SARIF artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: scorecard-sarif + path: results.sarif + retention-days: 5 + + - name: Upload to code scanning + uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 + with: + sarif_file: results.sarif diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md new file mode 100644 index 000000000..59b7af86f --- /dev/null +++ b/ACCESSIBILITY.md @@ -0,0 +1,50 @@ +# Accessibility + +Ignite UI for Web Components is built to be usable with a keyboard, a screen reader and other assistive technology. This document states what the library aims for, how that is verified, where the platform still limits what a Shadow DOM component can express, and how to report a problem. + +## Conformance target + +The components target [WCAG 2.1](https://www.w3.org/TR/WCAG21/) level AA for the parts of a page they render. Interactive components follow the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/) patterns for their role, such as combobox, listbox, tablist, tree, dialog and slider, including the keyboard interaction each pattern specifies. + +A component supplies its own semantics, keyboard handling, focus management and contrast within its themes. The host application remains responsible for page-level requirements, such as headings, landmarks, page titles, the text of labels it passes to a component, and the contrast of custom colors it applies through CSS custom properties. + +No formal conformance report (VPAT or ACR) is published for this package. + +## How accessibility is verified + +**Automated audits.** Component specifications run [axe-core](https://github.com/dequelabs/axe-core) through `chai-a11y-axe` against the component's light DOM, its shadow DOM, or both, in the states they exercise. New and changed specifications audit both trees. The audits run with the default axe ruleset, which covers the WCAG 2.0 and 2.1 A and AA success criteria axe can test, the WCAG 2.2 AA rules axe ships, and its best-practice rules. A failing audit fails the test run, so a regression cannot merge. + +Two things about automated audits are worth knowing: + +- Automated tools detect only a portion of accessibility problems. Keyboard operability, focus order, the quality of a name or description, and screen-reader announcements need a person to verify them. +- axe does not see ARIA that the components publish through `ElementInternals`, and some of its rules check for a content attribute, so they miss a relation set through ARIA element reflection. Where a rule misreports for this reason, it is disabled for that component and the real relation is asserted directly in the specification instead. These exceptions live in `src/internals/testing/helpers.spec.ts` and are documented there. + +**Storybook.** The Storybook build includes the accessibility addon, which runs axe against each story and shows the result in the panel. + +**Manual verification.** Components are checked by hand with a keyboard and with screen readers, primarily NVDA with Chrome and Firefox on Windows. Manual checks cover keyboard interaction against the APG pattern, focus visibility, announcements of state changes, and behavior with reduced motion and forced-colors modes. + +## Shadow DOM and the limits of the platform + +The components render in Shadow DOM. This gives them style and markup encapsulation, but ARIA was designed around a single document, and some of its mechanisms do not cross a shadow boundary: + +- **IDREF relations do not cross shadow roots.** Attributes such as `aria-labelledby`, `aria-describedby`, `aria-controls` and `aria-activedescendant` refer to elements by ID, and an ID inside one shadow root is not visible from another. A label element in the page cannot be referenced from an input inside a component's shadow root with an IDREF, and vice versa. +- **Composite roles are hard to split across roots.** Patterns such as a combobox that owns a listbox, or a tablist whose panels live in the page, require relations between elements that end up in different tree scopes. + +Where the platform offers a way around this, the components use it: + +- **`ElementInternals`.** Components attach internals and publish their role and ARIA state through it, so the host element carries the correct semantics without content attributes that a consumer could clobber. A controller in `src/internals/controllers/internals.ts` keeps that state in sync with component properties. Where a tool needs to see the role or the `aria-label` as a content attribute, it is mirrored there as well. A `role` or `aria-label` that the author sets on the host always wins. +- **ARIA element reflection.** Relations are set as element references (`ariaLabelledByElements`, `ariaDescribedByElements`, `ariaControlsElements`, `ariaActiveDescendantElement`) rather than IDREF strings. Element reflection resolves across shadow boundaries into ancestor tree scopes, which lets a composite component point at an element the page or another component owns. The projection controller in `src/internals/controllers/aria-projection.ts` carries those references to the native control inside an input-shaped component, since that is the element assistive technology lands on. One exception is deliberate: while no label or description is projected, the label and the description the component renders itself are referenced with same-root `aria-labelledby` and `aria-describedby` IDREFs, because they live in the same shadow root as the control and a content attribute stays visible to tools that read only attributes. +- **One naming order for form controls.** Every form associated component names its native control from the first source that is present: the host `aria-labelledby`, then external `