Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
76dd75e
feat(core): add Zod and Yup schema validation helpers and extend defa…
vannt-dev Aug 4, 2026
e781f71
feat: add useDynamicForm, schema adapters, extended renderers, wizard…
vannt-dev Aug 5, 2026
d580d5c
docs: update READMEs and example app with enterprise features
vannt-dev Aug 5, 2026
fc61279
fix(core): parse schemas synchronously and make adapter target explicit
vannt-dev Aug 5, 2026
c050564
feat: give useDynamicForm the same surface in every framework
vannt-dev Aug 5, 2026
3fe46fb
test: cover DevTools, extended renderers and group helpers
vannt-dev Aug 5, 2026
9f5cda3
docs: document adapter targets and add the missing changeset
vannt-dev Aug 5, 2026
4250f1f
fix(core): add "switch" to FieldTypeMap
vannt-dev Aug 5, 2026
5b9d3d5
fix: stop shipping angular sources and drop a non-existent example prop
vannt-dev Aug 5, 2026
f0ec57f
ci: gate releases on the same checks as PRs, and cover what CI could …
vannt-dev Aug 5, 2026
bb0e424
fix(ci): drop the postcss override that desynchronised package-lock
vannt-dev Aug 5, 2026
8d5d1be
fix(ci): declare Node 22 in .nvmrc, the version CI actually runs
vannt-dev Aug 5, 2026
85d0851
fix(ci): make the examples job actually install what the demos need
vannt-dev Aug 5, 2026
28cff59
ci: skip the changeset check on changesets' own release PR
vannt-dev Aug 5, 2026
3976ab3
feat(ci): release by picking a bump from the Actions UI
vannt-dev Aug 5, 2026
8b6475c
feat(core): let the wizard engine actually change step
vannt-dev Aug 5, 2026
abf7fab
feat(angular): show the error count in DevTools, matching react and vue
vannt-dev Aug 5, 2026
e97baac
feat: report field blur from MultiFieldInput in all three frameworks
vannt-dev Aug 5, 2026
95f866e
docs: document the v1.4 APIs and add a runnable wizard demo
vannt-dev Aug 5, 2026
85e5243
ci: publish the example apps to GitHub Pages
vannt-dev Aug 5, 2026
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
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "master",
"baseBranch": "develop",
"updateInternalDependencies": "patch",
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
Expand Down
24 changes: 24 additions & 0 deletions .changeset/lucky-pugs-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
'@dynamic-field-kit/core': minor
'@dynamic-field-kit/react': minor
'@dynamic-field-kit/vue': minor
'@dynamic-field-kit/angular': minor
---

Add form state hooks, schema adapters, wizard engine, DevTools and extended renderers.

**Core**

- `zodValidator`, `yupValidator`, `valibotValidator` / `standardSchemaValidator`. Adapters parse **synchronously** so their result is usable by the synchronous `validateFields`; schemas with async refinements or async `.test()` rules return a Promise and must be validated through `validateFieldsAsync`.
- Adapters take an explicit `{ target: 'form' | 'field' }` option. `'form'` (the default) parses the form data object; `'field'` parses a single scalar value. The field-name shorthand — `zodValidator(schema, 'email')` — is unchanged.
- Wizard engine: `createWizardState`, `validateStep`, `canGoNext`, `canGoPrev`, plus the navigation the engine needs to be usable - `goNext`, `goPrev`, `goToStep`, `markStepCompleted`, `isStepCompleted`. `goNext` records the step it leaves, so `completedSteps` is actually maintained.
- Group array helpers: `moveGroupItem`, `swapGroupItems`, `insertGroupItem`, `focusFirstInvalidField`.
- `switch` is a first-class field type: it had a shipped renderer in react and vue but no `FieldTypeMap` entry, so `type: 'switch'` did not typecheck.

**React / Vue / Angular**

