# useMaskedDateInput

[← Back to Composables README](https://github.com/NantHealth/featherk/blob/integration/packages/composables/README.md)

Composable for a masked single-date input that pairs naturally with Kendo Vue `MaskedTextBox`, and optionally coordinates with a `DatePicker`. It handles typed input in the mask `MM/DD/YYYY`, keyboard steppers (ArrowUp/ArrowDown), wheel steppers, caret persistence, min/max range validation, and clean emission only when the input is complete and valid.

This composable is designed to preserve the user's raw input: it does not coerce or rewrite what the user has typed in `onChange`. It only emits a parsed `Date` when the input is complete and valid.

When `required` is enabled, the composable defers required-validation until after blur: a blank or incomplete value is treated as valid while the field is focused, and only after blur will it flag the value as incomplete/required.

## Prerequisites

- Vue 3 Composition API
- `@progress/kendo-vue-inputs` (for `MaskedTextBox`)
- Optional: `@progress/kendo-vue-dateinputs` (`DatePicker`)

## Quick Start

```vue
<script setup lang="ts">
import { ref } from "vue";
import { DatePicker } from "@progress/kendo-vue-dateinputs";
import { MaskedTextBox } from "@progress/kendo-vue-inputs";
import { Error } from "@progress/kendo-vue-labels";
import { useMaskedDateInput } from "@featherk/composables/date";
import { usePopupTrap } from "@featherk/composables/trap";

const selectedDate = ref<Date | undefined>();
const showPicker = ref(false);
const pickerRoot = ref<HTMLElement | null>(null);

const MIN_DATE = new Date(2024, 0, 1);
const MAX_DATE = new Date(2026, 11, 31);

const masked = useMaskedDateInput({
  id: "startDate",
  onChange: ({ value }) => { selectedDate.value = value ?? undefined; },
  onShowCalendar: () => (showPicker.value = true),
  externalValue: selectedDate,
  min: MIN_DATE,
  max: MAX_DATE,
  required: true,
  // defaultValue: new Date(), // optional baseline for steppers
});

// Destructure only values used as component props
// (avoids .value in templates while keeping handlers namespaced)
const { debugEnabled, debugLines, isValid, placeholder, raw, validationMessage } = masked;

// Integrate focus trap for the DatePicker popup
usePopupTrap({
  isOpen: showPicker,
  onRequestClose: () => (showPicker.value = false),
  triggerEl: pickerRoot,
});
</script>

<template>
  <div ref="pickerRoot">
    <DatePicker
      v-model="selectedDate"
      dateInput="masked"
      :min="MIN_DATE"
      :max="MAX_DATE"
      :valid="isValid"
      :show="showPicker"
      :validationMessage="validationMessage"
      @open="showPicker = true"
      @close="showPicker = false"
    >
      <template #masked="{ props }">
        <div data-ref-id="custom-date-input" class="custom-date-input">
          <MaskedTextBox
            id="startDate"
            class="masked-date-input"
            :mask="'00/00/0000'"
            :value="raw"
            :placeholder="placeholder"
            :showClearButton="false"
            @change="masked.handleChange"
            @keydown="masked.handleKeyDown"
            @keyup="masked.handleKeyUp"
            @click="masked.handleClick"
            @wheel="masked.handleWheel"
            @blur="masked.handleBlur"
          />

          <div v-if="debugEnabled" class="composable-debugging debug-info">
            <div v-for="(row, idx) in debugLines" :key="idx">
              <strong>{{ row[0] }}:</strong> {{ row[1] }}
            </div>
          </div>
        </div>
      </template>
    </DatePicker>
  </div>

  <Error for="startDate">{{ validationMessage }}</Error>

</template>
```

Note: Destructure only the values used as component props that would otherwise require `.value` in templates — e.g., `isValid`, `validationMessage`, `debugEnabled`, and `debugLines`. Keep event handlers (e.g., `masked.handleChange`) namespaced to clearly indicate their origin from the composable and to avoid unnecessary destructuring.

### Integrating with Kendo DatePicker (masked slot)

`useMaskedDateInput` can also power a custom masked input inside Kendo `DatePicker` via the `dateInput="masked"` slot. See the full reference in [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue).

When binding to Kendo `DatePicker`, use `Ref<Date | undefined>` for the calendar
`v-model`. The composable's `ChangePayload` uses `Date | null` to represent an
incomplete or invalid mask, so normalize that payload at the callback boundary with
`value ?? undefined` before assigning it to the DatePicker model.

## API

### `useMaskedDateInput(options)`

Creates a controller for a masked single-date input.

#### Options

- `id: string`: DOM id of the `MaskedTextBox` input element.
- `onChange: (p: ChangePayload) => void`: Called with `{ value, event }` when the composable has a new parsed value to emit.
  - Emits `value = null` if input is empty, incomplete, invalid, or outside min/max.
  - Does not mutate the user's raw string.
- `onShowCalendar: (e: KeyboardEvent) => void`: Called when Space is pressed on the input; use this to open a calendar/popup.
- `externalValue?: Ref<string | Date | null | undefined>`: Reactive external source for the selected date (e.g., calendar `v-model`). When it updates to a valid date, the composable mirrors it into `raw` without clobbering in‑progress typing.
- `min?: Date | null | undefined` / `max?: Date | null | undefined`: Bounds for validation. These are plain `Date` values (not `Ref<Date>`), since min/max do not require reactivity.
- `required?: boolean`: When `true`, the field is treated as required. While focused, blank/incomplete values are not flagged as invalid; after blur, a blank or incomplete value is considered invalid with reason `"incomplete"` and validation message `"Required"`.
- `defaultValue?: Date | null`: Optional default date for the field. When steppers (ArrowUp/ArrowDown or wheel) are used on an invalid/incomplete input, this date is used as the baseline instead of always using "today".
- `dateFormat?: string`: Placeholder/format hint (default: `mm/dd/yyyy`).
- `debug?: boolean`: Enables internal debug reporting (e.g., `debugLines`).

#### Returns

- `raw: Ref<string>`: The masked string `MM/DD/YYYY`. Use as `v-model` for `MaskedTextBox`.
- `cursorPos: Ref<number | undefined>`: Caret position for restoring after programmatic updates.
- `placeholder: Ref<string>`: Format placeholder (defaults to `mm/dd/yyyy`).
- `digitsOnly: Computed<string>`: Raw string stripped of non-digits.
- `month`, `day`, `year`: Readonly computed parts (strings, zero-padded) for diagnostics.
- `isValid: Computed<boolean>`: Validity considering completeness, parsability, min/max bounds, and (when enabled) required state.
- `reason: Computed<string | undefined>`: Machine-readable reason for the current validation state:
  - `"incomplete"` (required + blank/incomplete after blur)
  - `"invalid-date"` (complete but not a real date)
  - `"out-of-bounds"` (outside min/max)
  - `"valid"` (everything OK)
  - `undefined` (no reason yet, e.g., optional + incomplete while typing)
- `validationMessage: Computed<string>`: Human-readable message for invalid states:
  - `"Required"` when `reason === "incomplete"`.
  - `Must be in mm/dd/yyyy format.` (or your custom `dateFormat`) when `reason === "invalid-date"`.
  - Range messages (between/on or after/on or before) when `reason === "out-of-bounds"`.
- `datePart(pos: number): 'mm' | 'dd' | 'yyyy'`: Helper to determine focused part from caret position.
- `datePart(pos: number): 'mm' | 'dd' | 'yyyy'`: Helper to determine focused part from caret position.
- `initStyling(): void`: Re-applies the `fk-datepicker` class to the parent of the closest `.k-datepicker` element. This is exposed so consumers can re-run theming after Kendo manipulates the DOM; the function is idempotent.  Use this to apply the fk-datepicker style hook when the date picker isn't available on initial load.
- Event handlers to wire to the input:
  - `handleChange(event)`
  - `handleKeyDown(event)` — Space opens calendar via `onShowCalendar`; ArrowUp/ArrowDown act as steppers.
  - `handleWheel(event)` — Prevents page scroll; interprets wheel as steppers (up/down) for the focused part.
  - `handleKeyUp(event)` — Maintains caret position after cursor keys/steppers.
  - `handleClick(event)` — Captures caret position on click for restoring.
  - `handleBlur(event)` — Marks the field as touched (used for required logic) and, when `required` is true and no `defaultValue` is provided, converts an `externalValue` of `undefined` with a blank mask into an empty string so downstream validation can treat it as incomplete/required.
- Debug:
  - `debugEnabled: Ref<boolean>`
  - `debugLines: Computed<Array<[label: string, value: string]>>`

## Behavior Details

- **Mask and parsing**: Expects `00/00/0000`. Parsing requires the input to be complete (10 chars including slashes, 8 digits) and form a real calendar date (e.g., Feb 29 checks).
- **Minimum year**: The masked input supports four-digit years from `1000` onward. Dates earlier than `1000`, including zero-filled years such as `0001`, `0035`, and `0850`, are treated as invalid dates.
- **Emission policy**: Emits `Date` only when the raw is complete, valid, and within `min`/`max`. Otherwise emits `null`. Empty input emits `null`.
- **Min/Max validation**: If provided, dates outside bounds are considered invalid (`isValid=false`) and produce a range `validationMessage`.
- **Steppers**: ArrowUp/ArrowDown and mouse wheel increment/decrement the focused part (`mm`, `dd`, `yyyy`) with wrapping (months and days). If raw is incomplete/invalid, steppers initialize to today, emit that date (if in range), and restore caret.
- **Caret persistence**: After programmatic updates (including steppers), the caret is restored to the prior position to preserve typing flow.
- **Space key**: The input consumes Space and calls `onShowCalendar` to open the DatePicker popup without inserting a space into `raw`.
- **External sync**: When `externalValue` is set to a valid date, `raw` mirrors it (e.g., from a calendar selection). If external becomes null/invalid, `raw` is preserved to avoid clobbering in-progress text.
- **DatePicker bounds**: Kendo `DatePicker` has its own `min`/`max` range, with a default minimum of `01/01/1900`. When using the masked slot with historical dates before 1900, pass a matching `min` to `DatePicker`; otherwise the composable can accept the typed date while the popup calendar still displays a clamped or previous calendar value.
- **Styling hook**: On mount, adds `fk-datepicker` class to the parent of the closest `.k-datepicker` element for theming. The composable also exposes `initStyling()` so consumers can re-run styling when needed (for example, after Kendo performs DOM updates). `initStyling()` is safe to call repeatedly — it is idempotent.

## Integration Patterns

- **MaskedTextBox only**: Use the Quick Start setup to accept typed dates with validation — no calendar required.
- **Kendo DatePicker (masked slot)**: Provide a custom `MaskedTextBox` in `DatePicker`'s `masked` slot. See [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue) for a complete example, including focus management and theming.

## Accessibility Notes

- Ensure labels (e.g., Kendo `Label` with `for`) correctly reference the input `id`.
- When used with `DatePicker`, Kendo manages its popup and focus behavior.
- Keep tab order logical; Space opens the calendar, Escape should close it.

## Limitations & Assumptions

- Date format is fixed to `MM/DD/YYYY`; internationalization of the mask/parser is not included yet.
- Built to pair with Kendo Vue inputs/dateinputs; other inputs may need minor adjustments.

## Tips

- Keep `minDate`/`maxDate` aligned with your calendar or DatePicker configuration for consistent validation.
- Use `debug: true` and display `debugLines` during integration to verify caret, parsed values, and masks.
- Avoid mutating `raw` externally except through `v-model`; let the composable manage its value and emit parsed dates.

## Types

```ts
export type ChangePayload = { value: Date | null; event: any };
```

## Reference Implementation

See the project’s full reference usage in [src/components/custom-date-picker/CustomDatePicker.vue](../src/components/custom-date-picker/CustomDatePicker.vue), which includes calendar integration, focus-trap, external store sync, and accessibility tweaks.
