npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ionic-daterange-picker

v1.1.1

Published

Syncfusion-style date range picker for Ionic + Angular, built from native Ionic components and Ionic CSS tokens so third-party themes (@rdlabo/ionic-theme-md3, @rdlabo/ionic-theme-ios26) style it for free.

Downloads

400

Readme

ionic-daterange-picker

A date range picker with the feature set of Syncfusion's ejs-daterangepicker, made of nothing but Ionic components.

The calendar is a real ion-datetime — the same grid, month/year drill-down, navigation, keyboard handling and locale the app already shows for a single date. The field is an ion-input, the dialog is an ion-modal, the shortcuts are ion-chips. A theme that restyles Ionic — @rdlabo/ionic-theme-md3, @rdlabo/ionic-theme-ios26 — therefore restyles this picker without knowing it exists, because there is nothing in it that is not theirs already.

What it adds on top of ion-datetime is the range: which day a tap starts, extends or restarts, a header with the two ends in its top corners and the length of the range between them, and the Clear / Cancel / Apply row. While a range is half picked, the end the next tap will set is the one in the accent colour.

Angular 20+, signals throughout, zoneless-safe, ControlValueAccessor included.

Status

1.1.1 — the footer row is a three-column grid aligned to the header's gutters, with no outlined button on iOS. See Theming.

1.1.0 — the footer's Cancel / Clear / Apply row was rebuilt so the three read as three different actions under @rdlabo/ionic-theme-ios27, which grouped and flattened them.

1.0.0 — first release.

Install

npm install ionic-daterange-picker
# or
bun add ionic-daterange-picker

Working against a local checkout

While developing the picker itself, a consuming app points at the built dist/ directly:

cd ionic-daterange-picker
bun install
bun run build          # emits dist/
// the app's package.json
"dependencies": {
  "ionic-daterange-picker": "file:../../ionic-daterange-picker/dist"
}

A file: dependency survives bun install; bun link does not — an install with the package missing from package.json prunes the symlink and the app stops compiling. bun links the built files one by one, so bun run watch is enough for a change to reach the app, but a new file in dist/ needs another bun install.

The app also needs preserveSymlinks: true in its angular.json build options. Without it esbuild resolves past the symlink, the Angular Linker never processes the partially-compiled library, and it fails at runtime asking for the JIT compiler.

Peer dependencies: @angular/core, @angular/common, @angular/forms, @ionic/angular, ionicons.

Quick start

With a reactive form — the control value is [startDate, endDate], exactly what Syncfusion published, so an existing formControlName needs no other change:

import { IonicDateRangePicker } from 'ionic-daterange-picker';

@Component({
    imports: [ReactiveFormsModule, IonicDateRangePicker],
    template: `<ionic-daterange-picker formControlName="fromTo" [openOnFocus]="true" />`
})

With signals and no form:

<ionic-daterange-picker [(value)]="range" [min]="today()" [maxDays]="30" (change)="onChange($event)" />

Inline, with no field (a filter panel, a wizard step):

<ionic-daterange-picker presentation="inline" formControlName="fromTo" />

Just the calendar, if you want to build your own surface around it:

<ionic-daterange-calendar [(value)]="range" [showButtons]="false" [min]="today()" />

API

<ionic-daterange-picker>

| Input | Type | Default | Notes | | --- | --- | --- | --- | | value | Date[] \| {start, end} \| null | null | Two-way. Reads any of those shapes, writes [start, end]. | | appearance | 'field' \| 'button' | 'field' | 'field' is an ion-input with a label; 'button' a filled ion-button with a calendar icon that reads the range as its text. | | buttonColor | string | 'primary' | The button trigger's Ionic colour. 'light' reads as a white pill on a coloured hero. | | label, labelPlacement, fill, placeholder, helperText, errorText | — | — | Passed straight to the ion-input. | | min / max | Date \| null | null | Bounds; days outside are disabled and navigation stops there. | | minDays / maxDays | number | 0 (off) | Inclusive span limits, enforced while picking. | | presentation | 'auto' \| 'popover' \| 'modal' \| 'inline' | 'auto' | 'auto' and 'modal' are a centred dialog over a dimmed backdrop; 'popover' anchors to the field; 'inline' has no field at all. | | autoApply | boolean | false | Commit and close on the second tap, instead of waiting for Apply. | | allowEdit | boolean | false | Typing into the field, parsed against format. | | openOnFocus | boolean | false | Opens on keyboard focus too. Focus restored by a closing overlay is ignored, or Cancel would reopen what it just closed. | | clearable | boolean | true | Shows the clear button when there is a value. | | presets | DateRangePreset[] \| null | null | defaultPresets(labels) gives the usual seven. | | format | string | locale pattern | Tokens: yyyy yy MMMM MMM MM M dd d EEEE EEE. | | locale | string | navigator.language | Drives names, first weekday and the default pattern. | | firstDayOfWeek | number \| null | from locale | 0 = Sunday. | | isDateEnabled | (date: Date) => boolean | null | Vetoes days — holidays, days already booked. Syncfusion's renderDayCell role. | | showTitle | boolean | true | The header with the summary line. | | labels | Partial<DateRangeLabels> | English | All user-facing text. | | disabled | boolean | false | Also set by setDisabledState from a form. |

| Output | Payload | | --- | --- | | change | { value, startDate, endDate, daysCount, nightsCount, source } | | opened / closed | void | | valueChange | Date[] \| null (the two-way half of value) |

Methods: open(), apply(), cancel(), clear().

