@khorshid-js/element
v0.1.0
Published
<khorshid-datepicker> — a framework-free Custom Element for the Khorshid Jalali & Gregorian date picker.
Maintainers
Readme
@khorshid-js/element
<khorshid-datepicker> — the Khorshid Jalali & Gregorian date picker as a
framework-free Custom Element. It wraps createDatePicker from
@khorshid-js/dom and renders into its light DOM, so the standard stylesheet
applies and the field takes part in the surrounding <form>.
<link rel="stylesheet" href="node_modules/@khorshid-js/element/styles/khorshid.css" />
<script type="module">
import '@khorshid-js/element/define'; // registers <khorshid-datepicker>
</script>
<khorshid-datepicker locale="fa-IR" placeholder="۱۴۰۵/۰۱/۰۱"></khorshid-datepicker>
<khorshid-datepicker mode="range" months="2" min-days="2" shortcuts></khorshid-datepicker>
<khorshid-datepicker display="inline" locale="en-US" secondary-calendar></khorshid-datepicker>The dist bundles @khorshid-js/dom + @khorshid-js/core, so a plain
<script type="module"> works without a bundler. Prefer explicit
registration? import { defineDatePickerElement } from '@khorshid-js/element' and
call defineDatePickerElement() (optionally with another tag name). The module
is SSR-safe: importing it on the server neither throws nor registers anything.
Also available from the umbrella package as khorshid/element.
Attributes
Booleans follow HTML semantics (present = on); ="false" also turns one off,
which is how you disable options that default to on.
| Attribute | Option | Notes |
|---|---|---|
| locale | locale | live (setLocale) |
| theme | theme | light / dark / auto, live |
| mode | mode | single / range / multiple, live |
| value | value | 2026-03-21, start/end (ISO interval), or a comma list; live |
| unit | unit | day / week / month / year |
| display | display | auto (popup / sheet), inline, popup, sheet, modal |
| min, max | min, max | ISO dates |
| months | months | panes side by side |
| min-days, max-days | minDays, maxDays | range length limits |
| shortcuts | shortcuts | boolean, or a comma list of preset ids (today,thisWeek) |
| secondary-calendar | secondaryCalendar | boolean, or a calendar id |
| natural-input | naturalInput | boolean |
| time | time | boolean |
| name, required | name, required | form field submission / validation |
| readonly | readOnly | boolean |
| disabled | — | disables the inner input; the picker never opens |
| placeholder | — | on the inner input, live |
| position | position | auto, bottom-start, … |
| z-index | zIndex | |
| mobile-breakpoint | mobileBreakpoint | px, or false |
| outside-days | outsideDays | interactive / visible / hidden |
| show-today, show-clear, animate, highlight-today, year-picker | same, camelCase | default on, so turn off with ="false" |
| confirm | confirm | true / false / auto |
| week-start | weekStart | 0 (Sun) … 6 (Sat) |
| format | format | display format, e.g. YYYY/MM/DD |
| fixed-weeks, auto-close, input-mask | same, camelCase | default on |
Unknown attributes and invalid values are ignored. locale, theme, mode,
value and placeholder apply live. Changing any other attribute re-creates
the picker and keeps the current value.
Properties and methods
const el = document.querySelector('khorshid-datepicker');
el.value; // core PickerValue: { mode: 'single', date: DateValue | null } …
el.value = '2026-03-21'; // anything setValue accepts (ISO, [..], { start, end }, PickerValue)
el.options = { // JS-only options merged over the attributes (assigning re-creates)
disabledDates: (d) => d.weekday === 5,
shortcuts: [{ id: 'nowruz', label: 'نوروز', value: '2026-03-21' }],
};
el.picker; // the @khorshid-js/dom DatePicker, or null while disconnected
el.core; // its headless PickerCore
el.open(); el.close(); el.clear();Removing the element destroys the picker, along with any open overlay. Re-inserting it builds a new picker with the same value.
Events
Every core event is re-dispatched on the host as a CustomEvent named
sdp-<event>. The event bubbles, is composed, and its detail is the core
payload. The sdp- prefix keeps these events apart from the inner input's
native change, input and select events, which also bubble through the
host.
| Event | detail |
|---|---|
| sdp-change | { value, source } |
| sdp-select | { date } |
| sdp-clear | { previous } |
| sdp-range-start, sdp-range-complete | { start }, { start, end } |
| sdp-open, sdp-close | {} |
| sdp-view-change, sdp-month-change, sdp-year-change | view / navigation |
| sdp-locale-change, sdp-calendar-change, sdp-time-change | |
| sdp-shortcut | { id, value } |
| sdp-input, sdp-error | |
el.addEventListener('sdp-change', (e) => console.log(e.detail.value));DATEPICKER_ELEMENT_EVENTS lists all of these names.
Forms
The element renders in light DOM, so whatever field @khorshid-js/dom renders for
name sits inside your <form>. That field is a hidden input.sdp-form-value
holding the serialized value: ISO YYYY-MM-DD, start/end for a range,
comma-joined for multiple. To change the format, pass formValue through
el.options.
<form>
<label>Check-in / out
<khorshid-datepicker name="stay" mode="range" required></khorshid-datepicker>
</label>
<button>Book</button>
</form>A wrapping <label> labels the inner input.
<khorshid-date-field>
A segmented date input: each part of the date (year, month, day, and
optionally hour, minute, second, AM/PM) is its own spinbutton, as in React Aria
or HeroUI's DateField. It wraps createDateField from @khorshid-js/dom and,
like the picker, renders into its light DOM.
<script type="module">
import '@khorshid-js/element/define'; // registers both elements
// or: import { defineDateFieldElement } from '@khorshid-js/element'; defineDateFieldElement();
</script>
<khorshid-date-field locale="fa-IR" label="تاریخ تولد" name="dob" required></khorshid-date-field>
<khorshid-date-field locale="en-US-gregorian" time="12h" minute-step="15"
value="2026-03-21T09:30" min="2026-01-01"></khorshid-date-field>Attributes
| Attribute | Option | Notes |
|---|---|---|
| locale | locale | live (setLocale); the calendar comes from the locale |
| theme | theme | light / dark / auto, live |
| value | value | YYYY-MM-DD or YYYY-MM-DDTHH:mm[:ss]; live. It is also the default that a form reset restores. Invalid → empty |
| min, max | min, max | ISO dates, live |
| required, readonly, disabled, invalid | same (readOnly) | booleans, live |
| placeholder-value | placeholderValue | ISO date that an empty segment steps from |
| format | format | segment order, e.g. YYYY-MM-DD |
| time | time | present / 24 = 24-hour, 12 / 12h = 12-hour with AM/PM, false = off |
| time-seconds | time.showSeconds | boolean, only with time |
| minute-step | time.minuteStep | arrow-key step, only with time |
| name | name | submits through a hidden input.sdp-form-value |
| label, description, error-message | same, camelCase | plain text (pass HTML or nodes through el.options) |
| variant | variant | primary / secondary |
| full-width | fullWidth | boolean (the stylesheet makes the host display: block) |
| aria-label | ariaLabel | accessible name when there is no label |
| auto-focus | autoFocus | focuses the first segment when inserted |
Any other attribute change, or a new el.options, re-creates the field. The
value is kept, and so is focus if a segment had it.
Properties, methods and events
const el = document.querySelector('khorshid-date-field');
el.value; // DateValue | null (null while empty, incomplete or invalid)
el.value = '2026-03-21'; // ISO, Date, PlainDate, DateValue or null
el.options = { validate: (d) => d.weekday !== 5 || 'No Fridays', prefix: '<svg …/>' };
el.field; // the @khorshid-js/dom DateField, or null while disconnected
el.core; // its headless DateFieldCore
el.clear(); el.focus(); el.validate();
el.addEventListener('sdp-change', (e) => console.log(e.detail.value, e.detail.source));| Event | detail |
|---|---|
| sdp-change | { value, source }, where source is 'input', 'api' or 'clear' |
| sdp-input | { type, value, text, complete } on every segment edit |
| sdp-error | validation { code, message, input } when a complete entry is refused |
| sdp-locale-change | { locale } |
DATE_FIELD_ELEMENT_EVENTS lists these names. The element is not
form-associated itself. With name (or required), the field renders its own
hidden input.sdp-form-value inside your <form>. That input mirrors
required and disabled and carries the field's validation message as its
custom validity. Removing the element destroys the field, and re-inserting it
restores the value.