- `useDynamicForm` (React, Vue) and `createDynamicFormStore` (Angular Signals) now expose the same surface, including `isSubmitting` and `isSubmitted`.
- `handleSubmit(onValid, onInvalid)` returns a submit handler in every framework and calls `preventDefault` on the event it receives.
- Default HTML5 renderers for `radio`, `range`, `file`, `date`, `time`, `datetime-local` and `switch`.
- `DynamicFormDevTools` overlay for inspecting form data, errors, metadata and field descriptions, with an error-count badge in all three frameworks.
- `MultiFieldInput` reports blur through `onBlurField` (an `@Output` in Angular), so a form store's `handleBlur` / `touched` / `validateOnBlur` can be wired to it. Vue and Angular previously had no blur plumbing at all.
9 changes: 9 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,12 @@ dist/
coverage/**
.nyc_output/**
*.log

# The `lint` script only covers packages/*/src, so CI never lints the demo
# apps. Without this, the pre-commit hook lints them anyway - against a config
# written for library source - and fails on things like a `require` in
# next.config.js or an import it cannot resolve from an example's own
# node_modules. The examples are still prettier-checked, typechecked by their
# own builds, and built in CI.
example/**
smoke/**
10 changes: 10 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Branch protection on master enables require_code_owner_reviews, which does
# nothing without this file. Every path needs an owner for that rule to bite.
* @vannt-dev

# Anything that decides what ships, or what a release does.
/.github/ @vannt-dev
/.changeset/ @vannt-dev
/scripts/ @vannt-dev
package.json @vannt-dev
package-lock.json @vannt-dev
25 changes: 25 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## What

<!-- What changed, in one or two sentences. -->

## Why

<!-- The problem this solves. Link the issue if there is one. -->

## How to test

<!-- Commands or steps a reviewer can actually run. -->

---

- [ ] Added a changeset (`npx changeset`) if any package under `packages/` changed
- [ ] Tests cover the change — a bug fix has a test that failed before it
- [ ] Public API changes are reflected in the README / package README
- [ ] Behaviour is consistent across react, vue and angular, or the difference is explained above

<!--
CI runs: lint, format, prod audit, build x4, typecheck, type tests,
per-package tests with coverage floors, the three example apps, the built
package smoke test, and the cross-framework verify scripts.
Run locally with: npm run lint && npm run typecheck && npm test
-->
137 changes: 18 additions & 119 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,136 +10,35 @@ concurrency:
cancel-in-progress: true

jobs:
lint-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Lint & Format
run: npm run lint && npm run format-check

- name: Build packages
run: |
npm run build --workspace=@dynamic-field-kit/core
npm run build --workspace=@dynamic-field-kit/react
npm run build --workspace=@dynamic-field-kit/vue
npm run build --workspace=@dynamic-field-kit/angular

# Share the built dist with the verify job so it doesn't rebuild all four
# packages (ng-packagr's Angular build especially). Common ancestor of the
# four paths is `packages`, so the artifact stores `<pkg>/dist/...`.
- name: Upload built dist
uses: actions/upload-artifact@v4
with:
name: package-dist
path: |
packages/core/dist
packages/react/dist
packages/vue/dist
packages/angular/dist
retention-days: 1
if-no-files-found: error

- name: Typecheck
run: npm run typecheck

- name: Type tests
run: npm run test:types --workspace=@dynamic-field-kit/core

- name: Show bundle sizes
run: node scripts/show-sizes.js
gates:
uses: ./.github/workflows/quality-gates.yml

test:
name: Test ${{ matrix.package }}
changeset:
name: Changeset present
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- package: core
test-cmd: npm run test --workspace=@dynamic-field-kit/core -- --coverage
coverage-file: packages/core/coverage/lcov.info
coverage-name: core
- package: react
build-cmd: npm run build --workspace=@dynamic-field-kit/core
test-cmd: npm run test --workspace=@dynamic-field-kit/react -- --coverage
coverage-file: packages/react/coverage/lcov.info
coverage-name: react
- package: vue
build-cmd: npm run build --workspace=@dynamic-field-kit/core
test-cmd: npm run test --workspace=@dynamic-field-kit/vue -- --coverage
coverage-file: packages/vue/coverage/lcov.info
coverage-name: vue
- package: angular
build-cmd: npm run build --workspace=@dynamic-field-kit/core
test-cmd: npm run test --workspace=@dynamic-field-kit/angular -- --coverage
coverage-file: packages/angular/coverage/lcov.info
coverage-name: angular
# Skipped on the release PR changesets itself opens: that PR consumes the
# changesets and bumps versions, so it changes packages while legitimately
# having none left, and would always fail this check.
if: >-
github.event_name == 'pull_request' &&
!startsWith(github.head_ref, 'changeset-release/')
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Build core
if: matrix.build-cmd != ''
run: ${{ matrix.build-cmd }}

- name: Run tests
run: ${{ matrix.test-cmd }}

- name: Upload coverage
if: matrix.coverage-file != '' && success()
uses: codecov/codecov-action@v4
with:
files: ${{ matrix.coverage-file }}
flags: unittests
name: ${{ matrix.coverage-name }}
fail_ci_if_error: false

verify:
runs-on: ubuntu-latest
needs: [lint-and-build, test]
steps:
- uses: actions/checkout@v4
# changeset status diffs against the base branch, so it needs history.
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install dependencies
run: npm ci

# Reuse the dist built by lint-and-build instead of rebuilding all four
# packages. Extract into `packages/` so paths become packages/<pkg>/dist.
- name: Download built dist
uses: actions/download-artifact@v4
with:
name: package-dist
path: packages

- name: Smoke test built packages
run: npm run test --workspace=@dynamic-field-kit/smoke

- name: Run verification scripts
run: |
node scripts/verify-framework-deps.js
node scripts/check-cross-framework-imports.js
node scripts/integration-cross-registry.js
# Fails when a PR changes a publishable package without adding a
# changeset, which would otherwise merge and silently never be released.
# Add one with `npx changeset`.
- name: Check for a changeset
run: npx changeset status --since=origin/${{ github.base_ref }}
97 changes: 97 additions & 0 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Deploy demos to Pages

# Publishes the three example apps to GitHub Pages so there is a link to hand
# people. They consume the packages through `file:` paths, so whatever is on
# this branch is exactly what the demos are built against.
on:
push:
branches: [develop]
# Only rebuild when something the demos actually render could have changed.
paths:
- 'packages/**'
- 'example/**'
- '.github/workflows/deploy-pages.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# One deployment at a time, and never cancel one midway.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
env:
# Project pages live under /<repo>/, so every app needs its assets
# prefixed or they 404.
SITE_BASE: /${{ github.event.repository.name }}
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'

- name: Install workspace dependencies
run: npm ci

- name: Build packages
run: npm run build

- name: Build react demo
working-directory: example/react-app
env:
PAGES_BASE_PATH: ${{ env.SITE_BASE }}/react
run: |
npm install --no-audit --no-fund
npm run build

- name: Build vue demo
working-directory: example/vue-app
env:
PAGES_BASE_PATH: ${{ env.SITE_BASE }}/vue/
run: |
npm install --no-audit --no-fund
npm run build

- name: Build angular demo
working-directory: example/angular-app
run: |
npm install --no-audit --no-fund
npx ng build --base-href ${{ env.SITE_BASE }}/angular/

- name: Assemble the site
run: |
mkdir -p _site
cp example/pages-index.html _site/index.html
cp -r example/react-app/out _site/react
cp -r example/vue-app/dist _site/vue
cp -r example/angular-app/dist/example-angular-app _site/angular
# Pages runs the output through Jekyll otherwise, which drops
# directories starting with an underscore - _next among them.
touch _site/.nojekyll
echo "--- deployed tree ---"
find _site -maxdepth 2 -name index.html

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: _site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Loading
Loading