<ionic-daterange-calendar>

The calendar on its own, with no field around it: value (two-way), min, max, minDays, maxDays, locale, firstDayOfWeek, isDateEnabled, labels, format, disabled, showTitle, showButtons, plus the rangeComplete, applied and cancelled outputs.

App-wide defaults

import { provideIonicDateRangePicker, defaultPresets } from 'ionic-daterange-picker';

providers: [
    provideIonicDateRangePicker({
        locale: 'es',
        format: 'dd/MM/yyyy',
        openOnFocus: true,
        presets: defaultPresets({ today: 'Hoy', last7Days: 'Últimos 7 días' }),
        labels: { apply: 'Aplicar', cancel: 'Cancelar', clear: 'Limpiar', title: 'Selecciona fechas' }
    })
]

Anything bound on an individual picker still wins over these.

Theming

There is almost nothing to theme. The calendar is an ion-datetime, the dialog an ion-modal, the field an ion-input: whatever styles those in your app styles them here, @rdlabo/ionic-theme-md3, @rdlabo/ionic-theme-ios26 and @rdlabo/ionic-theme-ios27 included.

The footer is a three-column grid — 1fr auto 1fr, the same one the header above it uses — so Cancel's text sits on the left gutter, Apply's filled edge on the right one, and Clear in the middle, under the range length. The three are separate ion-buttons rather than an ion-buttons group, because @rdlabo/ionic-theme-ios27 draws one glass pill around an ion-buttons that holds only clear buttons, which welded Clear to Apply and left Cancel looking exactly like Clear.

Only Apply carries a fill: on iOS an outlined button reads as a form control rather than a dialog action, so Cancel is tinted text, Clear is neutral text, and Apply is fill="solid" with the theme's .button-submit class. Apply is deliberately not type="submit", which would submit whatever form the picker sits in; .button-submit is the theme's opt-in for the same appearance without that.

Three custom properties tune the row, set on .idrp-actions: --idrp-action-gutter (the inline margin it shares with the header), --idrp-action-text-inset (the inline padding the platform gives an ion-button, which Cancel is pulled out by so its text lands on the gutter) and --idrp-action-height (one height for all three, so no fill sits a pixel lower than another).

Two knobs for the parts that are this component's own:

  • --idrp-dialog-background — the dialog surface. Set explicitly rather than inherited because a theme may make a modal that contains a calendar transparent, on the assumption that the calendar is itself the card. Here the card also holds the header and the buttons. In iOS mode the dialog instead takes the glass treatment, built from the variables @rdlabo/ionic-theme-ios26 defines.
  • --idrp-range-background / --idrp-range-color — every day of the range, painted through ion-datetime's calendar-day active CSS part. They exist because Ionic draws a selected day that happens to be today differently from the rest, which reads as two colours inside one range; this makes the whole range one colour. Set the background to a tint if you prefer a band with no emphasis.
  • Class hooks: .idrp-host, .idrp-trigger, .idrp-trigger-button, .idrp-input, .idrp-open, .idrp-clear, .idrp-panel, .idrp-presets, .idrp-summary, .idrp-datetime, .idrp-actions and the three .idrp-button-*. The components use ViewEncapsulation.None, so those work from a global stylesheet — but a page stylesheet still needs ::ng-deep, because the elements belong to this component's template and carry none of the page's encapsulation attributes.

Coming from ejs-daterangepicker

| Syncfusion | Here | | --- | --- | | <ejs-daterangepicker formControlName="x"> | <ionic-daterange-picker formControlName="x"> | | control value Date[] | unchanged — [startDate, endDate] | | [allowEdit], [openOnFocus], [min], [max], [format], [placeholder] | same names | | minDays / maxDays | same names | | presets (<e-presets> children) | [presets] array of { id, label, range } | | renderDayCell event | [isDateEnabled] function | | (change)RangeEventArgs | (change)DateRangeChangeEvent (startDate, endDate, value, plus daysCount/nightsCount) | | cssClass | plain class | | L10n.load(...) / loadCldr | [labels] + locale, everything else from Intl | | drp.show() | pickerRef.open() |

Not carried over, because ion-datetime does not do them: two months side by side, an ISO week column, and the hover preview of the range before the second click. Also gone: time selection (ejs-datetimepicker territory), strictMode, and the float-label animation — ion-input's own label placement replaces it.

Accessibility

Ionic's, unchanged: ion-datetime implements the WAI-ARIA date-grid pattern, and the range is built out of its own selection, so a screen reader reads the calendar exactly as it does anywhere else in the app.

Development

bun run build       # ng-packagr → dist/
bun run watch       # rebuild on change
bun run test        # vitest, pure logic only (no DOM, no TestBed)
bun run typecheck

The pure parts — date maths, value coercion, the selection state machine, the format/parse pair — live in src/lib/date-range-{date,model,intl}.ts and are where the tests are. The two components are thin by design: one wraps ion-datetime with range logic, the other wraps that with a field and a dialog.

Releasing

The package that goes to npm is the built one, dist/, not the repository root: the root package.json is the source manifest, and ng-packagr rewrites it — resolving the entry points, dropping the scripts and the devDependencies — into dist/package.json.

bun run test
bun run build
npm pack --dry-run ./dist   # what will actually be uploaded
npm login                   # once
bun run release             # build + npm publish ./dist

Bump version in the root package.json, tag it (git tag -a v1.0.1 -m v1.0.1 && git push --tags) and publish from a clean tree, so the tag and the version on npm say the same thing.