* feat(date-picker): add Jalali (Persian) calendar support
- Add pure Jalali calendar math algorithms (no npm packages)
- Add JalaliDate class with valueOf() for reliable calendar comparison
- Add BrnJalaliDateAdapter implementing BrnDateAdapter<JalaliDate>
- Add comprehensive unit tests for all adapter methods
- Add Persian (Farsi) calendar i18n with RTL layout
- Add Jalali date picker example to docs page
- Add Storybook story with Persian locale
- Extend TranslateService to support 'fa' language
* feat(date-picker): add Jalali (Persian) calendar support
Add pure Jalali calendar math, JalaliDate class, and BrnJalaliDateAdapter with tests.
Move example from component docs to blocks/calendar page per PR review.
* refactor: clean up jalali date-time code
- Deduplicate JALALI_MONTHS array in calendar i18n files
- snake_case to camelCase in PersianDate (gregorianDaysInMonth, jalaliDaysInMonth)
- Expand cryptic param names (j_y to jalaliYear, g_y to gregorianYear, etc.)
- Remove parseInt() no-ops on typed number params
- Remove getMonthName (i18n concern, not calendrical)
- Destructure tuples with descriptive names across adapter/date/persian-date
- Fix month+day add drift: clamp day after month step before day arithmetic
* style: fix long line formatting to pass nx format check
* fix(date-picker): resolve merge conflicts from upstream and stashed changes
Merge descriptive variable names (_constrain, parseInt, getMonthName,
full descriptive names) with upstream descriptive names. All tests pass.
* refactor: remove parseInt unwrapping and getMonthName, add PersianDate tests
A touch drag interrupted by the system fires touchcancel instead of touchend, so the drag teardown
(listeners + cursor) never ran until the next drag or destroy. Listen for touchcancel alongside
touchend and remove it in cleanup. Adds a regression test.
Drop the deprecated `injectBrnDialogCtx` in favour of `injectBrnDialogContext`, which the helm
dialog, docs app and storybook already use exclusively. Removing it before 1.0 avoids carrying the
alias on the stable line. The `BrnDialogContext` type it returned stays - it is still used inside
the service.
_cleanup removed only the pointermove listener; the pointerup/pointercancel listeners (registered
with { once: true }) were left attached to document, so a destroy-during-drag - or a normal end
where pointercancel never fired - kept this directive pinned to document until the next global
pointer release. The 500ms _resetDrawer timeout was also fire-and-forget, leaving a stale style
write on detached elements when destroyed within that window.
Remove both listeners in _cleanup, and track the reset timeout so it can be cancelled on destroy.
The timeout is intentionally only cleared on destroy, not in _cleanup, so the normal snap-back
path (which schedules the timeout immediately before _cleanup runs) still clears the transition.
startResize attaches mousemove/touchmove/mouseup/touchend listeners to document, removed only
in the drag-end handler. If the group is destroyed mid-drag (navigation, conditional removal),
those listeners stay attached to document - which is never garbage-collected - pinning the
directive and firing against a torn-down view, and the body cursor is left stuck on the resize
cursor.
Hoist the cleanup into a single teardown stored on the instance: it detaches the four document
listeners, cancels any queued animation frame and restores the cursor. It runs on normal
drag-end and from a DestroyRef.onDestroy hook, and also clears a stale in-flight drag before a
new one starts.
BrnAvatarImage/BrnAvatarFallback and their helm wrappers all used the same
unprefixed exportAs ('avatarImage'/'avatarFallback'). Since the helm directive
host-applies the brain one on the same <img>, a template ref was ambiguous, and
the names broke the brn*/hlm* convention every other exportAs follows. Rename to
brnAvatarImage/brnAvatarFallback and hlmAvatarImage/hlmAvatarFallback before the
exportAs surface is locked at 1.0. Regenerated the CLI avatar templates to match.
- Remove unused HlmDrawerHandle directive
- Add static touch-action: none to .spartan-drawer-content in all
style files so iOS Safari does not hijack the gesture before the
pointerdown handler fires
- Set _openTime via effect on dialog state open instead of on first
pointerdown, so the 500ms cooldown starts when the drawer opens
rather than eating the user's first drag
- Replace preventDefault() on pointerdown with conditional touch-action:
none to avoid suppressing click events per Pointer Events spec
- Defer setPointerCapture to only when drag starts, not on every
pointerdown — capture redirects synthetic clicks to the captured
element instead of the button
- Add releasePointerCapture and touch-action restoration in cleanup
so capture is released before the browser fires click
- Fix _animateAndClose target formula: use ±dimension instead of
dimension + getTranslate() which produced wrong position for
left/top drawers (negative transforms), causing drawer to stay
half-closed
- Add animation: none inline style before close() to prevent
Tailwind data-closed:slide-out-* keyframe animation from
overriding inline transform (was visually reopening drawer)
- Add setPointerCapture for reliable pointercancel delivery
- Add DestroyRef.onDestroy cleanup to prevent pointermove listener
leak if directive is destroyed mid-gesture
- Remove unused _scrollEl field
Detect scrollable ancestors on pointerdown and skip drawer drag when
the scrollable content has remaining scroll in the drag direction.
Only prevent default on pointerdown when no scrollable element is
found, so browser scrolling is not blocked. Move preventDefault in
pointermove after the scroll check.
Improve drag smoothness and reliability by implementing position
tracking in BrnDrawerHandle. Simplify the component structure by moving
the handle logic to a host directive on HlmDrawerContent and removing
manual pointer-events management.
The mcp vite config lacked `root: __dirname`, so the @nx/vitest executor (which
runs from the workspace root) resolved `src/**/*.spec.ts` against the workspace
root, found nothing, and `passWithNoTests` passed it silently - mcp's 19 tests
never ran.
Pin mcp's root, and drop `passWithNoTests` from every project that has specs
(mcp, cli, tools, brain, helm) so a future test-discovery break fails loudly
instead of going green. registry keeps the flag - it genuinely has no specs.
Replace the cross-entry-point relative import (../../../src/testing/axe) in the
checkbox, collapsible and switch specs with a @spartan-ng/brain/testing path
alias, mirroring the @angular/core/testing convention. The helper stays test
only: no ng-package.json, excluded from the lib build and dependency checks, so
it is never published.
Replace Jest with Vitest across the workspace. The brain and helm Angular suites
now run in a real browser (Playwright/Chromium via Vitest browser mode) so they
exercise real layout, focus, pointer events and computed styles that jsdom could
not. The cli, tools, mcp, registry and cli-smoke suites run on Vitest's node
environment.
- Add per-project vite configs and switch test targets to @nx/vitest:test
- Convert jest APIs to vi.* and jest module mocks to vi.mock with importOriginal
- Replace jest-axe with a browser-native axe-core helper and toHaveNoViolations
- Regenerate snapshots in vitest format (content unchanged, format-only)
- Remove all jest packages, configs, the preset and the @nx/jest target default
- Install Playwright Chromium in the CI unit job for the browser suites
Also fixes a brain avatar test that asserted inside a fire-and-forget setTimeout,
which passed vacuously under jsdom and only surfaced once a real browser loaded
the image.
spartan/ui is now Tailwind CSS v4 only. The v3 compatibility layer added
complexity across the brain preset, the CLI generators and the docs, all of
which is removed here.
Brain:
- Delete the v3 JavaScript preset (hlm-tailwind-preset.js) and its package
export; the v4 CSS preset (hlm-tailwind-preset.css) is now the only preset.
- Bump the tailwindcss peer dependency to >=4.0.0 and declare tw-animate-css
as a peer, since it is imported by the CSS preset.
CLI:
- Remove Tailwind version detection and the v3 confirmation prompt; the theme
generator always emits the v4 stylesheet (preset import plus OKLCH theme).
- Drop the legacy HSL theme table, the v3-only CDK overlay import branch, the
vestigial addCdkStyles option and the unused registryItemTailwindSchema.
Docs and tooling:
- Remove the Tailwind v3 installation and theming sections and snippets.
- Drop autoprefixer; Tailwind v4 handles vendor prefixing via @tailwindcss/postcss.
BREAKING CHANGE: Tailwind CSS v3 is no longer supported. The
@spartan-ng/brain/hlm-tailwind-preset (JS) package export has been removed; use
the @spartan-ng/brain/hlm-tailwind-preset.css preset with Tailwind CSS v4. The
tailwindcss peer dependency now requires >=4.0.0.
Expose a size input ('default' | 'sm') on the brain and helm switch, bound to the
button's data-size attribute, so the size-keyed registry style rules resolve and
a smaller switch can be rendered. Replaces the previously hardcoded
data-size="default". Add brn-switch specs covering the data-size reflection and
the thumb data-state, and update the ui-docs snapshot for the new input.
Emit the spartan-switch and spartan-switch-thumb markers so the per-style track
size, border width, corner radius and thumb size/offset defined in the registry
styles apply at runtime across vega, nova, maia, lyra, mira and luma, matching
shadcn. Add a data-size attribute to the brn-switch button and a data-state
attribute to the thumb so the registry's size- and state-keyed rules resolve, and
make the checked thumb translate direction-aware (ltr/rtl) so it animates
correctly under RTL. Enable the docs theme switcher and show all styles.
* feat(repo)!: upgrade nx to 22 and angular to 21
Upgrade the workspace to Nx 22.7.5 and Angular 21.2.16, and widen the
published peer dependency ranges to support Angular 21 and 22 while
dropping Angular 20.
- nx and @nx/* to 22.7.5; @angular/* to 21.2.16 (framework) and 21.2.14
(cli, build, devkit, cdk, ssr)
- brain, helm and trpc @angular/* peers to ">=21.0.0 <23.0.0"; the helm
embla-carousel-angular peer uses the same range
- @spartan-ng/cli minimums raised to >=21 and @schematics/angular to
21.2.14; the private tools lib aligned to nx 22 / angular 21
- analog 2.6.0, ng-packagr 21, angular-eslint 21, storybook 10, jest 30,
@testing-library/angular 19 and related tooling
- replace the calendar select host-listener $event with a typed output
subscription; constrain the ai-chat-link directive to anchors
- drive the menubar e2e arrow keys via native keydown and disable
Chromium throttling; hold cypress at 14 (15 breaks realPress specs)
BREAKING CHANGE: Angular 20 is no longer supported. spartan/ui now
supports Angular 21 and 22.
* ci: lower unit test parallelism to fit the runner memory
The docs app's vitest + Analog suite peaks at a few GB; running it
alongside two other test projects under --parallel=3 exhausted the
GitHub runner memory and the worker was OOM-killed with no output.
Run at most two test projects concurrently.
* fix(trpc): drop invalid environment providers from the test env module
The Nx 22 migration wrapped provideZoneChangeDetection() in an @NgModule,
but EnvironmentProviders are not valid in @NgModule providers and Angular
21 throws NG0207 at runtime. Restore the plain initTestEnvironment setup
(matching the docs app); the explicit zone provider is unnecessary.
* ci: run the docs app test suite in isolation
Bundling the docs app's vitest + Analog suite into the parallel test run
made it fail in CI while it passes on its own, and the interleaved output
hid the cause. Run the other projects in parallel, then the docs app as a
separate step.
* fix(nx): point test cache inputs at the real jest preset file
The test targets' cache inputs referenced jest.preset.js, but the
workspace preset is jest.preset.cjs, so the input never matched and a
preset change would not invalidate the cache. Fix both the
@nx/vitest:test target (added by the Nx 22 migration) and the existing
@nx/jest:jest target.
* ci: log exit code and retry the docs app test suite once
The docs app's vitest + Analog suite intermittently crashes in CI right
after loading Angular's testing modules, with no error output. Log the
exit code to classify the crash (128+signal indicates a native crash)
and retry once so a flaky crash does not fail the run while the root
cause is investigated.
* ci: clarify docs app test retry after pinpointing the flaky crash
The crash is in app.spec.ts: booting the full app shell (Header/Footer
and their deep import graph) intermittently crashes the vitest worker
during the Analog transform in CI (exit 1, no output, not reproducible
locally). It happens before any test runs, so a vitest per-test retry
cannot help; re-running the suite once does.
* chore(repo): hold analog at 2.1.3 and drop unit-test ci workarounds
Analog 2.6.0 intermittently crashed the docs app's vitest worker in CI
(a flaky transform of the app shell), so keep analog at the nx-migration's
tested 2.1.3, revert the marked bumps that 2.6.0 required, and restore the
ai-chat-link directive selector. Also restore the original unit test ci
step, dropping the parallel, isolation and retry workarounds added while
chasing that crash.
* fix: allow undefined in value model signals for autocomplete, combobox, and select
Closes: #1387
* fix(select): guard undefined in select/select-multiple key-manager effect values
* feat: explicit check for undefined value
---------
Co-authored-by: Marc Stammerjohann <8985933+marcjulian@users.noreply.github.com>