|
import { createDateField } from '../../index.js'; |
|
import { areAllDaysBetweenValid, dateStore, getAnnouncer, getDefaultDate, getFirstSegment, isBefore, isBeforeOrSame, } from '../../internal/helpers/date/index.js'; |
|
import { addMeltEventListener, makeElement, createElHelpers, effect, executeCallbacks, omit, overridable, sleep, styleToString, toWritableStores, } from '../../internal/helpers/index.js'; |
|
import { withGet } from '../../internal/helpers/withGet.js'; |
|
import { derived, writable } from 'svelte/store'; |
|
import { generateIds } from '../../internal/helpers/id.js'; |
|
import { removeDescriptionElement } from './_internal/helpers.js'; |
|
const defaults = { |
|
isDateUnavailable: undefined, |
|
value: undefined, |
|
hourCycle: undefined, |
|
locale: 'en', |
|
granularity: undefined, |
|
hideTimeZone: false, |
|
defaultValue: { |
|
start: undefined, |
|
end: undefined, |
|
}, |
|
startName: undefined, |
|
endName: undefined, |
|
disabled: false, |
|
readonly: false, |
|
readonlySegments: undefined, |
|
minValue: undefined, |
|
maxValue: undefined, |
|
}; |
|
const { name } = createElHelpers('dateField'); |
|
const rangeFieldIdParts = ['field', 'label', 'description', 'validation']; |
|
export function createDateRangeField(props) { |
|
const withDefaults = { ...defaults, ...props }; |
|
const options = toWritableStores(omit(withDefaults, 'value', 'placeholder')); |
|
const generatedIds = generateIds(rangeFieldIdParts); |
|
const ids = toWritableStores({ ...generatedIds, ...withDefaults.ids }); |
|
const defaultDate = getDefaultDate({ |
|
defaultValue: withDefaults.defaultValue?.start, |
|
defaultPlaceholder: withDefaults.defaultPlaceholder, |
|
granularity: withDefaults.granularity, |
|
}); |
|
const valueWritable = withDefaults.value ?? writable(withDefaults.defaultValue); |
|
const value = overridable(valueWritable, withDefaults.onValueChange); |
|
const startValue = withGet.writable(value.get()?.start ?? withDefaults.defaultValue?.start); |
|
const endValue = withGet.writable(value.get()?.end ?? withDefaults.defaultValue?.end); |
|
const isCompleted = derived(value, ($value) => { |
|
return $value?.start && $value?.end; |
|
}); |
|
const placeholderWritable = withDefaults.placeholder ?? writable(withDefaults.defaultPlaceholder ?? defaultDate); |
|
const placeholder = dateStore(overridable(placeholderWritable, withDefaults.onPlaceholderChange), withDefaults.defaultPlaceholder ?? defaultDate); |
|
const startField = createDateField({ |
|
...omit(withDefaults, 'defaultValue', 'onValueChange', 'startName', 'endName', 'readonlySegments'), |
|
value: startValue, |
|
name: withDefaults.startName, |
|
readonlySegments: withDefaults.readonlySegments?.start, |
|
ids: { |
|
...generatedIds, |
|
...withDefaults.ids, |
|
...withDefaults.startIds, |
|
}, |
|
}); |
|
const endField = createDateField({ |
|
...omit(withDefaults, 'defaultValue', 'onValueChange', 'endName', 'startName', 'readonlySegments'), |
|
value: endValue, |
|
name: withDefaults.endName, |
|
readonlySegments: withDefaults.readonlySegments?.end, |
|
ids: { |
|
...generatedIds, |
|
...withDefaults.ids, |
|
...withDefaults.endIds, |
|
}, |
|
}); |
|
const { elements: { segment: startSegment, hiddenInput: startHiddenInput }, states: { isInvalid: isStartInvalid, segmentContents: startSegmentContents, segmentValues: startSegmentValues, }, options: { name: startName }, } = startField; |
|
const { elements: { segment: endSegment, hiddenInput: endHiddenInput }, states: { isInvalid: isEndInvalid, segmentContents: endSegmentContents, segmentValues: endSegmentValues, }, options: { name: endName }, } = endField; |
|
const isInvalid = derived([value, isStartInvalid, isEndInvalid, options.isDateUnavailable], ([$value, $isStartInvalid, $isEndInvalid, $isDateUnavailable]) => { |
|
if ($isStartInvalid || $isEndInvalid) { |
|
return true; |
|
} |
|
if (!$value?.start || !$value?.end) { |
|
return false; |
|
} |
|
if (!isBeforeOrSame($value?.start, $value?.end)) { |
|
return true; |
|
} |
|
if ($isDateUnavailable !== undefined) { |
|
const allValid = areAllDaysBetweenValid($value?.start, $value?.end, $isDateUnavailable, undefined); |
|
if (!allValid) { |
|
return true; |
|
} |
|
} |
|
return false; |
|
}); |
|
const label = makeElement(name('label'), { |
|
stores: [isInvalid, options.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 fieldIdDeps = derived([ids.field, ids.label, ids.description, ids.validation], ([$fieldId, $labelId, $descriptionId, $validationId]) => { |
|
return { |
|
field: $fieldId, |
|
label: $labelId, |
|
description: $descriptionId, |
|
validation: $validationId, |
|
}; |
|
}); |
|
const field = makeElement(name('field'), { |
|
stores: [isCompleted, isInvalid, fieldIdDeps], |
|
returned: ([$isCompleted, $isInvalid, $ids]) => { |
|
const describedBy = $isCompleted |
|
? `${$ids.description}${$isInvalid ? ` ${$ids.validation}` : ''}` |
|
: `${$ids.description}`; |
|
return { |
|
role: 'group', |
|
id: $ids.field, |
|
'aria-labelledby': $ids.label, |
|
'aria-describedby': describedBy, |
|
'data-invalid': $isInvalid ? '' : undefined, |
|
}; |
|
}, |
|
action: () => { |
|
getAnnouncer(); |
|
return { |
|
destroy() { |
|
removeDescriptionElement(ids.description.get()); |
|
}, |
|
}; |
|
}, |
|
}); |
|
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 segmentContents = derived([startSegmentContents, endSegmentContents], ([$startSegmentContents, $endSegmentContents]) => { |
|
return { |
|
start: $startSegmentContents, |
|
end: $endSegmentContents, |
|
}; |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
effect([value], ([$value]) => { |
|
const $startValue = startValue.get(); |
|
const $endValue = endValue.get(); |
|
if ($value?.start && $value?.end) { |
|
if ($value.start !== $startValue) { |
|
startValue.set($value.start); |
|
} |
|
if ($value.end !== $endValue) { |
|
endValue.set($value.end); |
|
} |
|
return; |
|
} |
|
}); |
|
effect([startValue, endValue], ([$startValue, $endValue]) => { |
|
const $value = value.get(); |
|
if ($value && $value?.start === $startValue && $value?.end === $endValue) |
|
return; |
|
if ($startValue && $endValue) { |
|
value.update((prev) => { |
|
if (prev?.start === $startValue && prev?.end === $endValue) { |
|
return prev; |
|
} |
|
return { |
|
start: $startValue, |
|
end: $endValue, |
|
}; |
|
}); |
|
} |
|
else if ($value && $value?.start && $value?.end) { |
|
value.set({ |
|
start: undefined, |
|
end: undefined, |
|
}); |
|
} |
|
}); |
|
effect([options.disabled], ([$disabled]) => { |
|
startField.options.disabled.set($disabled); |
|
endField.options.disabled.set($disabled); |
|
}); |
|
effect([options.readonly], ([$readonly]) => { |
|
startField.options.readonly.set($readonly); |
|
endField.options.readonly.set($readonly); |
|
}); |
|
effect([options.readonlySegments], ([$readonlySegments]) => { |
|
startField.options.readonlySegments.set($readonlySegments?.start); |
|
endField.options.readonlySegments.set($readonlySegments?.end); |
|
}); |
|
effect([options.minValue], ([$minValue]) => { |
|
startField.options.minValue.set($minValue); |
|
endField.options.minValue.set($minValue); |
|
}); |
|
effect([options.maxValue], ([$maxValue]) => { |
|
startField.options.maxValue.set($maxValue); |
|
endField.options.maxValue.set($maxValue); |
|
}); |
|
effect([options.granularity], ([$granularity]) => { |
|
startField.options.granularity.set($granularity); |
|
endField.options.granularity.set($granularity); |
|
}); |
|
effect([options.hideTimeZone], ([$hideTimeZone]) => { |
|
startField.options.hideTimeZone.set($hideTimeZone); |
|
endField.options.hideTimeZone.set($hideTimeZone); |
|
}); |
|
effect([options.hourCycle], ([$hourCycle]) => { |
|
startField.options.hourCycle.set($hourCycle); |
|
endField.options.hourCycle.set($hourCycle); |
|
}); |
|
effect([options.locale], ([$locale]) => { |
|
startField.options.locale.set($locale); |
|
endField.options.locale.set($locale); |
|
}); |
|
return { |
|
elements: { |
|
field, |
|
label, |
|
startSegment, |
|
endSegment, |
|
startHiddenInput, |
|
endHiddenInput, |
|
validation, |
|
}, |
|
states: { |
|
value, |
|
placeholder: placeholder.toWritable(), |
|
segmentContents, |
|
endSegmentValues, |
|
startSegmentValues, |
|
isInvalid, |
|
}, |
|
options: { |
|
...options, |
|
endName, |
|
startName, |
|
}, |
|
ids: { |
|
field: ids, |
|
start: startField.ids, |
|
end: endField.ids, |
|
}, |
|
}; |
|
} |
|
|