|
import { createFormatter, dateStore, getAnnouncer, getDaysInMonth, getDefaultDate, getFirstSegment, handleSegmentNavigation, isBefore, isSegmentNavigationKey, moveToNextSegment, toDate, } from '../../internal/helpers/date/index.js'; |
|
import { addMeltEventListener, makeElement, createElHelpers, effect, executeCallbacks, isHTMLElement, isNumberString, kbd, noop, omit, overridable, sleep, styleToString, toWritableStores, withGet, } from '../../internal/helpers/index.js'; |
|
import { derived, writable } from 'svelte/store'; |
|
import { generateIds } from '../../internal/helpers/id.js'; |
|
import { areAllSegmentsFilled, createContent, getPartFromNode, getValueFromSegments, inferGranularity, initSegmentStates, initializeSegmentValues, isAcceptableSegmentKey, isDateAndTimeSegmentObj, isDateSegmentPart, isFirstSegment, removeDescriptionElement, setDescription, syncSegmentValues, } from './_internal/helpers.js'; |
|
import { createHiddenInput } from '../hidden-input/create.js'; |
|
const defaults = { |
|
isDateUnavailable: undefined, |
|
value: undefined, |
|
hourCycle: undefined, |
|
locale: 'en', |
|
granularity: undefined, |
|
hideTimeZone: false, |
|
disabled: false, |
|
readonly: false, |
|
readonlySegments: undefined, |
|
name: undefined, |
|
required: false, |
|
minValue: undefined, |
|
maxValue: undefined, |
|
}; |
|
const prefix = 'dateField'; |
|
const { name } = createElHelpers(prefix); |
|
const dateFieldIdParts = [ |
|
'field', |
|
'label', |
|
'description', |
|
'validation', |
|
'day', |
|
'month', |
|
'year', |
|
'hour', |
|
'minute', |
|
'second', |
|
'dayPeriod', |
|
'timeZoneName', |
|
]; |
|
export function createDateField(props) { |
|
const withDefaults = { ...defaults, ...props }; |
|
const options = toWritableStores(omit(withDefaults, 'value', 'placeholder', 'ids')); |
|
const { locale, granularity, hourCycle, hideTimeZone, isDateUnavailable, disabled, readonly, readonlySegments, name: nameStore, required, minValue, maxValue, } = options; |
|
const defaultDate = getDefaultDate({ |
|
defaultPlaceholder: withDefaults.defaultPlaceholder, |
|
granularity: withDefaults.granularity, |
|
defaultValue: withDefaults.defaultValue, |
|
}); |
|
const valueWritable = withDefaults.value ?? writable(withDefaults.defaultValue); |
|
const value = overridable(valueWritable, withDefaults.onValueChange); |
|
const isInvalid = derived([value, isDateUnavailable, minValue, maxValue], ([$value, $isDateUnavailable, $minValue, $maxValue]) => { |
|
if (!$value) |
|
return false; |
|
if ($isDateUnavailable?.($value)) |
|
return true; |
|
if ($minValue && isBefore($value, $minValue)) |
|
return true; |
|
if ($maxValue && isBefore($maxValue, $value)) |
|
return true; |
|
return false; |
|
}); |
|
const placeholderWritable = withDefaults.placeholder ?? writable(withDefaults.defaultPlaceholder ?? defaultDate); |
|
const placeholder = dateStore(overridable(placeholderWritable, withDefaults.onPlaceholderChange), withDefaults.defaultPlaceholder ?? defaultDate); |
|
const inferredGranularity = withGet.derived([placeholder, granularity], ([$placeholder, $granularity]) => { |
|
if ($granularity) { |
|
return $granularity; |
|
} |
|
else { |
|
return inferGranularity($placeholder, $granularity); |
|
} |
|
}); |
|
const formatter = createFormatter(locale.get()); |
|
const initialSegments = initializeSegmentValues(inferredGranularity.get()); |
|
const segmentValues = withGet.writable(structuredClone(initialSegments)); |
|
let announcer = getAnnouncer(); |
|
|
|
|
|
|
|
|
|
|
|
const updatingDayPeriod = writable(null); |
|
const readonlySegmentsSet = withGet(derived(readonlySegments, ($readonlySegments) => new Set($readonlySegments))); |
|
const ids = toWritableStores({ ...generateIds(dateFieldIdParts), ...withDefaults.ids }); |
|
const idValues = derived([ |
|
ids.field, |
|
ids.label, |
|
ids.description, |
|
ids.validation, |
|
ids.day, |
|
ids.month, |
|
ids.year, |
|
ids.hour, |
|
ids.minute, |
|
ids.second, |
|
ids.dayPeriod, |
|
ids.timeZoneName, |
|
], ([$fieldId, $labelId, $descriptionId, $validationId, $dayId, $monthId, $yearId, $hourId, $minuteId, $secondId, $dayPeriodId, $timeZoneNameId,]) => { |
|
return { |
|
field: $fieldId, |
|
label: $labelId, |
|
description: $descriptionId, |
|
validation: $validationId, |
|
day: $dayId, |
|
month: $monthId, |
|
year: $yearId, |
|
hour: $hourId, |
|
minute: $minuteId, |
|
second: $secondId, |
|
dayPeriod: $dayPeriodId, |
|
timeZoneName: $timeZoneNameId, |
|
}; |
|
}); |
|
|
|
|
|
|
|
|
|
const defaultSegmentAttrs = { |
|
role: 'spinbutton', |
|
contenteditable: true, |
|
tabindex: 0, |
|
spellcheck: false, |
|
inputmode: 'numeric', |
|
autocorrect: 'off', |
|
enterkeyhint: 'next', |
|
style: styleToString({ |
|
'caret-color': 'transparent', |
|
}), |
|
}; |
|
const states = initSegmentStates(); |
|
const allSegmentContent = derived([segmentValues, locale, inferredGranularity, hideTimeZone, hourCycle], ([$segmentValues, $locale, $inferredGranularity, $hideTimeZone, $hourCycle]) => { |
|
return createContent({ |
|
segmentValues: $segmentValues, |
|
formatter, |
|
locale: $locale, |
|
granularity: $inferredGranularity, |
|
dateRef: placeholder.get(), |
|
hideTimeZone: $hideTimeZone, |
|
hourCycle: $hourCycle, |
|
}); |
|
}); |
|
const segmentContents = derived(allSegmentContent, ($allSegmentContent) => $allSegmentContent.arr); |
|
const segmentContentsObj = derived(allSegmentContent, ($allSegmentContent) => $allSegmentContent.obj); |
|
const label = makeElement(name('label'), { |
|
stores: [isInvalid, disabled, ids.label], |
|
returned: ([$isInvalid, $disabled, $labelId]) => { |
|
return { |
|
id: $labelId, |
|
'data-invalid': $isInvalid ? '' : undefined, |
|
'data-disabled': $disabled ? '' : undefined, |
|
}; |
|
}, |
|
action: (node) => { |
|
const unsub = executeCallbacks(addMeltEventListener(node, 'click', () => { |
|
const firstSegment = getFirstSegment(ids.field.get()); |
|
if (!firstSegment) |
|
return; |
|
sleep(1).then(() => firstSegment.focus()); |
|
}), addMeltEventListener(node, 'mousedown', (e) => { |
|
if (!e.defaultPrevented && e.detail > 1) { |
|
e.preventDefault(); |
|
} |
|
})); |
|
return { |
|
destroy: unsub, |
|
}; |
|
}, |
|
}); |
|
const validation = makeElement(name('validation'), { |
|
stores: [isInvalid, ids.validation], |
|
returned: ([$isInvalid, $validationId]) => { |
|
const validStyle = styleToString({ |
|
display: 'none', |
|
}); |
|
return { |
|
id: $validationId, |
|
'data-invalid': $isInvalid ? '' : undefined, |
|
style: $isInvalid ? undefined : validStyle, |
|
}; |
|
}, |
|
}); |
|
const hiddenInput = createHiddenInput({ |
|
prefix, |
|
value: derived(value, ($value) => $value?.toString() ?? ''), |
|
name: nameStore, |
|
disabled, |
|
required, |
|
}); |
|
const fieldIdDeps = derived([ids.field, ids.label, ids.description, ids.label], ([$fieldId, $labelId, $descriptionId, $validationId]) => { |
|
return { |
|
field: $fieldId, |
|
label: $labelId, |
|
description: $descriptionId, |
|
validation: $validationId, |
|
}; |
|
}); |
|
|
|
|
|
|
|
const field = makeElement(name('field'), { |
|
stores: [value, isInvalid, disabled, readonly, fieldIdDeps], |
|
returned: ([$value, $isInvalid, $disabled, $readonly, $ids]) => { |
|
const describedBy = $value |
|
? `${$ids.description}${$isInvalid ? ` ${$ids.validation}` : ''}` |
|
: `${$ids.description}`; |
|
return { |
|
role: 'group', |
|
id: $ids.field, |
|
'aria-labelledby': $ids.label, |
|
'aria-describedby': describedBy, |
|
'aria-disabled': $disabled ? 'true' : undefined, |
|
'aria-readonly': $readonly ? 'true' : undefined, |
|
'data-invalid': $isInvalid ? '' : undefined, |
|
'data-disabled': $disabled ? '' : undefined, |
|
}; |
|
}, |
|
|
|
|
|
action: (_node) => { |
|
|
|
|
|
|
|
|
|
announcer = getAnnouncer(); |
|
return { |
|
destroy() { |
|
removeDescriptionElement(ids.description.get()); |
|
}, |
|
}; |
|
}, |
|
}); |
|
const segmentBuilders = { |
|
day: { |
|
attrs: daySegmentAttrs, |
|
action: daySegmentAction, |
|
}, |
|
month: { |
|
attrs: monthSegmentAttrs, |
|
action: monthSegmentAction, |
|
}, |
|
year: { |
|
attrs: yearSegmentAttrs, |
|
action: yearSegmentAction, |
|
}, |
|
hour: { |
|
attrs: hourSegmentAttrs, |
|
action: hourSegmentAction, |
|
}, |
|
minute: { |
|
attrs: minuteSegmentAttrs, |
|
action: minuteSegmentAction, |
|
}, |
|
second: { |
|
attrs: secondSegmentAttrs, |
|
action: secondSegmentAction, |
|
}, |
|
dayPeriod: { |
|
attrs: dayPeriodSegmentAttrs, |
|
action: dayPeriodSegmentAction, |
|
}, |
|
literal: { |
|
attrs: literalSegmentAttrs, |
|
action: literalSegmentAction, |
|
}, |
|
timeZoneName: { |
|
attrs: timeZoneSegmentAttrs, |
|
action: timeZoneSegmentAction, |
|
}, |
|
}; |
|
const segment = makeElement(name('segment'), { |
|
stores: [ |
|
segmentValues, |
|
hourCycle, |
|
placeholder, |
|
value, |
|
isInvalid, |
|
disabled, |
|
readonly, |
|
readonlySegmentsSet, |
|
idValues, |
|
locale, |
|
], |
|
returned: ([$segmentValues, $hourCycle, $placeholder, $value, $isInvalid, $disabled, $readonly, $readonlySegmentsSet, $idValues, _,]) => { |
|
const props = { |
|
segmentValues: $segmentValues, |
|
hourCycle: $hourCycle, |
|
placeholder: $placeholder, |
|
ids: $idValues, |
|
}; |
|
return (part) => { |
|
const inReadonlySegments = $readonlySegmentsSet.has(part); |
|
const defaultAttrs = { |
|
...getSegmentAttrs(part, props), |
|
'aria-invalid': $isInvalid ? 'true' : undefined, |
|
'aria-disabled': $disabled ? 'true' : undefined, |
|
'aria-readonly': $readonly || inReadonlySegments ? 'true' : undefined, |
|
'data-invalid': $isInvalid ? '' : undefined, |
|
'data-disabled': $disabled ? '' : undefined, |
|
'data-segment': `${part}`, |
|
}; |
|
if (part === 'literal') { |
|
return defaultAttrs; |
|
} |
|
const id = $idValues[part]; |
|
const hasDescription = isFirstSegment(id, $idValues.field) || $value; |
|
const describedBy = hasDescription |
|
? `${hasDescription} ${$isInvalid ? $idValues.validation : ''}` |
|
: undefined; |
|
return { |
|
...defaultAttrs, |
|
id: $idValues[part], |
|
'aria-labelledby': getLabelledBy(part), |
|
contenteditable: $readonly || inReadonlySegments || $disabled ? false : true, |
|
'aria-describedby': describedBy, |
|
tabindex: $disabled ? undefined : 0, |
|
}; |
|
}; |
|
}, |
|
action: (node) => getSegmentAction(node), |
|
}); |
|
function updateSegment(part, cb) { |
|
if (disabled.get() || readonly.get() || readonlySegmentsSet.get().has(part)) |
|
return; |
|
segmentValues.update((prev) => { |
|
const dateRef = placeholder.get(); |
|
if (isDateAndTimeSegmentObj(prev)) { |
|
const pVal = prev[part]; |
|
const castCb = cb; |
|
if (part === 'month') { |
|
const next = castCb(pVal); |
|
if (part === 'month' && next !== null && prev.day !== null) { |
|
const date = dateRef.set({ month: next }); |
|
const daysInMonth = getDaysInMonth(toDate(date)); |
|
if (prev.day > daysInMonth) { |
|
prev.day = daysInMonth; |
|
} |
|
} |
|
return { |
|
...prev, |
|
[part]: next, |
|
}; |
|
} |
|
else if (part === 'dayPeriod') { |
|
const next = castCb(pVal); |
|
updatingDayPeriod.set(next); |
|
const date = placeholder.get(); |
|
if ('hour' in date) { |
|
const trueHour = date.hour; |
|
if (next === 'AM') { |
|
if (trueHour >= 12) { |
|
prev.hour = trueHour - 12; |
|
} |
|
} |
|
else if (next === 'PM') { |
|
if (trueHour < 12) { |
|
prev.hour = trueHour + 12; |
|
} |
|
} |
|
} |
|
return { |
|
...prev, |
|
[part]: next, |
|
}; |
|
} |
|
else if (part === 'hour') { |
|
const next = castCb(pVal); |
|
if (next !== null && prev.dayPeriod !== null) { |
|
const dayPeriod = formatter.dayPeriod(toDate(dateRef.set({ hour: next }))); |
|
if (dayPeriod === 'AM' || dayPeriod === 'PM') { |
|
prev.dayPeriod = dayPeriod; |
|
} |
|
} |
|
return { |
|
...prev, |
|
[part]: next, |
|
}; |
|
} |
|
const next = castCb(pVal); |
|
return { |
|
...prev, |
|
[part]: next, |
|
}; |
|
} |
|
else if (isDateSegmentPart(part)) { |
|
const pVal = prev[part]; |
|
const castCb = cb; |
|
const next = castCb(pVal); |
|
if (part === 'month' && next !== null && prev.day !== null) { |
|
const date = dateRef.set({ month: next }); |
|
const daysInMonth = getDaysInMonth(toDate(date)); |
|
if (prev.day > daysInMonth) { |
|
prev.day = daysInMonth; |
|
} |
|
} |
|
return { |
|
...prev, |
|
[part]: next, |
|
}; |
|
} |
|
return prev; |
|
}); |
|
const $segmentValues = segmentValues.get(); |
|
const $fieldId = ids.field.get(); |
|
if (areAllSegmentsFilled($segmentValues, $fieldId)) { |
|
value.set(getValueFromSegments({ |
|
segmentObj: $segmentValues, |
|
id: $fieldId, |
|
dateRef: placeholder.get(), |
|
})); |
|
updatingDayPeriod.set(null); |
|
} |
|
else { |
|
value.set(undefined); |
|
segmentValues.set($segmentValues); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function handleSegmentKeydown(e, part) { |
|
const $disabled = disabled.get(); |
|
if (e.key !== kbd.TAB) { |
|
e.preventDefault(); |
|
} |
|
if ($disabled) |
|
return; |
|
const segmentKeydownHandlers = { |
|
day: handleDaySegmentKeydown, |
|
month: handleMonthSegmentKeydown, |
|
year: handleYearSegmentKeydown, |
|
hour: handleHourSegmentKeydown, |
|
minute: handleMinuteSegmentKeydown, |
|
second: handleSecondSegmentKeydown, |
|
dayPeriod: handleDayPeriodSegmentKeydown, |
|
timeZoneName: handleTimeZoneSegmentKeydown, |
|
}; |
|
segmentKeydownHandlers[part](e); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function handleSegmentClick(e) { |
|
const $disabled = disabled.get(); |
|
if ($disabled) { |
|
e.preventDefault(); |
|
return; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function daySegmentAttrs(props) { |
|
const { segmentValues, placeholder, ids } = props; |
|
const isEmpty = segmentValues.day === null; |
|
const date = segmentValues.day ? placeholder.set({ day: segmentValues.day }) : placeholder; |
|
const valueNow = date.day; |
|
const valueMin = 1; |
|
const valueMax = getDaysInMonth(toDate(date)); |
|
const valueText = isEmpty ? 'Empty' : `${valueNow}`; |
|
return { |
|
...defaultSegmentAttrs, |
|
id: ids.day, |
|
'aria-label': `day,`, |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function daySegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'day')), addMeltEventListener(node, 'focusout', () => (states.day.hasLeftFocus = true)), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleDaySegmentKeydown(e) { |
|
if (!isAcceptableSegmentKey(e.key)) { |
|
return; |
|
} |
|
const $segmentMonthValue = segmentValues.get().month; |
|
const $placeholder = placeholder.get(); |
|
const daysInMonth = $segmentMonthValue |
|
? getDaysInMonth($placeholder.set({ month: $segmentMonthValue })) |
|
: getDaysInMonth($placeholder); |
|
if (e.key === kbd.ARROW_UP) { |
|
updateSegment('day', (prev) => { |
|
if (prev === null) { |
|
const next = $placeholder.day; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = $placeholder.set({ day: prev }).cycle('day', 1).day; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_DOWN) { |
|
updateSegment('day', (prev) => { |
|
if (prev === null) { |
|
const next = $placeholder.day; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = $placeholder.set({ day: prev }).cycle('day', -1).day; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
const $fieldId = ids.field.get(); |
|
if (isNumberString(e.key)) { |
|
const num = parseInt(e.key); |
|
let moveToNext = false; |
|
updateSegment('day', (prev) => { |
|
const max = daysInMonth; |
|
const maxStart = Math.floor(max / 10); |
|
|
|
|
|
|
|
|
|
|
|
if (states.day.hasLeftFocus) { |
|
prev = null; |
|
states.day.hasLeftFocus = false; |
|
} |
|
if (prev === null) { |
|
|
|
|
|
|
|
|
|
|
|
if (num === 0) { |
|
states.day.lastKeyZero = true; |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (states.day.lastKeyZero || num > maxStart) { |
|
moveToNext = true; |
|
} |
|
states.day.lastKeyZero = false; |
|
|
|
|
|
|
|
|
|
|
|
return num; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const digits = prev.toString().length; |
|
const total = parseInt(prev.toString() + num.toString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (digits === 2 || total > max) { |
|
|
|
|
|
|
|
|
|
|
|
if (num > maxStart || total > max) { |
|
moveToNext = true; |
|
} |
|
announcer.announce(num); |
|
return num; |
|
} |
|
moveToNext = true; |
|
announcer.announce(total); |
|
return total; |
|
}); |
|
if (moveToNext) { |
|
moveToNextSegment(e, $fieldId); |
|
} |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
const currentTarget = e.currentTarget; |
|
if (!isHTMLElement(currentTarget)) |
|
return; |
|
updateSegment('day', (prev) => { |
|
if (prev === null) |
|
return null; |
|
const str = prev.toString(); |
|
if (str.length === 1) |
|
return null; |
|
return parseInt(str.slice(0, -1)); |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, $fieldId); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function monthSegmentAttrs(props) { |
|
const { segmentValues, placeholder, ids } = props; |
|
const isEmpty = segmentValues.month === null; |
|
const date = segmentValues.month |
|
? placeholder.set({ month: segmentValues.month }) |
|
: placeholder; |
|
const valueNow = date.month; |
|
const valueMin = 1; |
|
const valueMax = 12; |
|
const valueText = isEmpty ? 'Empty' : `${valueNow} - ${formatter.fullMonth(toDate(date))}`; |
|
return { |
|
...defaultSegmentAttrs, |
|
id: ids.month, |
|
'aria-label': 'month, ', |
|
contenteditable: true, |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function monthSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'month')), addMeltEventListener(node, 'focusout', () => (states.month.hasLeftFocus = true)), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleMonthSegmentKeydown(e) { |
|
if (!isAcceptableSegmentKey(e.key)) { |
|
return; |
|
} |
|
const $placeholder = placeholder.get(); |
|
function getMonthAnnouncement(month) { |
|
return `${month} - ${formatter.fullMonth(toDate($placeholder.set({ month })))}`; |
|
} |
|
const max = 12; |
|
states.month.hasTouched = true; |
|
if (e.key === kbd.ARROW_UP) { |
|
updateSegment('month', (prev) => { |
|
if (prev === null) { |
|
const next = $placeholder.month; |
|
announcer.announce(getMonthAnnouncement(next)); |
|
return next; |
|
} |
|
const next = $placeholder.set({ month: prev }).cycle('month', 1); |
|
announcer.announce(getMonthAnnouncement(next.month)); |
|
return next.month; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_DOWN) { |
|
updateSegment('month', (prev) => { |
|
if (prev === null) { |
|
const next = $placeholder.month; |
|
announcer.announce(getMonthAnnouncement(next)); |
|
return next; |
|
} |
|
const next = $placeholder.set({ month: prev }).cycle('month', -1).month; |
|
announcer.announce(getMonthAnnouncement(next)); |
|
return next; |
|
}); |
|
return; |
|
} |
|
const $fieldId = ids.field.get(); |
|
if (isNumberString(e.key)) { |
|
const num = parseInt(e.key); |
|
let moveToNext = false; |
|
updateSegment('month', (prev) => { |
|
const maxStart = Math.floor(max / 10); |
|
|
|
|
|
|
|
|
|
|
|
if (states.month.hasLeftFocus) { |
|
prev = null; |
|
states.month.hasLeftFocus = false; |
|
} |
|
if (prev === null) { |
|
|
|
|
|
|
|
|
|
|
|
if (num === 0) { |
|
states.month.lastKeyZero = true; |
|
announcer.announce(null); |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (states.month.lastKeyZero || num > maxStart) { |
|
moveToNext = true; |
|
} |
|
states.month.lastKeyZero = false; |
|
|
|
|
|
|
|
|
|
|
|
announcer.announce(num); |
|
return num; |
|
} |
|
const digits = prev.toString().length; |
|
const total = parseInt(prev.toString() + num.toString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (digits === 2 || total > max) { |
|
|
|
|
|
|
|
|
|
|
|
if (num > maxStart) { |
|
moveToNext = true; |
|
} |
|
announcer.announce(num); |
|
return num; |
|
} |
|
moveToNext = true; |
|
announcer.announce(total); |
|
return total; |
|
}); |
|
if (moveToNext) { |
|
moveToNextSegment(e, $fieldId); |
|
} |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
states.month.hasLeftFocus = false; |
|
updateSegment('month', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const str = prev.toString(); |
|
if (str.length === 1) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const next = parseInt(str.slice(0, -1)); |
|
announcer.announce(getMonthAnnouncement(next)); |
|
return next; |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, $fieldId); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function yearSegmentAttrs(props) { |
|
const { segmentValues, placeholder, ids } = props; |
|
const isEmpty = segmentValues.year === null; |
|
const date = segmentValues.year ? placeholder.set({ year: segmentValues.year }) : placeholder; |
|
const valueMin = 1; |
|
const valueMax = 9999; |
|
const valueNow = date.year; |
|
const valueText = isEmpty ? 'Empty' : `${valueNow}`; |
|
return { |
|
...defaultSegmentAttrs, |
|
id: ids.year, |
|
'aria-label': 'year, ', |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function yearSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'year')), addMeltEventListener(node, 'focusout', () => (states.year.hasLeftFocus = true)), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleYearSegmentKeydown(e) { |
|
if (!isAcceptableSegmentKey(e.key)) { |
|
return; |
|
} |
|
states.year.hasTouched = true; |
|
const $placeholder = placeholder.get(); |
|
if (e.key === kbd.ARROW_UP) { |
|
updateSegment('year', (prev) => { |
|
if (prev === null) { |
|
const next = $placeholder.year; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = $placeholder.set({ year: prev }).cycle('year', 1).year; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_DOWN) { |
|
updateSegment('year', (prev) => { |
|
if (prev === null) { |
|
const next = $placeholder.year; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = $placeholder.set({ year: prev }).cycle('year', -1).year; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
const $fieldId = ids.field.get(); |
|
if (isNumberString(e.key)) { |
|
let moveToNext = false; |
|
const num = parseInt(e.key); |
|
updateSegment('year', (prev) => { |
|
if (states.year.hasLeftFocus) { |
|
prev = null; |
|
states.year.hasLeftFocus = false; |
|
} |
|
if (prev === null) { |
|
announcer.announce(num); |
|
return num; |
|
} |
|
const str = prev.toString() + num.toString(); |
|
if (str.length > 4) { |
|
announcer.announce(num); |
|
return num; |
|
} |
|
if (str.length === 4) { |
|
moveToNext = true; |
|
} |
|
const int = parseInt(str); |
|
announcer.announce(int); |
|
return int; |
|
}); |
|
if (moveToNext) { |
|
moveToNextSegment(e, $fieldId); |
|
} |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
updateSegment('year', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const str = prev.toString(); |
|
if (str.length === 1) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const next = parseInt(str.slice(0, -1)); |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, $fieldId); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function hourSegmentAttrs(props) { |
|
const { segmentValues, hourCycle, placeholder, ids } = props; |
|
if (!('hour' in segmentValues) || !('hour' in placeholder)) |
|
return {}; |
|
const isEmpty = segmentValues.hour === null; |
|
const date = segmentValues.hour ? placeholder.set({ hour: segmentValues.hour }) : placeholder; |
|
const valueMin = hourCycle === 12 ? 1 : 0; |
|
const valueMax = hourCycle === 12 ? 12 : 23; |
|
const valueNow = date.hour; |
|
const valueText = isEmpty ? 'Empty' : `${valueNow} ${segmentValues.dayPeriod ?? ''}`; |
|
return { |
|
...defaultSegmentAttrs, |
|
id: ids.hour, |
|
'aria-label': 'hour, ', |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function hourSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'hour')), addMeltEventListener(node, 'focusout', () => (states.hour.hasLeftFocus = true)), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleHourSegmentKeydown(e) { |
|
const dateRef = placeholder.get(); |
|
if (!isAcceptableSegmentKey(e.key) || !('hour' in dateRef)) { |
|
return; |
|
} |
|
states.hour.hasTouched = true; |
|
const $hourCycle = hourCycle.get(); |
|
if (e.key === kbd.ARROW_UP) { |
|
updateSegment('hour', (prev) => { |
|
if (prev === null) { |
|
const next = dateRef.cycle('hour', 1, { hourCycle: $hourCycle }).hour; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = dateRef.set({ hour: prev }).cycle('hour', 1, { hourCycle: $hourCycle }).hour; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_DOWN) { |
|
updateSegment('hour', (prev) => { |
|
if (prev === null) { |
|
const next = dateRef.cycle('hour', -1, { hourCycle: $hourCycle }).hour; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = dateRef.set({ hour: prev }).cycle('hour', -1, { hourCycle: $hourCycle }).hour; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
const $fieldId = ids.field.get(); |
|
if (isNumberString(e.key)) { |
|
const num = parseInt(e.key); |
|
let moveToNext = false; |
|
updateSegment('hour', (prev) => { |
|
const maxStart = Math.floor(24 / 10); |
|
|
|
|
|
|
|
|
|
|
|
if (states.hour.hasLeftFocus) { |
|
prev = null; |
|
states.hour.hasLeftFocus = false; |
|
} |
|
if (prev === null) { |
|
|
|
|
|
|
|
|
|
|
|
if (num === 0) { |
|
states.hour.lastKeyZero = true; |
|
announcer.announce(null); |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (states.hour.lastKeyZero || num > maxStart) { |
|
moveToNext = true; |
|
} |
|
states.hour.lastKeyZero = false; |
|
announcer.announce(num); |
|
|
|
|
|
|
|
|
|
|
|
return num; |
|
} |
|
const digits = prev.toString().length; |
|
const total = parseInt(prev.toString() + num.toString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (digits === 2 || total > 24) { |
|
|
|
|
|
|
|
|
|
if (num > maxStart) { |
|
moveToNext = true; |
|
} |
|
announcer.announce(num); |
|
return num; |
|
} |
|
moveToNext = true; |
|
announcer.announce(total); |
|
return total; |
|
}); |
|
if (moveToNext) { |
|
moveToNextSegment(e, $fieldId); |
|
} |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
states.hour.hasLeftFocus = false; |
|
updateSegment('hour', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const str = prev.toString(); |
|
if (str.length === 1) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const next = parseInt(str.slice(0, -1)); |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, $fieldId); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function minuteSegmentAttrs(props) { |
|
const { segmentValues, placeholder, ids } = props; |
|
if (!('minute' in segmentValues) || !('minute' in placeholder)) |
|
return {}; |
|
const isEmpty = segmentValues.minute === null; |
|
const date = segmentValues.minute |
|
? placeholder.set({ minute: segmentValues.minute }) |
|
: placeholder; |
|
const valueNow = date.minute; |
|
const valueMin = 0; |
|
const valueMax = 59; |
|
const valueText = isEmpty ? 'Empty' : `${valueNow}`; |
|
return { |
|
...defaultSegmentAttrs, |
|
id: ids.minute, |
|
'aria-label': 'minute, ', |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function minuteSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'minute')), addMeltEventListener(node, 'focusout', () => (states.minute.hasLeftFocus = true)), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleMinuteSegmentKeydown(e) { |
|
const dateRef = placeholder.get(); |
|
if (!isAcceptableSegmentKey(e.key) || !('minute' in dateRef)) { |
|
return; |
|
} |
|
states.minute.hasTouched = true; |
|
const min = 0; |
|
const max = 59; |
|
if (e.key === kbd.ARROW_UP) { |
|
updateSegment('minute', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(min); |
|
return min; |
|
} |
|
const next = dateRef.set({ minute: prev }).cycle('minute', 1).minute; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_DOWN) { |
|
updateSegment('minute', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(max); |
|
return max; |
|
} |
|
const next = dateRef.set({ minute: prev }).cycle('minute', -1).minute; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
const $fieldId = ids.field.get(); |
|
if (isNumberString(e.key)) { |
|
const num = parseInt(e.key); |
|
let moveToNext = false; |
|
updateSegment('minute', (prev) => { |
|
const maxStart = Math.floor(max / 10); |
|
|
|
|
|
|
|
|
|
|
|
if (states.minute.hasLeftFocus) { |
|
prev = null; |
|
states.minute.hasLeftFocus = false; |
|
} |
|
if (prev === null) { |
|
|
|
|
|
|
|
|
|
|
|
if (num === 0) { |
|
states.minute.lastKeyZero = true; |
|
announcer.announce(null); |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (states.minute.lastKeyZero || num > maxStart) { |
|
moveToNext = true; |
|
} |
|
states.minute.lastKeyZero = false; |
|
announcer.announce(num); |
|
|
|
|
|
|
|
|
|
|
|
return num; |
|
} |
|
const digits = prev.toString().length; |
|
const total = parseInt(prev.toString() + num.toString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (digits === 2 || total > max) { |
|
|
|
|
|
|
|
|
|
if (num > maxStart) { |
|
moveToNext = true; |
|
} |
|
announcer.announce(num); |
|
return num; |
|
} |
|
moveToNext = true; |
|
announcer.announce(total); |
|
return total; |
|
}); |
|
if (moveToNext) { |
|
moveToNextSegment(e, $fieldId); |
|
} |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
states.minute.hasLeftFocus = false; |
|
updateSegment('minute', (prev) => { |
|
if (prev === null) { |
|
announcer.announce('Empty'); |
|
return null; |
|
} |
|
const str = prev.toString(); |
|
if (str.length === 1) { |
|
announcer.announce('Empty'); |
|
return null; |
|
} |
|
const next = parseInt(str.slice(0, -1)); |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, $fieldId); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function secondSegmentAttrs(props) { |
|
const { segmentValues, placeholder, ids } = props; |
|
if (!('second' in segmentValues) || !('second' in placeholder)) |
|
return {}; |
|
const isEmpty = segmentValues.second === null; |
|
const date = segmentValues.second |
|
? placeholder.set({ second: segmentValues.second }) |
|
: placeholder; |
|
const valueNow = date.second; |
|
const valueMin = 0; |
|
const valueMax = 59; |
|
const valueText = isEmpty ? 'Empty' : `${valueNow}`; |
|
return { |
|
...defaultSegmentAttrs, |
|
id: ids.second, |
|
'aria-label': 'second, ', |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function secondSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'second')), addMeltEventListener(node, 'focusout', () => (states.second.hasLeftFocus = true)), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleSecondSegmentKeydown(e) { |
|
const dateRef = placeholder.get(); |
|
if (!isAcceptableSegmentKey(e.key)) { |
|
return; |
|
} |
|
states.second.hasTouched = true; |
|
const min = 0; |
|
const max = 59; |
|
if (!('second' in dateRef)) |
|
return; |
|
if (e.key === kbd.ARROW_UP) { |
|
updateSegment('second', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(min); |
|
return min; |
|
} |
|
const next = dateRef.set({ second: prev }).cycle('second', 1).second; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_DOWN) { |
|
updateSegment('second', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(max); |
|
return max; |
|
} |
|
const next = dateRef.set({ second: prev }).cycle('second', -1).second; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
const $fieldId = ids.field.get(); |
|
if (isNumberString(e.key)) { |
|
const num = parseInt(e.key); |
|
let moveToNext = false; |
|
updateSegment('second', (prev) => { |
|
const maxStart = Math.floor(max / 10); |
|
|
|
|
|
|
|
|
|
|
|
if (states.second.hasLeftFocus) { |
|
prev = null; |
|
states.second.hasLeftFocus = false; |
|
} |
|
if (prev === null) { |
|
|
|
|
|
|
|
|
|
|
|
if (num === 0) { |
|
states.second.lastKeyZero = true; |
|
announcer.announce(null); |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (states.second.lastKeyZero || num > maxStart) { |
|
moveToNext = true; |
|
} |
|
states.second.lastKeyZero = false; |
|
|
|
|
|
|
|
|
|
|
|
announcer.announce(num); |
|
return num; |
|
} |
|
const digits = prev.toString().length; |
|
const total = parseInt(prev.toString() + num.toString()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (digits === 2 || total > max) { |
|
|
|
|
|
|
|
|
|
if (num > maxStart) { |
|
moveToNext = true; |
|
} |
|
announcer.announce(num); |
|
return num; |
|
} |
|
moveToNext = true; |
|
announcer.announce(total); |
|
return total; |
|
}); |
|
if (moveToNext) { |
|
moveToNextSegment(e, $fieldId); |
|
} |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
states.second.hasLeftFocus = false; |
|
updateSegment('second', (prev) => { |
|
if (prev === null) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const str = prev.toString(); |
|
if (str.length === 1) { |
|
announcer.announce(null); |
|
return null; |
|
} |
|
const next = parseInt(str.slice(0, -1)); |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, $fieldId); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function dayPeriodSegmentAttrs(props) { |
|
const { segmentValues, ids } = props; |
|
if (!('dayPeriod' in segmentValues)) |
|
return {}; |
|
const valueMin = 0; |
|
const valueMax = 12; |
|
const valueNow = segmentValues.dayPeriod ?? 0; |
|
const valueText = segmentValues.dayPeriod ?? 'AM'; |
|
return { |
|
...defaultSegmentAttrs, |
|
inputmode: 'text', |
|
id: ids.dayPeriod, |
|
'aria-label': 'AM/PM', |
|
'aria-valuemin': valueMin, |
|
'aria-valuemax': valueMax, |
|
'aria-valuenow': valueNow, |
|
'aria-valuetext': valueText, |
|
}; |
|
} |
|
function dayPeriodSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'dayPeriod')), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
function handleDayPeriodSegmentKeydown(e) { |
|
if (!isAcceptableSegmentKey(e.key) && e.key !== kbd.A && e.key !== kbd.P) { |
|
return; |
|
} |
|
if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN) { |
|
updateSegment('dayPeriod', (prev) => { |
|
if (prev === 'AM') { |
|
const next = 'PM'; |
|
announcer.announce(next); |
|
return next; |
|
} |
|
const next = 'AM'; |
|
announcer.announce(next); |
|
return next; |
|
}); |
|
return; |
|
} |
|
if (e.key === kbd.BACKSPACE) { |
|
states.second.hasLeftFocus = false; |
|
updateSegment('dayPeriod', () => { |
|
const next = 'AM'; |
|
announcer.announce(next); |
|
return 'AM'; |
|
}); |
|
} |
|
if (e.key === 'a') { |
|
updateSegment('dayPeriod', () => { |
|
const next = 'AM'; |
|
announcer.announce(next); |
|
return 'AM'; |
|
}); |
|
} |
|
if (e.key === 'p') { |
|
updateSegment('dayPeriod', () => { |
|
const next = 'PM'; |
|
announcer.announce(next); |
|
return 'PM'; |
|
}); |
|
} |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, ids.field.get()); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function literalSegmentAttrs(_) { |
|
return { |
|
'aria-hidden': true, |
|
'data-segment': 'literal', |
|
}; |
|
} |
|
function literalSegmentAction(_) { |
|
return { |
|
destroy: noop, |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function timeZoneSegmentAttrs(_) { |
|
return { |
|
role: 'textbox', |
|
'aria-label': 'timezone, ', |
|
'data-readonly': true, |
|
'data-segment': 'timeZoneName', |
|
tabindex: 0, |
|
style: styleToString({ |
|
'caret-color': 'transparent', |
|
}), |
|
}; |
|
} |
|
function timeZoneSegmentAction(node) { |
|
const unsubEvents = executeCallbacks(addMeltEventListener(node, 'keydown', (e) => handleSegmentKeydown(e, 'timeZoneName')), addMeltEventListener(node, 'click', handleSegmentClick)); |
|
return { |
|
destroy() { |
|
unsubEvents(); |
|
}, |
|
}; |
|
} |
|
function handleTimeZoneSegmentKeydown(e) { |
|
if (isSegmentNavigationKey(e.key)) { |
|
handleSegmentNavigation(e, ids.field.get()); |
|
} |
|
} |
|
function getSegmentAttrs(part, props) { |
|
return segmentBuilders[part]?.attrs(props); |
|
} |
|
function getSegmentAction(node) { |
|
const part = getPartFromNode(node); |
|
if (!part) { |
|
throw new Error('No segment part found'); |
|
} |
|
return segmentBuilders[part].action(node); |
|
} |
|
|
|
|
|
|
|
function getLabelledBy(part) { |
|
return `${ids[part].get()} ${ids.label.get()}`; |
|
} |
|
|
|
|
|
|
|
|
|
effect(locale, ($locale) => { |
|
if (formatter.getLocale() === $locale) |
|
return; |
|
formatter.setLocale($locale); |
|
}); |
|
effect(value, ($value) => { |
|
if ($value) { |
|
|
|
setDescription(ids.description.get(), formatter, $value); |
|
} |
|
if ($value && placeholder.get() !== $value) { |
|
placeholder.set($value); |
|
} |
|
}); |
|
effect([value, locale], ([$value, _]) => { |
|
|
|
|
|
if ($value) { |
|
syncSegmentValues({ |
|
value: $value, |
|
segmentValues, |
|
formatter, |
|
updatingDayPeriod, |
|
}); |
|
} |
|
else { |
|
segmentValues.set(structuredClone(initialSegments)); |
|
} |
|
}); |
|
const _isDateUnavailable = derived(isDateUnavailable, ($isDateUnavailable) => { |
|
return (date) => $isDateUnavailable?.(date); |
|
}); |
|
return { |
|
elements: { |
|
field, |
|
segment, |
|
label, |
|
hiddenInput, |
|
validation, |
|
}, |
|
states: { |
|
value, |
|
segmentValues, |
|
segmentContents, |
|
segmentContentsObj, |
|
placeholder: placeholder.toWritable(), |
|
isInvalid, |
|
}, |
|
helpers: { |
|
isDateUnavailable: _isDateUnavailable, |
|
}, |
|
options, |
|
ids, |
|
}; |
|
} |
|
|