Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,17 @@ updates:
labels: [dependencies]

- package-ecosystem: npm
directories: [/examples/vite, /examples/next]
directories:
[
/examples/vite,
/examples/next,
/examples/vue,
/examples/nuxt,
/examples/sveltekit,
/examples/solid,
/examples/angular,
/examples/astro,
]
schedule:
interval: monthly
cooldown:
Expand Down
40 changes: 39 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
# every source file opens with the licence and copyright lines LICENSE gives (SPDX); CONTRIBUTING.md says how
- if: matrix.node == 'latest'
run: |
missing="$(git ls-files '*.ts' '*.tsx' '*.mjs' '*.js' '*.css' '*.html' | while read -r file; do
missing="$(git ls-files '*.ts' '*.tsx' '*.mjs' '*.js' '*.css' '*.html' '*.vue' '*.svelte' '*.astro' | while read -r file; do
head -n 4 "$file" | grep -q 'SPDX-License-Identifier: MIT' && head -n 4 "$file" | grep -q 'Copyright (c) 2026 Arcwright Studio' || echo "$file"
done)"
if [ -n "$missing" ]; then
Expand All @@ -55,6 +55,44 @@ jobs:
run: npm run coverage
- run: npm run check:package

# every example, installed from the package as it would be published, built with its framework's own tool and driven in a browser
examples:
name: examples
runs-on: ubuntu-26.04
timeout-minutes: 30
steps:
- run: git config --global init.defaultBranch main
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: lts/*
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run check:examples

# each framework entry, tested against the oldest version of its framework that package.json supports
oldest:
name: oldest frameworks
runs-on: ubuntu-26.04
timeout-minutes: 20
steps:
- run: git config --global init.defaultBranch main
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: lts/*
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run check:oldest

# every commit of a pull request certifies the Developer Certificate of Origin (CONTRIBUTING.md) with a sign-off naming its author;
# Dependabot's updates are made by a tool, not a person
sign-off:
Expand Down
48 changes: 48 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
# weekly too, so a query added to CodeQL meets what is already on main
schedule:
- cron: '23 5 * * 1'

permissions:
contents: read

# a newer push to a pull request replaces its run under way; every push to main is analysed
concurrency:
group: codeql-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-26.04
timeout-minutes: 20
permissions:
contents: read
# to upload what the analysis found
security-events: write
strategy:
fail-fast: false
matrix:
# the workflows, and the source with its tests, scripts and examples
language: [actions, javascript-typescript]
steps:
# the name for the branch checkout starts its repository with (unset, git prints a hint about its default)
- run: git config --global init.defaultBranch main
# each action is pinned to the commit of its release, which Dependabot keeps current
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
languages: ${{ matrix.language }}
# nothing is compiled: CodeQL reads the source as it is
build-mode: none
queries: security-extended
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
category: /language:${{ matrix.language }}
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

Svifa follows [semantic versioning](https://semver.org).

## Unreleased

### Added

- `snap`: a scroller with `scroll-snap-type` glides to the snap points its CSS sets. The wheel moves as far as it would without
snapping and the glide rests on the nearest snap point; with `mandatory`, one notch or one trackpad swipe moves one point.
`scrollTo` and same-page links rest on the snap point nearest their target. `scroll-padding`, `scroll-margin`,
`scroll-snap-stop: always` and areas longer than the visible area are honoured, and every rest is where the browser itself would
let the scroller rest, so its own snapping never moves it afterwards. `configure` changes it while Svifa runs.
- Components for every major framework, beside the React one: Vue and Nuxt (`@arcwrightstudio/svifa/vue`: `SmoothScroll`,
`useSvifa`), Svelte and SvelteKit (`@arcwrightstudio/svifa/svelte`: `smoothScroll`, `useSvifa`), Solid
(`@arcwrightstudio/svifa/solid`: `SmoothScroll`, `useSvifa`), Angular (`@arcwrightstudio/svifa/angular`: `provideSmoothScroll`,
`injectSvifa`, `createSvifa`) and Astro (`@arcwrightstudio/svifa/astro`: `SmoothScroll`, view transitions included). Each renders
on a server and is tested on the oldest version of its framework it supports: Vue 3.3, Svelte 4, Solid 1.6 and Angular 19.
- Examples for Vue, Nuxt, SvelteKit, Solid, Angular and Astro (Nuxt, SvelteKit and Angular rendered on the server). Every example
is built with its framework's own tool from the package as it would be published, served as its framework serves it, and driven
in a browser, on each change.

### Fixed

- Installing Svifa in a Yarn Plug'n'Play project no longer turns on Yarn's experimental ES module loader, nor its warning that the
loader is experimental. The package is `"type": "commonjs"`; `dist/package.json` marks the built ES modules for Node, every file
keeps its name, and each framework entry imports the core from the file beside it.
- React: a prop taken off `SmoothScroll` (left out of its props) now goes back to its default; it kept the value it had.
- The README gave the size as under 8 kB minified and gzipped, which 1.1.0 already passed (8.6 kB). It now gives the size as
measured, and a test keeps it true.

## 1.1.0

### Added
Expand Down
32 changes: 20 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,27 @@ Development needs Node 22.18, 24.11, 26 or later.
npm ci # the tools (and npx playwright install, once, for the browsers)
npm run lint # oxlint for defects, and Prettier for the code style (npm run format applies it)
npm run typecheck # the library, then the tests and the config files
npm run build # dist/: the ES module, the CommonJS file, the script builds, the React entry, the types, the SBOM
npm run build # dist/: the ES module, the CommonJS file, the script builds, the framework entries, the types, the SBOM
npm test # the suite in Chromium, Firefox and WebKit, about a minute
npm run coverage # how much of src/ the tests run, file by file; fails below the floors in scripts/coverage.mjs
npm run check:package # the exports and types as every module resolution reads them, and the CDN tags' pinned hashes
npm run check:installs # installs the packed package with npm, pnpm, Yarn 1 and 4 (Plug'n'Play), Bun and Deno; fails on warnings
npm run check:examples # builds every example from the packed package with its framework's own tool and drives it in a browser
npm run check:oldest # each framework entry's tests on the oldest version of its framework that package.json supports
```

`SVIFA_REACT_DIR=<a node_modules folder with another React> npx playwright test tests/react.spec.ts` runs the React entry's tests
against another React version (React 18 is supported as well as 19).
`SVIFA_<NAME>_DIR=<a node_modules folder> npx playwright test tests/<name>.spec.ts` runs a framework entry's tests against another
version of its framework (`SVIFA_REACT_DIR`, `SVIFA_VUE_DIR`, `SVIFA_SVELTE_DIR`, `SVIFA_SOLID_DIR` or `SVIFA_ANGULAR_DIR`);
`check:oldest` does this for the oldest version of each.

### When the checks run

On every pull request and every push to `main`, CI runs the lint, the license lines, the types, the build, the suite in all three
engines on the newest Node release and the newest long-term support release, the coverage floors, the package checks and the
reproducible build check; a pull request also gets dependency review and the sign-off check, and CodeQL analyses both. A pull
request can merge only once the required checks pass. Before a version is staged on npm, the Release workflow checks the
dependencies with `npm audit` and runs the lint, the types, the build, the suite and the package checks again.
engines on the newest Node release and the newest long-term support release, the coverage floors, the package checks, every example
built and driven in a browser, each framework entry on the oldest version of its framework, and the reproducible build check; a pull
request also gets dependency review and the sign-off check, and CodeQL analyses both. A pull request can merge only once the
required checks pass. Before a version is staged on npm, the Release workflow checks the dependencies with `npm audit` and runs the
lint, the types, the build, the suite and the package checks again.

### How it works

Expand All @@ -50,9 +54,13 @@ drawn on the chosen elements. Anything else that moves the scroller (a key, the
- `src/subpixel.ts`: draws the part of a pixel the browser cannot scroll by as a `translate` on the chosen elements, holding sticky
elements still while they are held.
- `src/target.ts`: what a `scrollTo` target names and the position it stands for (scroll-margin, scroll-padding, nested boxes).
- `src/snap.ts`: snapping (`snap`): the places a scroller may rest, read from its CSS, and which of them a movement rests on. The
browser has the last word: `src/svifa.ts` asks it where it would rest the scroller before each glide.
- `src/wheel.ts`: a wheel event in pixels, and whether an element inside the scroller takes it itself.
- `src/types.ts`: the public types; their JSDoc is the reference editors show. `src/react.ts`: the React entry (`SmoothScroll`,
`useSvifa`), which imports the core by the package's own name (`tsconfig.json` maps it to `src/index.ts`).
- `src/types.ts`: the public types; their JSDoc is the reference editors show.
- `src/react.ts`, `src/vue.ts`, `src/svelte.ts`, `src/solid.ts`, `src/angular.ts` and `astro/SmoothScroll.astro`: the framework
entries, each importing the core by the package's own name (`tsconfig.json` maps it to `src/index.ts`), so a project holds one
Svifa class whichever it uses. `src/adapter.ts` is what they share: which options retune a Svifa and which make it again.
- `tests/`: one spec per area. `tests/setup.ts` loads the script build and steps Svifa frame by frame on a clock of the test's own
(`autoRaf: false`), so every run sees the same frames at any chosen refresh rate. A test for something only some engines have is
tagged (`@chromium-only`, `@not-webkit`) or listed in `playwright.config.ts`, so it runs only there and nothing is reported
Expand All @@ -69,9 +77,9 @@ defects with its correctness and suspicious rules, as `.oxlintrc.json` sets them
drive, and call a constructor for what it does). `npm run lint` checks both, and CI runs it on every change. An exception to a
rule is rare, and is written where it applies, with the reason beside it.

Every source file opens with two lines: the license as an [SPDX](https://spdx.dev/learn/handling-license-info/) identifier, and
the copyright that `LICENSE` gives. CSS and HTML files write them in their own comments. A new file starts with them too, and CI
checks every file for them.
Every source file opens with two lines: the license as an [SPDX](https://spdx.dev/learn/handling-license-info/) identifier, and the
copyright that `LICENSE` gives. CSS, HTML, Vue, Svelte and Astro files write them in their own comments. A new file starts with them
too, and CI checks every file for them.

```ts
// SPDX-License-Identifier: MIT
Expand Down
Loading
Loading