This commit is contained in:
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { boxWith, mergeProps } from "svelte-toolbelt";
|
||||
import type { TimeRangeFieldInputProps } from "../types.js";
|
||||
import { TimeRangeFieldInputState } from "../time-range-field.svelte.js";
|
||||
import { createId } from "../../../internal/create-id.js";
|
||||
import TimeFieldHiddenInput from "../../time-field/components/time-field-hidden-input.svelte";
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
id = createId(uid),
|
||||
ref = $bindable(null),
|
||||
name = "",
|
||||
child,
|
||||
children,
|
||||
type,
|
||||
...restProps
|
||||
}: TimeRangeFieldInputProps = $props();
|
||||
|
||||
const inputState = TimeRangeFieldInputState.create(
|
||||
{
|
||||
id: boxWith(() => id),
|
||||
ref: boxWith(
|
||||
() => ref,
|
||||
(v) => (ref = v)
|
||||
),
|
||||
name: boxWith(() => name),
|
||||
},
|
||||
type
|
||||
);
|
||||
|
||||
const mergedProps = $derived(mergeProps(restProps, inputState.props, { role: "presentation" }));
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ props: mergedProps, segments: inputState.root.segmentContents })}
|
||||
{:else}
|
||||
<div {...mergedProps}>
|
||||
{@render children?.({ segments: inputState.root.segmentContents })}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<TimeFieldHiddenInput />
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { TimeRangeFieldInputProps } from "../types.js";
|
||||
declare const TimeRangeFieldInput: import("svelte").Component<TimeRangeFieldInputProps, {}, "ref">;
|
||||
type TimeRangeFieldInput = ReturnType<typeof TimeRangeFieldInput>;
|
||||
export default TimeRangeFieldInput;
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { boxWith, mergeProps } from "svelte-toolbelt";
|
||||
import { TimeRangeFieldLabelState } from "../time-range-field.svelte.js";
|
||||
import type { TimeRangeFieldLabelProps } from "../types.js";
|
||||
import { createId } from "../../../internal/create-id.js";
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
id = createId(uid),
|
||||
ref = $bindable(null),
|
||||
children,
|
||||
child,
|
||||
...restProps
|
||||
}: TimeRangeFieldLabelProps = $props();
|
||||
|
||||
const labelState = TimeRangeFieldLabelState.create({
|
||||
id: boxWith(() => id),
|
||||
ref: boxWith(
|
||||
() => ref,
|
||||
(v) => (ref = v)
|
||||
),
|
||||
});
|
||||
|
||||
const mergedProps = $derived(mergeProps(restProps, labelState.props));
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ props: mergedProps })}
|
||||
{:else}
|
||||
<span {...mergedProps}>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
{/if}
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import type { TimeRangeFieldLabelProps } from "../types.js";
|
||||
declare const TimeRangeFieldLabel: import("svelte").Component<TimeRangeFieldLabelProps, {}, "ref">;
|
||||
type TimeRangeFieldLabel = ReturnType<typeof TimeRangeFieldLabel>;
|
||||
export default TimeRangeFieldLabel;
|
||||
Generated
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
<script lang="ts" module>
|
||||
import type { TimeRange, TimeValue } from "../../../shared/date/types.js";
|
||||
import type { Time } from "@internationalized/date";
|
||||
|
||||
type T = unknown;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends TimeValue = Time">
|
||||
import { watch } from "runed";
|
||||
import { boxWith, mergeProps } from "svelte-toolbelt";
|
||||
import { TimeRangeFieldRootState } from "../time-range-field.svelte.js";
|
||||
import type { TimeRangeFieldRootProps } from "../types.js";
|
||||
import { createId } from "../../../internal/create-id.js";
|
||||
import { noop } from "../../../internal/noop.js";
|
||||
import { getDefaultTime } from "../../../internal/date-time/utils.js";
|
||||
import { resolveLocaleProp } from "../../utilities/config/prop-resolvers.js";
|
||||
|
||||
const uid = $props.id();
|
||||
|
||||
let {
|
||||
id = createId(uid),
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
onValueChange = noop,
|
||||
placeholder = $bindable(),
|
||||
onPlaceholderChange = noop,
|
||||
disabled = false,
|
||||
readonly = false,
|
||||
required = false,
|
||||
hourCycle,
|
||||
granularity,
|
||||
locale,
|
||||
hideTimeZone = false,
|
||||
validate = noop,
|
||||
onInvalid = noop,
|
||||
maxValue,
|
||||
minValue,
|
||||
readonlySegments = [],
|
||||
children,
|
||||
child,
|
||||
onStartValueChange = noop,
|
||||
onEndValueChange = noop,
|
||||
errorMessageId,
|
||||
...restProps
|
||||
}: TimeRangeFieldRootProps<T> = $props();
|
||||
|
||||
let startValue = $state<T | undefined>(value?.start);
|
||||
let endValue = $state<T | undefined>(value?.end);
|
||||
|
||||
function handleDefaultPlaceholder() {
|
||||
if (placeholder !== undefined) return;
|
||||
const defaultPlaceholder = getDefaultTime({ granularity, defaultValue: value?.start });
|
||||
placeholder = defaultPlaceholder;
|
||||
}
|
||||
|
||||
// SSR
|
||||
handleDefaultPlaceholder();
|
||||
|
||||
watch.pre(
|
||||
() => placeholder,
|
||||
() => {
|
||||
handleDefaultPlaceholder();
|
||||
}
|
||||
);
|
||||
|
||||
function handleDefaultValue() {
|
||||
if (value !== undefined) return;
|
||||
const defaultValue = { start: undefined, end: undefined };
|
||||
value = defaultValue;
|
||||
}
|
||||
|
||||
// SSR
|
||||
handleDefaultValue();
|
||||
|
||||
/**
|
||||
* Covers an edge case where when a spread props object is reassigned,
|
||||
* the props are reset to their default values, which would make value
|
||||
* undefined which causes errors to be thrown.
|
||||
*/
|
||||
watch.pre(
|
||||
() => value,
|
||||
() => {
|
||||
handleDefaultValue();
|
||||
}
|
||||
);
|
||||
|
||||
const rootState = TimeRangeFieldRootState.create({
|
||||
id: boxWith(() => id),
|
||||
ref: boxWith(
|
||||
() => ref,
|
||||
(v) => (ref = v)
|
||||
),
|
||||
disabled: boxWith(() => disabled),
|
||||
readonly: boxWith(() => readonly),
|
||||
required: boxWith(() => required),
|
||||
hourCycle: boxWith(() => hourCycle),
|
||||
granularity: boxWith(() => granularity),
|
||||
locale: resolveLocaleProp(() => locale),
|
||||
hideTimeZone: boxWith(() => hideTimeZone),
|
||||
validate: boxWith(() => validate),
|
||||
maxValue: boxWith(() => maxValue),
|
||||
minValue: boxWith(() => minValue),
|
||||
placeholder: boxWith(
|
||||
() => placeholder as TimeValue,
|
||||
(v) => {
|
||||
placeholder = v;
|
||||
onPlaceholderChange(v);
|
||||
}
|
||||
),
|
||||
readonlySegments: boxWith(() => readonlySegments),
|
||||
value: boxWith(
|
||||
() => value as TimeRange<T>,
|
||||
(v) => {
|
||||
value = v;
|
||||
onValueChange(v);
|
||||
}
|
||||
),
|
||||
startValue: boxWith(
|
||||
() => startValue,
|
||||
(v) => {
|
||||
startValue = v;
|
||||
onStartValueChange(v);
|
||||
}
|
||||
),
|
||||
endValue: boxWith(
|
||||
() => endValue,
|
||||
(v) => {
|
||||
endValue = v;
|
||||
onEndValueChange(v);
|
||||
}
|
||||
),
|
||||
onInvalid: boxWith(() => onInvalid),
|
||||
errorMessageId: boxWith(() => errorMessageId),
|
||||
});
|
||||
|
||||
const mergedProps = $derived(mergeProps(restProps, rootState.props));
|
||||
</script>
|
||||
|
||||
{#if child}
|
||||
{@render child({ props: mergedProps })}
|
||||
{:else}
|
||||
<div {...mergedProps}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
{/if}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
import type { TimeValue } from "../../../shared/date/types.js";
|
||||
import type { Time } from "@internationalized/date";
|
||||
import type { TimeRangeFieldRootProps } from "../types.js";
|
||||
declare class __sveltets_Render<T extends TimeValue = Time> {
|
||||
props(): TimeRangeFieldRootProps<T>;
|
||||
events(): {};
|
||||
slots(): {};
|
||||
bindings(): "value" | "placeholder" | "ref";
|
||||
exports(): {};
|
||||
}
|
||||
interface $$IsomorphicComponent {
|
||||
new <T extends TimeValue = Time>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
||||
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
||||
} & ReturnType<__sveltets_Render<T>['exports']>;
|
||||
<T extends TimeValue = Time>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
||||
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
||||
}
|
||||
declare const TimeRangeField: $$IsomorphicComponent;
|
||||
type TimeRangeField<T extends TimeValue = Time> = InstanceType<typeof TimeRangeField<T>>;
|
||||
export default TimeRangeField;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export { default as Root } from "./components/time-range-field.svelte";
|
||||
export { default as Input } from "./components/time-range-field-input.svelte";
|
||||
export { default as Label } from "./components/time-range-field-label.svelte";
|
||||
export { default as Segment } from "../time-field/components/time-field-segment.svelte";
|
||||
export type { TimeRangeFieldRootProps as RootProps, TimeRangeFieldLabelProps as LabelProps, TimeRangeFieldInputProps as InputProps, TimeRangeFieldSegmentProps as SegmentProps, } from "./types.js";
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export { default as Root } from "./components/time-range-field.svelte";
|
||||
export { default as Input } from "./components/time-range-field-input.svelte";
|
||||
export { default as Label } from "./components/time-range-field-label.svelte";
|
||||
export { default as Segment } from "../time-field/components/time-field-segment.svelte";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * as TimeRangeField from "./exports.js";
|
||||
+1
@@ -0,0 +1 @@
|
||||
export * as TimeRangeField from "./exports.js";
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import type { Time } from "@internationalized/date";
|
||||
import { DOMContext, type ReadableBoxedValues, type WritableBoxedValues } from "svelte-toolbelt";
|
||||
import { Context } from "runed";
|
||||
import { TimeFieldRootState } from "../time-field/time-field.svelte.js";
|
||||
import { TimeFieldInputState } from "../time-field/time-field.svelte.js";
|
||||
import type { TimeSegmentPart } from "../../shared/index.js";
|
||||
import type { RefAttachment, WithRefOpts } from "../../internal/types.js";
|
||||
import type { TimeGranularity, TimeOnInvalid, TimeRange, TimeRangeValidator, TimeValue } from "../../shared/date/types.js";
|
||||
import { type TimeFormatter } from "../../internal/date-time/formatter.js";
|
||||
export declare const timeRangeFieldAttrs: import("../../internal/attrs.js").CreateBitsAttrsReturn<readonly ["root", "label"]>;
|
||||
export declare const TimeRangeFieldRootContext: Context<TimeRangeFieldRootState<Time>>;
|
||||
interface TimeRangeFieldRootStateOpts<T extends TimeValue = Time> extends WithRefOpts, WritableBoxedValues<{
|
||||
value: TimeRange<T>;
|
||||
placeholder: TimeValue;
|
||||
startValue: T | undefined;
|
||||
endValue: T | undefined;
|
||||
}>, ReadableBoxedValues<{
|
||||
readonlySegments: TimeSegmentPart[];
|
||||
validate: TimeRangeValidator<T> | undefined;
|
||||
onInvalid: TimeOnInvalid | undefined;
|
||||
minValue: TimeValue | undefined;
|
||||
maxValue: TimeValue | undefined;
|
||||
disabled: boolean;
|
||||
readonly: boolean;
|
||||
granularity: TimeGranularity | undefined;
|
||||
hourCycle: 12 | 24 | undefined;
|
||||
locale: string;
|
||||
hideTimeZone: boolean;
|
||||
required: boolean;
|
||||
errorMessageId: string | undefined;
|
||||
}> {
|
||||
}
|
||||
export declare class TimeRangeFieldRootState<T extends TimeValue = Time> {
|
||||
#private;
|
||||
static create<T extends TimeValue = Time>(opts: TimeRangeFieldRootStateOpts<T>): TimeRangeFieldRootState<Time>;
|
||||
readonly opts: TimeRangeFieldRootStateOpts<T>;
|
||||
readonly attachment: RefAttachment;
|
||||
startFieldState: TimeFieldRootState | undefined;
|
||||
endFieldState: TimeFieldRootState | undefined;
|
||||
descriptionId: string;
|
||||
formatter: TimeFormatter;
|
||||
fieldNode: HTMLElement | null;
|
||||
labelNode: HTMLElement | null;
|
||||
descriptionNode: HTMLElement | null;
|
||||
readonly startValueComplete: boolean;
|
||||
readonly endValueComplete: boolean;
|
||||
readonly rangeComplete: boolean;
|
||||
readonly startValueTime: Time | undefined;
|
||||
readonly endValueTime: Time | undefined;
|
||||
readonly minValueTime: Time | undefined;
|
||||
readonly maxValueTime: Time | undefined;
|
||||
domContext: DOMContext;
|
||||
constructor(opts: TimeRangeFieldRootStateOpts<T>);
|
||||
readonly validationStatus: false | {
|
||||
readonly reason: "custom";
|
||||
readonly message: string | string[];
|
||||
} | {
|
||||
readonly reason: "min";
|
||||
readonly message?: undefined;
|
||||
} | {
|
||||
readonly reason: "max";
|
||||
readonly message?: undefined;
|
||||
};
|
||||
readonly isInvalid: boolean;
|
||||
props: {
|
||||
readonly id: string;
|
||||
readonly role: "group";
|
||||
readonly "data-invalid": "" | undefined;
|
||||
};
|
||||
}
|
||||
interface TimeRangeFieldLabelStateOpts extends WithRefOpts {
|
||||
}
|
||||
export declare class TimeRangeFieldLabelState {
|
||||
#private;
|
||||
static create(opts: TimeRangeFieldLabelStateOpts): TimeRangeFieldLabelState;
|
||||
readonly opts: TimeRangeFieldLabelStateOpts;
|
||||
readonly root: TimeRangeFieldRootState;
|
||||
readonly attachment: RefAttachment;
|
||||
constructor(opts: TimeRangeFieldLabelStateOpts, root: TimeRangeFieldRootState);
|
||||
readonly props: {
|
||||
readonly id: string;
|
||||
readonly "data-invalid": "" | undefined;
|
||||
readonly "data-disabled": "" | undefined;
|
||||
readonly onclick: () => void;
|
||||
};
|
||||
}
|
||||
interface TimeRangeFieldInputStateOpts<T extends TimeValue = Time> extends WritableBoxedValues<{
|
||||
value: T | undefined;
|
||||
}>, ReadableBoxedValues<{
|
||||
name: string;
|
||||
}>, WithRefOpts {
|
||||
}
|
||||
export declare class TimeRangeFieldInputState {
|
||||
static create(opts: Omit<TimeRangeFieldInputStateOpts, "value">, type: "start" | "end"): TimeFieldInputState;
|
||||
}
|
||||
export {};
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
import { boxWith, onDestroyEffect, attachRef, DOMContext, } from "svelte-toolbelt";
|
||||
import { Context, watch } from "runed";
|
||||
import { TimeFieldRootState } from "../time-field/time-field.svelte.js";
|
||||
import { TimeFieldInputState } from "../time-field/time-field.svelte.js";
|
||||
import { useId } from "../../internal/use-id.js";
|
||||
import { createBitsAttrs, boolToEmptyStrOrUndef } from "../../internal/attrs.js";
|
||||
import { createTimeFormatter } from "../../internal/date-time/formatter.js";
|
||||
import { removeDescriptionElement } from "../../internal/date-time/field/helpers.js";
|
||||
import { getFirstSegment } from "../../internal/date-time/field/segments.js";
|
||||
import { convertTimeValueToTime, isTimeBefore, } from "../../internal/date-time/field/time-helpers.js";
|
||||
export const timeRangeFieldAttrs = createBitsAttrs({
|
||||
component: "time-range-field",
|
||||
parts: ["root", "label"],
|
||||
});
|
||||
export const TimeRangeFieldRootContext = new Context("TimeRangeField.Root");
|
||||
export class TimeRangeFieldRootState {
|
||||
static create(opts) {
|
||||
return TimeRangeFieldRootContext.set(new TimeRangeFieldRootState(opts));
|
||||
}
|
||||
opts;
|
||||
attachment;
|
||||
startFieldState = undefined;
|
||||
endFieldState = undefined;
|
||||
descriptionId = useId();
|
||||
formatter;
|
||||
fieldNode = $state(null);
|
||||
labelNode = $state(null);
|
||||
descriptionNode = $state(null);
|
||||
startValueComplete = $derived.by(() => this.opts.startValue.current !== undefined);
|
||||
endValueComplete = $derived.by(() => this.opts.endValue.current !== undefined);
|
||||
rangeComplete = $derived(this.startValueComplete && this.endValueComplete);
|
||||
startValueTime = $derived.by(() => {
|
||||
if (!this.opts.startValue.current)
|
||||
return undefined;
|
||||
return convertTimeValueToTime(this.opts.startValue.current);
|
||||
});
|
||||
endValueTime = $derived.by(() => {
|
||||
if (!this.opts.endValue.current)
|
||||
return undefined;
|
||||
return convertTimeValueToTime(this.opts.endValue.current);
|
||||
});
|
||||
minValueTime = $derived.by(() => {
|
||||
if (!this.opts.minValue.current)
|
||||
return undefined;
|
||||
return convertTimeValueToTime(this.opts.minValue.current);
|
||||
});
|
||||
maxValueTime = $derived.by(() => {
|
||||
if (!this.opts.maxValue.current)
|
||||
return undefined;
|
||||
return convertTimeValueToTime(this.opts.maxValue.current);
|
||||
});
|
||||
domContext;
|
||||
constructor(opts) {
|
||||
this.opts = opts;
|
||||
this.formatter = createTimeFormatter(this.opts.locale.current);
|
||||
this.domContext = new DOMContext(this.opts.ref);
|
||||
this.attachment = attachRef(this.opts.ref, (v) => (this.fieldNode = v));
|
||||
onDestroyEffect(() => {
|
||||
removeDescriptionElement(this.descriptionId, this.domContext.getDocument());
|
||||
});
|
||||
$effect(() => {
|
||||
if (this.formatter.getLocale() === this.opts.locale.current)
|
||||
return;
|
||||
this.formatter.setLocale(this.opts.locale.current);
|
||||
});
|
||||
/**
|
||||
* Synchronize the start and end values with the `value` in case
|
||||
* it is updated externally.
|
||||
*/
|
||||
watch(() => this.opts.value.current, (value) => {
|
||||
if (value.start && value.end) {
|
||||
this.opts.startValue.current = value.start;
|
||||
this.opts.endValue.current = value.end;
|
||||
}
|
||||
else if (value.start) {
|
||||
this.opts.startValue.current = value.start;
|
||||
this.opts.endValue.current = undefined;
|
||||
}
|
||||
else if (value.start === undefined && value.end === undefined) {
|
||||
this.opts.startValue.current = undefined;
|
||||
this.opts.endValue.current = undefined;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Synchronize the placeholder value with the current start value
|
||||
*/
|
||||
watch(() => this.opts.value.current, (value) => {
|
||||
const startValue = value.start;
|
||||
if (startValue && this.opts.placeholder.current !== startValue) {
|
||||
this.opts.placeholder.current = startValue;
|
||||
}
|
||||
});
|
||||
watch([() => this.opts.startValue.current, () => this.opts.endValue.current], ([startValue, endValue]) => {
|
||||
if (this.opts.value.current &&
|
||||
this.opts.value.current.start === startValue &&
|
||||
this.opts.value.current.end === endValue) {
|
||||
return;
|
||||
}
|
||||
if (startValue && endValue) {
|
||||
this.#updateValue((prev) => {
|
||||
if (prev.start === startValue && prev.end === endValue) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
start: startValue,
|
||||
end: endValue,
|
||||
};
|
||||
});
|
||||
}
|
||||
else if (this.opts.value.current &&
|
||||
this.opts.value.current.start &&
|
||||
this.opts.value.current.end) {
|
||||
this.opts.value.current.start = undefined;
|
||||
this.opts.value.current.end = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
validationStatus = $derived.by(() => {
|
||||
const value = this.opts.value.current;
|
||||
if (value === undefined)
|
||||
return false;
|
||||
if (value.start === undefined || value.end === undefined)
|
||||
return false;
|
||||
const msg = this.opts.validate.current?.({
|
||||
start: value.start,
|
||||
end: value.end,
|
||||
});
|
||||
if (msg) {
|
||||
return {
|
||||
reason: "custom",
|
||||
message: msg,
|
||||
};
|
||||
}
|
||||
if (this.minValueTime &&
|
||||
this.startValueTime &&
|
||||
isTimeBefore(this.startValueTime, this.minValueTime)) {
|
||||
return {
|
||||
reason: "min",
|
||||
};
|
||||
}
|
||||
if (this.maxValueTime &&
|
||||
this.endValueTime &&
|
||||
isTimeBefore(this.maxValueTime, this.endValueTime)) {
|
||||
return {
|
||||
reason: "max",
|
||||
};
|
||||
}
|
||||
return false;
|
||||
});
|
||||
isInvalid = $derived.by(() => {
|
||||
if (this.validationStatus === false)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
#updateValue(cb) {
|
||||
const value = this.opts.value.current;
|
||||
const newValue = cb(value);
|
||||
this.opts.value.current = newValue;
|
||||
}
|
||||
props = $derived.by(() => ({
|
||||
id: this.opts.id.current,
|
||||
role: "group",
|
||||
[timeRangeFieldAttrs.root]: "",
|
||||
"data-invalid": boolToEmptyStrOrUndef(this.isInvalid),
|
||||
...this.attachment,
|
||||
}));
|
||||
}
|
||||
export class TimeRangeFieldLabelState {
|
||||
static create(opts) {
|
||||
return new TimeRangeFieldLabelState(opts, TimeRangeFieldRootContext.get());
|
||||
}
|
||||
opts;
|
||||
root;
|
||||
attachment;
|
||||
constructor(opts, root) {
|
||||
this.opts = opts;
|
||||
this.root = root;
|
||||
this.attachment = attachRef(this.opts.ref, (v) => (this.root.labelNode = v));
|
||||
}
|
||||
#onclick = () => {
|
||||
if (this.root.opts.disabled.current)
|
||||
return;
|
||||
const firstSegment = getFirstSegment(this.root.fieldNode);
|
||||
if (!firstSegment)
|
||||
return;
|
||||
firstSegment.focus();
|
||||
};
|
||||
props = $derived.by(() => ({
|
||||
id: this.opts.id.current,
|
||||
"data-invalid": boolToEmptyStrOrUndef(this.root.isInvalid),
|
||||
"data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),
|
||||
[timeRangeFieldAttrs.label]: "",
|
||||
onclick: this.#onclick,
|
||||
...this.attachment,
|
||||
}));
|
||||
}
|
||||
export class TimeRangeFieldInputState {
|
||||
static create(opts, type) {
|
||||
const root = TimeRangeFieldRootContext.get();
|
||||
const fieldState = TimeFieldRootState.create({
|
||||
value: type === "start" ? root.opts.startValue : root.opts.endValue,
|
||||
disabled: root.opts.disabled,
|
||||
readonly: root.opts.readonly,
|
||||
readonlySegments: root.opts.readonlySegments,
|
||||
validate: boxWith(() => undefined),
|
||||
minValue: root.opts.minValue,
|
||||
maxValue: root.opts.maxValue,
|
||||
hourCycle: root.opts.hourCycle,
|
||||
locale: root.opts.locale,
|
||||
hideTimeZone: root.opts.hideTimeZone,
|
||||
required: root.opts.required,
|
||||
granularity: root.opts.granularity,
|
||||
placeholder: root.opts.placeholder,
|
||||
onInvalid: root.opts.onInvalid,
|
||||
errorMessageId: root.opts.errorMessageId,
|
||||
isInvalidProp: boxWith(() => root.isInvalid),
|
||||
}, root);
|
||||
return new TimeFieldInputState({ name: opts.name, id: opts.id, ref: opts.ref }, fieldState);
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
import type { Time } from "@internationalized/date";
|
||||
import type { OnChangeFn, WithChild, Without } from "../../internal/types.js";
|
||||
import type { BitsPrimitiveDivAttributes, BitsPrimitiveSpanAttributes } from "../../shared/attributes.js";
|
||||
import type { TimeSegmentPart } from "../../shared/index.js";
|
||||
import type { TimeFieldSegmentProps, TimeFieldSegmentPropsWithoutHTML } from "../time-field/types.js";
|
||||
import type { EditableTimeSegmentPart, TimeGranularity, TimeOnInvalid, TimeRange, TimeRangeValidator, TimeValue } from "../../shared/date/types.js";
|
||||
export type TimeRangeFieldRootPropsWithoutHTML<T extends TimeValue = Time> = WithChild<{
|
||||
/**
|
||||
* The value of the date range field.
|
||||
*
|
||||
* @bindable
|
||||
*/
|
||||
value?: TimeRange<T>;
|
||||
/**
|
||||
* A callback that is called when the value of the date range field changes.
|
||||
*/
|
||||
onValueChange?: OnChangeFn<TimeRange<T> | undefined>;
|
||||
/**
|
||||
* The placeholder value of the time field. This determines the format
|
||||
* and what date the field starts at when it is empty.
|
||||
*
|
||||
* @bindable
|
||||
*/
|
||||
placeholder?: TimeValue;
|
||||
/**
|
||||
* A callback that is called when the time field's placeholder value changes.
|
||||
*/
|
||||
onPlaceholderChange?: OnChangeFn<TimeValue | undefined>;
|
||||
/**
|
||||
* A function that returns a string or array of strings as validation errors if the date is
|
||||
* invalid, or nothing if the date is valid
|
||||
*/
|
||||
validate?: TimeRangeValidator<T>;
|
||||
/**
|
||||
* A callback fired when the time field's value is invalid. Use this to display an error
|
||||
* message to the user.
|
||||
*/
|
||||
onInvalid?: TimeOnInvalid;
|
||||
/**
|
||||
* The minimum acceptable date. When provided, the time field
|
||||
* will be marked as invalid if the user enters a date before this date.
|
||||
*/
|
||||
minValue?: TimeValue;
|
||||
/**
|
||||
* The maximum acceptable date. When provided, the time field
|
||||
* will be marked as invalid if the user enters a date after this date.
|
||||
*/
|
||||
maxValue?: TimeValue;
|
||||
/**
|
||||
* If true, the time field will be disabled and users will not be able
|
||||
* to interact with it. This also disables the hidden input element if
|
||||
* the time field is used in a form.
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* If true, the time field will be readonly, and users will not be able to
|
||||
* edit the values of any of the individual segments.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
readonly?: boolean;
|
||||
/**
|
||||
* If true, the time field will be required, which is useful when used within
|
||||
* a form. If the time field is empty when the form is submitted, the form
|
||||
* will not be valid.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
required?: boolean;
|
||||
/**
|
||||
* An array of segment names that should be readonly. If provided, only the
|
||||
* segments not in this array will be editable.
|
||||
*/
|
||||
readonlySegments?: EditableTimeSegmentPart[];
|
||||
/**
|
||||
* The format to use for displaying the time in the input.
|
||||
* If using a 12 hour clock, ensure you also include the `dayPeriod`
|
||||
* segment in your input to ensure the user can select AM/PM.
|
||||
*
|
||||
* @default - the locale's default time format
|
||||
*/
|
||||
hourCycle?: 12 | 24;
|
||||
/**
|
||||
* The locale to use for formatting the time field.
|
||||
*
|
||||
* @default 'en'
|
||||
*/
|
||||
locale?: string;
|
||||
/**
|
||||
* The granularity of the time field. This determines which
|
||||
* segments will be includes in the segments array used to
|
||||
* build the time field.
|
||||
*
|
||||
* Granularity is only used for visual purposes, and does not impact
|
||||
* the value of the time field. You can have the same value synced
|
||||
* between multiple time fields with different granularities and they
|
||||
* will all contain the same value.
|
||||
*
|
||||
* @default 'minute'
|
||||
*/
|
||||
granularity?: TimeGranularity;
|
||||
/**
|
||||
* Whether or not to hide the timeZoneName segment from the time field.
|
||||
*
|
||||
* @defaultValue false;
|
||||
*/
|
||||
hideTimeZone?: boolean;
|
||||
/**
|
||||
* A callback function called when the start value changes. This doesn't necessarily mean
|
||||
* the `value` has updated and should be used to apply cosmetic changes to the field when
|
||||
* only part of the value is changed/completed.
|
||||
*/
|
||||
onStartValueChange?: OnChangeFn<T | undefined>;
|
||||
/**
|
||||
* A callback function called when the end value changes. This doesn't necessarily mean
|
||||
* the `value` has updated and should be used to apply cosmetic changes to the field when
|
||||
* only part of the value is changed/completed.
|
||||
*/
|
||||
onEndValueChange?: OnChangeFn<T | undefined>;
|
||||
/**
|
||||
* The `id` of the element which contains the error messages for the time field when the
|
||||
* time is invalid.
|
||||
*/
|
||||
errorMessageId?: string;
|
||||
}>;
|
||||
export type TimeRangeFieldRootProps<T extends TimeValue = Time> = TimeRangeFieldRootPropsWithoutHTML<T> & Without<BitsPrimitiveDivAttributes, TimeRangeFieldRootPropsWithoutHTML<T>>;
|
||||
export type TimeRangeFieldLabelPropsWithoutHTML = WithChild;
|
||||
export type TimeRangeFieldLabelProps = TimeRangeFieldLabelPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, TimeRangeFieldLabelPropsWithoutHTML>;
|
||||
export type TimeRangeFieldInputSnippetProps = {
|
||||
segments: Array<{
|
||||
part: TimeSegmentPart;
|
||||
value: string;
|
||||
}>;
|
||||
};
|
||||
export type TimeRangeFieldInputPropsWithoutHTML = WithChild<{
|
||||
/**
|
||||
* The name to use for the hidden input element associated with this input
|
||||
* used for form submission.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Whether this input represents the start or end of the date range.
|
||||
*/
|
||||
type: "start" | "end";
|
||||
}, TimeRangeFieldInputSnippetProps>;
|
||||
export type TimeRangeFieldInputProps = TimeRangeFieldInputPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, TimeRangeFieldInputPropsWithoutHTML>;
|
||||
export type TimeRangeFieldSegmentPropsWithoutHTML = TimeFieldSegmentPropsWithoutHTML;
|
||||
export type TimeRangeFieldSegmentProps = TimeFieldSegmentProps;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Reference in New Issue
Block a user