Skip to content

feat(date-picker): add date-picker - #55

Merged
MGREMY merged 3 commits into
mainfrom
feat/date-picker
Aug 5, 2026
Merged

feat(date-picker): add date-picker#55
MGREMY merged 3 commits into
mainfrom
feat/date-picker

Conversation

@MGREMY

@MGREMY MGREMY commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added date picker and date-range picker components with month navigation, date selection, form integration, and multiple color themes.
    • Added calendar cells, grids, labels, headers, and previous/next month controls.
    • Added reusable date picker styling and theme color utilities.
  • Documentation
    • Added date picker documentation, usage examples, previews, and package guidance.
  • Bug Fixes
    • Improved documentation quick links by excluding headings from embedded previews.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Added Angular date-picker and date-range-picker primitives with value-accessor support, themed styles, package exports, dependency updates, documentation, previews, and a range-picker example.

Changes

Date Picker Feature

Layer / File(s) Summary
Package contracts and exports
packages/ng-primitives/date-picker/*, packages/ng-primitives/date-picker/src/*, tsconfig.base.json
Added the secondary package configuration, public range type, barrel exports, and TypeScript path mapping.
Picker directives and state wiring
packages/ng-primitives/date-picker/src/lib/*
Added picker, range-picker, calendar, cell, button, label, header, row, and month-navigation directives with state and value-accessor wiring.
Date-picker theme styles
packages/ng-primitives/_theme/components/date-picker/*, packages/ng-primitives/_theme/mgnp.css
Added calendar layout, controls, date states, range states, color variants, and theme integration.
Documentation and preview examples
apps/documentation/src/content/documentation/ng-primitives/date-picker.md, apps/documentation/src/examples/date-picker/range-picker.ts, apps/documentation/src/previews/date-picker.ts, apps/documentation/src/app/components/app-quick-links.ts
Added date-picker documentation, a range-picker example, color previews, and filtering for headings inside app-preview elements.
Dependency version alignment
package.json, packages/ng-primitives/package.json, pnpm-workspace.yaml
Updated ng-primitives development, peer, and workspace release constraints to version 0.128.8.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DocumentationPreview
  participant MgnpDatePicker
  participant MgnpDatePickerDateButton
  participant NgpDatePicker
  DocumentationPreview->>MgnpDatePicker: Render date picker
  MgnpDatePicker->>NgpDatePicker: Provide picker state
  DocumentationPreview->>MgnpDatePickerDateButton: Render date button
  MgnpDatePickerDateButton->>NgpDatePicker: Delegate date selection
Loading

Possibly related PRs

  • MGREMY/nx_source#21: Uses the same host-directive and state-provider pattern as the date-picker directives.
  • MGREMY/nx_source#35: Also changes the documentation heading-anchor area.
  • MGREMY/nx_source#54: Adds a comparable Angular primitive with documentation, previews, theme styles, and a secondary package entry point.

Poem

A rabbit taps dates in a row,
Arrows guide the months as they go.
Colors bloom, ranges align,
Buttons hop through every design.
The picker shines, neat and fine.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding date-picker functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/date-picker

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

package.json

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Error while loading rule '@nx/nx-plugin-checks': Cannot read properties of undefined (reading 'nodes')
Occurred while linting /package.json
at create (/node_modules/.pnpm/@nx+eslint-plugin@23.1.0_@babel+traverse@7.29.7_supports-color@7.2.0__@swc-node+registe_cee3c3f96c2c818e871d9a3c884f392b/node_modules/@nx/eslint-plugin/dist/src/rules/nx-plugin-checks.js:95:71)
at Object.create (/node_modules/.pnpm/@typescript-eslint+utils@8.56.1_eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0__supports-color@7.2.0_typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js:39:20)
at createRuleListeners (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1019:15)
at /node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1151:7
at Array.forEach ()
at runRules (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1085:31)
at #flatVerifyWithoutProcessors (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:2115:4)
at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:2203:43)
at Linter._verifyWithFlatConfigArray (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:2306:15)
at Linter.verify (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1677:10)

packages/ng-primitives/package.json

Oops! Something went wrong! :(

ESLint: 9.39.4

TypeError: Error while loading rule '@nx/nx-plugin-checks': Cannot read properties of undefined (reading 'nodes')
Occurred while linting /packages/ng-primitives/package.json
at create (/node_modules/.pnpm/@nx+eslint-plugin@23.1.0_@babel+traverse@7.29.7_supports-color@7.2.0__@swc-node+registe_cee3c3f96c2c818e871d9a3c884f392b/node_modules/@nx/eslint-plugin/dist/src/rules/nx-plugin-checks.js:95:71)
at Object.create (/node_modules/.pnpm/@typescript-eslint+utils@8.56.1_eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0__supports-color@7.2.0_typescript@6.0.3/node_modules/@typescript-eslint/utils/dist/eslint-utils/RuleCreator.js:39:20)
at createRuleListeners (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1019:15)
at /node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1151:7
at Array.forEach ()
at runRules (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1085:31)
at #flatVerifyWithoutProcessors (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:2115:4)
at Linter._verifyWithFlatConfigArrayAndWithoutProcessors (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:2203:43)
at Linter._verifyWithFlatConfigArray (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:2306:15)
at Linter.verify (/node_modules/.pnpm/eslint@9.39.4_jiti@2.7.0_supports-color@7.2.0/node_modules/eslint/lib/linter/linter.js:1677:10)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ng-primitives/_theme/components/date-picker.css`:
- Around line 11-25: Update the mgnp-date-picker-date-button styles to set an
explicit non-inline display value, such as flex, so size-full covers the entire
date cell. Apply the same display correction to the date target and
range-background styling referenced by this date-picker component.

In
`@packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts`:
- Around line 17-52: Implement ControlValueAccessor on MgnpDateRangePicker to
match the existing provideValueAccessor registration: add writeValue,
registerOnChange, registerOnTouched, and setDisabledState, and connect them to
the injected state and range change outputs so form values, touch status, and
disabled state stay synchronized. If form support is not intended, remove
provideValueAccessor instead.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 27767583-99db-4e97-96ad-8140a78b6f5a

📥 Commits

Reviewing files that changed from the base of the PR and between 2883066 and 0854879.

📒 Files selected for processing (24)
  • apps/documentation/src/app/components/app-quick-links.ts
  • apps/documentation/src/app/directives/heading-anchor.ts
  • apps/documentation/src/content/documentation/ng-primitives/date-picker.md
  • apps/documentation/src/examples/date-picker/range-picker.ts
  • apps/documentation/src/previews/date-picker.ts
  • eslint.config.mjs
  • packages/ng-primitives/_theme/components/date-picker.css
  • packages/ng-primitives/_theme/components/date-picker/color.css
  • packages/ng-primitives/_theme/mgnp.css
  • packages/ng-primitives/date-picker/README.md
  • packages/ng-primitives/date-picker/ng-package.json
  • packages/ng-primitives/date-picker/src/index.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-cell-render/mgnp-date-picker-cell-render.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-cell/mgnp-date-picker-cell.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-date-button/mgnp-date-picker-date-button.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-grid/mgnp-date-picker-grid.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-header/mgnp-date-picker-header.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-label/mgnp-date-picker-label.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-next-month/mgnp-date-picker-next-month.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-previous-month/mgnp-date-picker-previous-month.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-row-render/mgnp-date-picker-row-render.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-picker/mgnp-date-picker.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts
  • tsconfig.base.json
💤 Files with no reviewable changes (2)
  • eslint.config.mjs
  • apps/documentation/src/app/directives/heading-anchor.ts

Comment thread packages/ng-primitives/_theme/components/date-picker.css
Comment on lines +17 to +52
providers: [provideDateRangePickerState(), provideValueAccessor(MgnpDateRangePicker)],
host: {
class: 'mgnp-date-range-picker mgnp-c-date-range-picker',
'data-mgnp-date-range-picker': '',
'[attr.data-mgnp-date-picker-color]': 'color()',
},
hostDirectives: [
{
directive: NgpDateRangePicker,
inputs: [
'ngpDateRangePickerMin:mgnpDateRangePickerMin',
'ngpDateRangePickerMax:mgnpDateRangePickerMax',
'ngpDateRangePickerDisabled:mgnpDateRangePickerDisabled',
'ngpDateRangePickerDateDisabled:mgnpDateRangePickerDateDisabled',
'ngpDateRangePickerFirstDayOfWeek:mgnpDateRangePickerFirstDayOfWeek',
'ngpDateRangePickerStartDate:mgnpDateRangePickerStartDate',
'ngpDateRangePickerDefaultStartDate:mgnpDateRangePickerDefaultStartDate',
'ngpDateRangePickerEndDate:mgnpDateRangePickerEndDate',
'ngpDateRangePickerDefaultEndDate:mgnpDateRangePickerDefaultEndDate',
'ngpDateRangePickerFocusedDate:mgnpDateRangePickerFocusedDate',
'ngpDateRangePickerDefaultFocusedDate:mgnpDateRangePickerDefaultFocusedDate',
],
outputs: [
'ngpDateRangePickerStartDateChange:mgnpDateRangePickerStartDateChange',
'ngpDateRangePickerEndDateChange:mgnpDateRangePickerEndDateChange',
'ngpDateRangePickerFocusedDateChange:mgnpDateRangePickerFocusedDateChange',
],
},
],
exportAs: 'mgnpDateRangePicker',
})
export class MgnpDateRangePicker<T = Date> {
readonly state = injectDateRangePickerState<T>();

readonly color = input<MgnpDateRangePickerColor>('ui');
}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Compare the registered range accessor with the implemented date accessor.
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-picker/mgnp-date-picker.ts --items all
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts --items all

rg -n -C 4 \
  'provideValueAccessor|implements ControlValueAccessor|writeValue|registerOnChange|registerOnTouched|setDisabledState|DateChange' \
  packages/ng-primitives/date-picker/src/lib

Repository: MGREMY/nx_source

Length of output: 10907


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== providers/utils files =="
fd -a 'providers|value.*accessor|utils' packages/ng-primitives | sed 's#^\./##' | head -50

echo "== locate provideValueAccessor =="
rg -n "function provideValueAccessor|export function provideValueAccessor|provideValueAccessor|NG_VALUE_ACCESSOR|ValueAccessor" packages/ng-primitives

echo "== date picker files with lines =="
sed -n '1,120p' packages/ng-primitives/date-picker/src/lib/mgnp-date-picker/mgnp-date-picker.ts
echo "---"
sed -n '1,90p' packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts

Repository: MGREMY/nx_source

Length of output: 9802


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package declarations =="
rg -n '"exports"|"ng-primitives/utils"|"date-picker"|provideValueAccessor|export const provideValueAccessor|export interface|export type (ChangeFn|TouchedFn)|NG_VALUE_ACCESSOR' package.json packages -g "package.json" -g "*.ts" | head -200

echo "== candidate source files =="
git ls-files packages | rg 'utils|value.*accessor|providers|date-range' | head -100

echo "== all provideValueAccessor definitions/usages across repo =="
rg -n -C 3 "provideValueAccessor|NG_VALUE_ACCESSOR" .

Repository: MGREMY/nx_source

Length of output: 19921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package json relevant:"
cat packages/ng-primitives/package.json

echo "== candidate source files exact:"
git ls-files packages/ng-primitives | rg '(^packages/ng-primitives/(utils|date-picker)|value|providers)'

echo "== direct source search for function definitions:"
rg -n -C 3 "provideValueAccessor|function provideValueAccessor|export .*provideValueAccessor" packages/ng-primitives -g "*.ts" || true

echo "== direct source search for Angular value accessor APIs:"
rg -n -C 2 "NG_VALUE_ACCESSOR|ValueAccessor|ControlValueAccessor|ChangeFn|TouchedFn" packages/ng-primitives -g "*.ts" || true

Repository: MGREMY/nx_source

Length of output: 50372


Implement the registered range value accessor.

provideValueAccessor(MgnpDateRangePicker) registers this directive for Angular form value binding, but the class does not implement ControlValueAccessor. Add writeValue, registerOnChange, registerOnTouched, and setDisabledState, then tie writeValue / change events to the range state, or remove the accessor if this directive should not support forms.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts`
around lines 17 - 52, Implement ControlValueAccessor on MgnpDateRangePicker to
match the existing provideValueAccessor registration: add writeValue,
registerOnChange, registerOnTouched, and setDisabledState, and connect them to
the injected state and range change outputs so form values, touch status, and
disabled state stay synchronized. If form support is not intended, remove
provideValueAccessor instead.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting for a this PR to be merged

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MGREMY, acknowledged. The finding depends on ng-primitives/ng-primitives#897.

I will keep it unresolved until that PR merges. Please update this thread after the upstream change is available.

You are interacting with an AI system.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you implement this ? Since this must be a value accessor for two controls technically. Wouldn't it be good to require a group of two controls (like startDate, endDate) and manage them inside the component, or could this be done in another way ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== upstream PR 897 metadata =='
gh pr view 897 --repo ng-primitives/ng-primitives \
  --json number,title,state,body,files,url

echo '== upstream PR 897 changed-file patches =='
gh pr diff 897 --repo ng-primitives/ng-primitives --patch

echo '== local picker outlines =='
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-picker/mgnp-date-picker.ts --items all
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts --items all

echo '== local picker and example form-related code =='
rg -n -C 5 \
  'class MgnpDatePicker|class MgnpDateRangePicker|ControlValueAccessor|writeValue|registerOnChange|registerOnTouched|setDisabledState|mgnpDateRangePicker|mgnpDatePicker' \
  packages/ng-primitives/date-picker apps/documentation/src/examples/date-picker

Length of output: 52055


@MGREMY, use one FormControl for one range value. Do not require a parent FormGroup.

A ControlValueAccessor connects one host element to one Angular form control. The date-range picker is one UI control, even though it contains two dates. Its form value should be an atomic range value, for example:

export interface MgnpDateRange<T> {
  start: T | undefined;
  end: T | undefined;
}

Then consumers use:

range = new FormControl<MgnpDateRange<Date>>({
  start: undefined,
  end: undefined,
});

This keeps the control reusable with both reactive forms and template-driven forms. It also lets validators operate on the range as a whole, such as start <= end.

After ng-primitives/ng-primitives#897 merges, I would implement the value accessor as follows:

  1. writeValue(value) calls state.setStart(value?.start, { emit: false }) and state.setEnd(value?.end, { emit: false }).
  2. Subscribe to both state().startDateChange and state().endDateChange.
  3. On either event, call onChange with the current complete value:
    {
      start: this.state().startDate(),
      end: this.state().endDate(),
    }
  4. setDisabledState(isDisabled) calls state().setDisabled(isDisabled).
  5. Mark the control touched on a user interaction or when focus leaves the picker. Do not mark it touched from writeValue.

The update methods must suppress emitted events during writeValue. Otherwise, a form model update can feed back into onChange.

A two-control FormGroup is still useful at the application level:

new FormGroup({
  startDate: new FormControl<Date | undefined>(),
  endDate: new FormControl<Date | undefined>(),
});

However, that should be an optional adapter or application-owned composition. The picker directive should not require or mutate a parent FormGroup. A directive cannot cleanly act as two independent ControlValueAccessor instances on the same host element.

Until #897 merges, remove provideValueAccessor(MgnpDateRangePicker) or defer the range-picker form integration. The current registration advertises Angular forms support but cannot implement writeValue correctly without the upstream setters.

You are interacting with an AI system.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interface over a Type ?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MGREMY, use a type alias here.

The range value is a small value object. It is not designed for declaration merging or consumer extension. A type also keeps the option open to model partial or complete ranges with unions later.

export type MgnpDateRange<T> = {
  start: T | null;
  end: T | null;
};

Use the date-picker library’s existing range type instead if it exports one. Do not introduce a duplicate public type when the upstream API already defines the range contract.

Use an interface only if you intend consumers to extend the range contract through inheritance or declaration merging. Neither is needed for a ControlValueAccessor.

You are interacting with an AI system.

@MGREMY

MGREMY commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Waiting for this PR

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts`:
- Around line 60-68: Rename the lifecycle method in the date-range picker from
contructor to constructor so Angular executes the subscription setup during
directive creation. Preserve both startDateChange and endDateChange
subscriptions and their existing onChangeFn callback behavior.
- Line 25: Replace the direct onTouchedFn call in the host focusout binding with
an onHandleFocusOut($event) method on mgnpDateRangePicker. In that method,
invoke onTouchedFn only when event.relatedTarget is not contained within the
host element, preserving focus transitions between internal range-date buttons
without marking the picker touched.
- Around line 70-72: Update MgnpDateRangePicker.writeValue to handle null or
undefined values by clearing both range endpoints instead of dereferencing the
value; retain the existing start/end assignments for non-null MgnpDateRange
values.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f7a9f39-60fa-4e23-b0a0-ef972e3ea250

📥 Commits

Reviewing files that changed from the base of the PR and between 0854879 and fe9b473.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • package.json
  • packages/ng-primitives/_theme/components/date-picker.css
  • packages/ng-primitives/_theme/components/date-picker/color.css
  • packages/ng-primitives/date-picker/src/index.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts
  • packages/ng-primitives/date-picker/src/lib/mgnp-date-range.ts
  • packages/ng-primitives/package.json
  • pnpm-workspace.yaml
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ng-primitives/_theme/components/date-picker.css
  • packages/ng-primitives/date-picker/src/index.ts

class: 'mgnp-date-range-picker mgnp-c-date-range-picker',
'data-mgnp-date-range-picker': '',
'[attr.data-mgnp-date-picker-color]': 'color()',
'(focusout)': 'onTouchedFn?.()',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 4 \
  '\(focusout\)|onTouchedFn|mgnpDateRangePicker|mgnpDatePickerDateButton' \
  packages/ng-primitives/date-picker/src/lib -g '*.ts' || true

Repository: MGREMY/nx_source

Length of output: 13192


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files:\n'
git ls-files 'packages/ng-primitives/date-picker/src/lib/mgnp-date-%picker.ts' 'packages/ng-primitives/date-picker/src/lib/*' | sed -n '1,200p'

printf '\nRange picker outline:\n'
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts --view expanded || true

printf '\nDate picker outline:\n'
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-picker/mgnp-date-picker.ts --view expanded || true

printf '\nDate button outline:\n'
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-date-button/mgnp-date-picker-date-button.ts --view expanded || true

printf '\nRelevant contents:\n'
cat -n packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts | sed -n '1,110p'
printf '\n--- date picker ---\n'
cat -n packages/ng-primitives/date-picker/src/lib/mgnp-date-picker/mgnp-date-picker.ts | sed -n '1,110p'
printf '\n--- date button ---\n'
cat -n packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-date-button/mgnp-date-picker-date-button.ts | sed -n '1,80p'

Repository: MGREMY/nx_source

Length of output: 11037


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Search for NgpDateRangePicker / NgpDatePicker symbols:\n'
rg -n 'class Ngp(DateRangePicker|DatePicker)|export .*Ngp(DateRangePicker|DatePicker)|NgpDateRangePicker|NgpDatePicker' packages -g '*.ts' || true

printf '\nFocusout/touched handlers in repo:\n'
rg -n -C 3 'focusout|registerOnTouched|onTouchedFn|nextElement|document\.activeElement|contains\(.*focus' packages -g '*.ts' || true

printf '\nBehavioral probe for focusout event target vs current target:\n'
cat <<'EOF' > /tmp/focusout_probe.html
<!DOCTYPE html>
<meta charset="utf-8">
<div id="host" tabindex="0" style="border:1px solid black;padding:10px">
  <button id="button1">1</button>
  <button id="button2">2</button>
</div>
<script>
  const log = [];
  document.getElementById('host').addEventListener('focusout', (e) => {
    log.push({
      currentTargetOutside: !e.currentTarget.contains(e.relatedTarget),
      currentTargetContainsRelatedTarget: e.currentTarget.contains(e.relatedTarget),
      relatedTargetId: e.relatedTarget?.id ?? null
    });
  });
  document.getElementById('button1').focus();
  document.getElementById('button2').focus();
  document.body.style.position = 'absolute';
  document.body.style.left = '9999px';
  document.body.focus();
  document.getElementById('host').removeEventListener('focusout', () => {});
  console.log(JSON.stringify(log));
</script>
EOF
# If a headless browser is unavailable, print the DOM-only representation of the same event semantics expected by Element.contains().
set +e
docker run --rm --network none alpine echo "docker unavailable" >/dev/null 2>&1 || true
printf '\nProbe file exists at /tmp/focusout_probe.html\n'

Repository: MGREMY/nx_source

Length of output: 21989


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Ngp* classes definitions:\n'
rg -n -C 6 '`@Directive`|class NgpDateRangePicker|class NgpDatePicker|export interface NgpDateRangePicker|export interface NgpDatePicker' packages/ng-primitives -g '*.ts' | sed -n '1,260p'

printf '\nCalendar/grid/focus files:\n'
git ls-files packages/ng-primitives/date-picker/src/lib | grep -E 'cell|grid|row|month|button|range' | sed -n '1,200p'

printf '\nDate cell implementation:\n'
cat -n packages/ng-primitives/date-picker/src/lib/mgnp-date-picker-cell/mgnp-date-picker-cell.ts | sed -n '1,120p'

printf '\nDate range implementations:\n'
for f in packages/ng-primitives/date-picker/src/lib/mgnp-date-range*.ts; do
  echo "--- $f"
  cat -n "$f" | sed -n '1,160p'
done

Repository: MGREMY/nx_source

Length of output: 30586


Only call registerOnTouched when focus leaves the picker.

focusout also fires when focus moves between range-date buttons inside mgnpDateRangePicker, and the current listener immediately calls onTouchedFn. Add a host method that invokes the callback only when event.relatedTarget is not contained by the host element, and bind '(focusout)': 'onHandleFocusOut($event)'.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts`
at line 25, Replace the direct onTouchedFn call in the host focusout binding
with an onHandleFocusOut($event) method on mgnpDateRangePicker. In that method,
invoke onTouchedFn only when event.relatedTarget is not contained within the
host element, preserving focus transitions between internal range-date buttons
without marking the picker touched.

Comment on lines +60 to +68
contructor() {
this.state()
.startDateChange.pipe(takeUntilDestroyed())
.subscribe((value) => this.onChangeFn?.({ start: value, end: this.state().endDate() }));

this.state()
.endDateChange.pipe(takeUntilDestroyed())
.subscribe((value) => this.onChangeFn?.({ start: this.state().startDate(), end: value }));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts'

ast-grep outline "$FILE" --items all
ast-grep run --lang typescript --pattern 'contructor() { $$$ }' "$FILE"

Repository: MGREMY/nx_source

Length of output: 1169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts'

printf '--- relevant file lines ---\n'
sed -n '55,75p' "$FILE" | cat -n

printf '\n--- contructor occurrences ---\n'
rg -n 'contructor|constructor' "$FILE" || true

printf '\n--- behavioral probe: TypeScript class constructor vs normal method ---\n'
node - <<'JS'
console.log('TypeScript constructor names are checked by the TypeScript compiler; a class method contructor is not equivalent to constructor.');
JS

Repository: MGREMY/nx_source

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE='packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts'

echo '--- relevant file lines ---'
sed -n '55,75p' "$FILE" | cat -n

echo
echo '--- contructor occurrences ---'
rg -n 'contructor|constructor' "$FILE" || true

echo
echo '--- behavioral probe: TS constructor name rule ---'
node - <<'JS'
console.log('TypeScript class constructors must be named constructor; a class method named contructor is not a constructor.');
JS

Repository: MGREMY/nx_source

Length of output: 1227


Rename contructor to constructor.

contructor is parsed as an ordinary method, so this subscription setup never runs when Angular creates the directive. Because the change callbacks are not subscribed, user date changes do not call the registered form change callback.

Proposed fix
-  contructor() {
+  constructor() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
contructor() {
this.state()
.startDateChange.pipe(takeUntilDestroyed())
.subscribe((value) => this.onChangeFn?.({ start: value, end: this.state().endDate() }));
this.state()
.endDateChange.pipe(takeUntilDestroyed())
.subscribe((value) => this.onChangeFn?.({ start: this.state().startDate(), end: value }));
}
constructor() {
this.state()
.startDateChange.pipe(takeUntilDestroyed())
.subscribe((value) => this.onChangeFn?.({ start: value, end: this.state().endDate() }));
this.state()
.endDateChange.pipe(takeUntilDestroyed())
.subscribe((value) => this.onChangeFn?.({ start: this.state().startDate(), end: value }));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts`
around lines 60 - 68, Rename the lifecycle method in the date-range picker from
contructor to constructor so Angular executes the subscription setup during
directive creation. Preserve both startDateChange and endDateChange
subscriptions and their existing onChangeFn callback behavior.

Comment on lines +70 to +72
writeValue(value: MgnpDateRange<T>): void {
this.state().setStart(value.start);
this.state().setEnd(value.end);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 \
  'new FormControl|\.reset\(|writeValue\(value:' \
  packages apps -g '*.ts' || true

Repository: MGREMY/nx_source

Length of output: 9584


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## candidate files"
fd -a 'mgnp-date-range-picker\.ts|date-range' packages/ng-primitives/date-picker || true

echo "## file outline"
ast-grep outline packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts --view expanded || true

echo "## relevant source"
cat -n packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts

echo "## date picker related usages and types"
rg -n -C 2 'MgnpDateRange|--date-range|dateRange|writeValue' packages/ng-primitives/date-picker -g '*.ts' || true

Repository: MGREMY/nx_source

Length of output: 24203


🌐 Web query:

Angular ControlValueAccessor writeValue allowed null value documentation

💡 Result:

In Angular, the ControlValueAccessor interface's writeValue(obj: any) method is designed to accept any value, including null [1][2]. The official Angular documentation does not explicitly forbid or warn against null values; rather, it defines the parameter type as any [1][3]. It is important to understand that your implementation of writeValue may receive a null value for several reasons: 1. Initial State: Form controls are often initialized with a default value of null, which triggers a call to writeValue [4][5][6]. 2. Form Resets: Calling the reset method on a FormControl will set its value to null, which also triggers writeValue [6]. 3. Lifecycle Behavior: Depending on the specific Angular version and the interaction between directives (such as NgModel or Reactive Forms), you may encounter scenarios where writeValue is called with null during initialization [4][7]. Because null is a valid value in the Angular forms system, your implementation of writeValue should be written defensively to handle it gracefully [4][6]. Ignoring null values entirely can cause issues, as a null value may represent a legitimate intentional state (e.g., clearing a selection or resetting a form) that your component needs to reflect in the UI [4]. Always ensure your component logic correctly interprets or sanitizes incoming null values to avoid errors or unexpected visual states [6].

Citations:


Handle null values in MgnpDateRangePicker.writeValue.

Angular forms can pass null to ControlValueAccessor.writeValue during initialization or after reset(). The current implementation dereferences value.start and value.end unconditionally and can throw. Clear both range endpoints when writeValue receives null or undefined.

Proposed fix
-  writeValue(value: MgnpDateRange<T>): void {
-    this.state().setStart(value.start);
-    this.state().setEnd(value.end);
+  writeValue(value: MgnpDateRange<T> | null | undefined): void {
+    this.state().setStart(value?.start);
+    this.state().setEnd(value?.end);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
writeValue(value: MgnpDateRange<T>): void {
this.state().setStart(value.start);
this.state().setEnd(value.end);
writeValue(value: MgnpDateRange<T> | null | undefined): void {
this.state().setStart(value?.start);
this.state().setEnd(value?.end);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ng-primitives/date-picker/src/lib/mgnp-date-range-picker/mgnp-date-range-picker.ts`
around lines 70 - 72, Update MgnpDateRangePicker.writeValue to handle null or
undefined values by clearing both range endpoints instead of dereferencing the
value; retain the existing start/end assignments for non-null MgnpDateRange
values.

@MGREMY
MGREMY merged commit 905a552 into main Aug 5, 2026
5 checks passed
@MGREMY
MGREMY deleted the feat/date-picker branch August 5, 2026 12:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant