@proyecto-viviana/solid-stately 0.0.5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +312 -0
- package/dist/index.d.ts +3363 -6
- package/dist/index.js +6194 -19
- package/dist/index.js.map +1 -1
- package/package.json +52 -48
- package/src/autocomplete/createAutocompleteState.d.ts +46 -0
- package/src/autocomplete/createAutocompleteState.d.ts.map +1 -0
- package/src/autocomplete/createAutocompleteState.ts +90 -0
- package/src/autocomplete/index.d.ts +2 -0
- package/src/autocomplete/index.d.ts.map +1 -0
- package/src/autocomplete/index.ts +5 -0
- package/src/calendar/createCalendarState.d.ts +130 -0
- package/src/calendar/createCalendarState.d.ts.map +1 -0
- package/src/calendar/createCalendarState.ts +461 -0
- package/src/calendar/createDateFieldState.d.ts +110 -0
- package/src/calendar/createDateFieldState.d.ts.map +1 -0
- package/src/calendar/createDateFieldState.ts +562 -0
- package/src/calendar/createRangeCalendarState.d.ts +146 -0
- package/src/calendar/createRangeCalendarState.d.ts.map +1 -0
- package/src/calendar/createRangeCalendarState.ts +535 -0
- package/src/calendar/createTimeFieldState.d.ts +95 -0
- package/src/calendar/createTimeFieldState.d.ts.map +1 -0
- package/src/calendar/createTimeFieldState.ts +483 -0
- package/src/calendar/index.d.ts +7 -0
- package/src/calendar/index.d.ts.map +1 -0
- package/src/calendar/index.ts +81 -0
- package/{dist → src}/checkbox/createCheckboxGroupState.d.ts +1 -0
- package/src/checkbox/createCheckboxGroupState.d.ts.map +1 -0
- package/{dist → src}/checkbox/index.d.ts +1 -0
- package/src/checkbox/index.d.ts.map +1 -0
- package/src/collections/ListCollection.d.ts +37 -0
- package/src/collections/ListCollection.d.ts.map +1 -0
- package/src/collections/ListCollection.ts +146 -0
- package/src/collections/createListState.d.ts +79 -0
- package/src/collections/createListState.d.ts.map +1 -0
- package/src/collections/createListState.ts +264 -0
- package/src/collections/createMenuState.d.ts +50 -0
- package/src/collections/createMenuState.d.ts.map +1 -0
- package/src/collections/createMenuState.ts +106 -0
- package/src/collections/createSelectionState.d.ts +76 -0
- package/src/collections/createSelectionState.d.ts.map +1 -0
- package/src/collections/createSelectionState.ts +336 -0
- package/src/collections/index.d.ts +6 -0
- package/src/collections/index.d.ts.map +1 -0
- package/src/collections/index.ts +46 -0
- package/src/collections/types.d.ts +147 -0
- package/src/collections/types.d.ts.map +1 -0
- package/src/collections/types.ts +169 -0
- package/src/color/Color.d.ts +28 -0
- package/src/color/Color.d.ts.map +1 -0
- package/src/color/Color.ts +951 -0
- package/src/color/createColorAreaState.d.ts +76 -0
- package/src/color/createColorAreaState.d.ts.map +1 -0
- package/src/color/createColorAreaState.ts +293 -0
- package/src/color/createColorFieldState.d.ts +55 -0
- package/src/color/createColorFieldState.d.ts.map +1 -0
- package/src/color/createColorFieldState.ts +292 -0
- package/src/color/createColorSliderState.d.ts +67 -0
- package/src/color/createColorSliderState.d.ts.map +1 -0
- package/src/color/createColorSliderState.ts +241 -0
- package/src/color/createColorWheelState.d.ts +51 -0
- package/src/color/createColorWheelState.d.ts.map +1 -0
- package/src/color/createColorWheelState.ts +211 -0
- package/src/color/index.d.ts +10 -0
- package/src/color/index.d.ts.map +1 -0
- package/src/color/index.ts +47 -0
- package/src/color/types.d.ts +106 -0
- package/src/color/types.d.ts.map +1 -0
- package/src/color/types.ts +127 -0
- package/src/combobox/createComboBoxState.d.ts +125 -0
- package/src/combobox/createComboBoxState.d.ts.map +1 -0
- package/src/combobox/createComboBoxState.ts +703 -0
- package/src/combobox/index.d.ts +5 -0
- package/src/combobox/index.d.ts.map +1 -0
- package/src/combobox/index.ts +13 -0
- package/src/disclosure/createDisclosureState.d.ts +64 -0
- package/src/disclosure/createDisclosureState.d.ts.map +1 -0
- package/src/disclosure/createDisclosureState.ts +193 -0
- package/src/disclosure/index.d.ts +2 -0
- package/src/disclosure/index.d.ts.map +1 -0
- package/src/disclosure/index.ts +9 -0
- package/src/dnd/createDragState.d.ts +59 -0
- package/src/dnd/createDragState.d.ts.map +1 -0
- package/src/dnd/createDragState.ts +153 -0
- package/src/dnd/createDraggableCollectionState.d.ts +57 -0
- package/src/dnd/createDraggableCollectionState.d.ts.map +1 -0
- package/src/dnd/createDraggableCollectionState.ts +165 -0
- package/src/dnd/createDropState.d.ts +61 -0
- package/src/dnd/createDropState.d.ts.map +1 -0
- package/src/dnd/createDropState.ts +212 -0
- package/src/dnd/createDroppableCollectionState.d.ts +78 -0
- package/src/dnd/createDroppableCollectionState.d.ts.map +1 -0
- package/src/dnd/createDroppableCollectionState.ts +357 -0
- package/src/dnd/index.d.ts +11 -0
- package/src/dnd/index.d.ts.map +1 -0
- package/src/dnd/index.ts +76 -0
- package/src/dnd/types.d.ts +264 -0
- package/src/dnd/types.d.ts.map +1 -0
- package/src/dnd/types.ts +317 -0
- package/src/form/createFormValidationState.d.ts +100 -0
- package/src/form/createFormValidationState.d.ts.map +1 -0
- package/src/form/createFormValidationState.ts +389 -0
- package/src/form/index.d.ts +2 -0
- package/src/form/index.d.ts.map +1 -0
- package/src/form/index.ts +15 -0
- package/src/grid/createGridState.d.ts +12 -0
- package/src/grid/createGridState.d.ts.map +1 -0
- package/src/grid/createGridState.ts +327 -0
- package/src/grid/index.d.ts +7 -0
- package/src/grid/index.d.ts.map +1 -0
- package/src/grid/index.ts +13 -0
- package/src/grid/types.d.ts +156 -0
- package/src/grid/types.d.ts.map +1 -0
- package/src/grid/types.ts +179 -0
- package/src/index.d.ts +26 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +350 -1
- package/src/numberfield/createNumberFieldState.d.ts +65 -0
- package/src/numberfield/createNumberFieldState.d.ts.map +1 -0
- package/src/numberfield/createNumberFieldState.ts +383 -0
- package/src/numberfield/index.d.ts +2 -0
- package/src/numberfield/index.d.ts.map +1 -0
- package/src/numberfield/index.ts +5 -0
- package/src/overlays/createOverlayTriggerState.d.ts +32 -0
- package/src/overlays/createOverlayTriggerState.d.ts.map +1 -0
- package/src/overlays/createOverlayTriggerState.ts +67 -0
- package/src/overlays/index.d.ts +2 -0
- package/src/overlays/index.d.ts.map +1 -0
- package/src/overlays/index.ts +5 -0
- package/{dist → src}/radio/createRadioGroupState.d.ts +1 -0
- package/src/radio/createRadioGroupState.d.ts.map +1 -0
- package/{dist → src}/radio/index.d.ts +1 -0
- package/src/radio/index.d.ts.map +1 -0
- package/src/searchfield/createSearchFieldState.d.ts +25 -0
- package/src/searchfield/createSearchFieldState.d.ts.map +1 -0
- package/src/searchfield/createSearchFieldState.ts +62 -0
- package/src/searchfield/index.d.ts +3 -0
- package/src/searchfield/index.d.ts.map +1 -0
- package/src/searchfield/index.ts +5 -0
- package/src/select/createSelectState.d.ts +73 -0
- package/src/select/createSelectState.d.ts.map +1 -0
- package/src/select/createSelectState.ts +181 -0
- package/src/select/index.d.ts +2 -0
- package/src/select/index.d.ts.map +1 -0
- package/src/select/index.ts +5 -0
- package/src/slider/createSliderState.d.ts +72 -0
- package/src/slider/createSliderState.d.ts.map +1 -0
- package/src/slider/createSliderState.ts +211 -0
- package/src/slider/index.d.ts +3 -0
- package/src/slider/index.d.ts.map +1 -0
- package/src/slider/index.ts +6 -0
- package/{dist → src}/ssr/index.d.ts +5 -0
- package/src/ssr/index.d.ts.map +1 -0
- package/src/ssr/index.ts +6 -1
- package/src/table/TableCollection.d.ts +52 -0
- package/src/table/TableCollection.d.ts.map +1 -0
- package/src/table/TableCollection.ts +388 -0
- package/src/table/createTableState.d.ts +12 -0
- package/src/table/createTableState.d.ts.map +1 -0
- package/src/table/createTableState.ts +127 -0
- package/src/table/index.d.ts +8 -0
- package/src/table/index.d.ts.map +1 -0
- package/src/table/index.ts +18 -0
- package/src/table/types.d.ts +139 -0
- package/src/table/types.d.ts.map +1 -0
- package/src/table/types.ts +150 -0
- package/src/tabs/createTabListState.d.ts +68 -0
- package/src/tabs/createTabListState.d.ts.map +1 -0
- package/src/tabs/createTabListState.ts +240 -0
- package/src/tabs/index.d.ts +2 -0
- package/src/tabs/index.d.ts.map +1 -0
- package/src/tabs/index.ts +7 -0
- package/{dist → src}/textfield/createTextFieldState.d.ts +1 -0
- package/src/textfield/createTextFieldState.d.ts.map +1 -0
- package/{dist → src}/textfield/index.d.ts +1 -0
- package/src/textfield/index.d.ts.map +1 -0
- package/src/toast/createToastState.d.ts +118 -0
- package/src/toast/createToastState.d.ts.map +1 -0
- package/src/toast/createToastState.ts +316 -0
- package/src/toast/index.d.ts +2 -0
- package/src/toast/index.d.ts.map +1 -0
- package/src/toast/index.ts +11 -0
- package/{dist → src}/toggle/createToggleState.d.ts +1 -0
- package/src/toggle/createToggleState.d.ts.map +1 -0
- package/{dist → src}/toggle/index.d.ts +1 -0
- package/src/toggle/index.d.ts.map +1 -0
- package/src/tooltip/createTooltipTriggerState.d.ts +39 -0
- package/src/tooltip/createTooltipTriggerState.d.ts.map +1 -0
- package/src/tooltip/createTooltipTriggerState.ts +183 -0
- package/src/tooltip/index.d.ts +2 -0
- package/src/tooltip/index.d.ts.map +1 -0
- package/src/tooltip/index.ts +6 -0
- package/src/tree/TreeCollection.d.ts +40 -0
- package/src/tree/TreeCollection.d.ts.map +1 -0
- package/src/tree/TreeCollection.ts +175 -0
- package/src/tree/createTreeState.d.ts +14 -0
- package/src/tree/createTreeState.d.ts.map +1 -0
- package/src/tree/createTreeState.ts +392 -0
- package/src/tree/index.d.ts +7 -0
- package/src/tree/index.d.ts.map +1 -0
- package/src/tree/index.ts +13 -0
- package/src/tree/types.d.ts +157 -0
- package/src/tree/types.d.ts.map +1 -0
- package/src/tree/types.ts +174 -0
- package/{dist → src}/utils/index.d.ts +1 -0
- package/src/utils/index.d.ts.map +1 -0
- package/{dist → src}/utils/reactivity.d.ts +1 -0
- package/src/utils/reactivity.d.ts.map +1 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DateFieldState for Solid-Stately
|
|
3
|
+
*
|
|
4
|
+
* Provides state management for date field components with segment-based editing.
|
|
5
|
+
* Based on @react-stately/datepicker useDateFieldState
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createSignal, createMemo, type Accessor } from 'solid-js';
|
|
9
|
+
import {
|
|
10
|
+
type CalendarDate,
|
|
11
|
+
type CalendarDateTime,
|
|
12
|
+
type ZonedDateTime,
|
|
13
|
+
type DateValue,
|
|
14
|
+
today,
|
|
15
|
+
getLocalTimeZone,
|
|
16
|
+
DateFormatter,
|
|
17
|
+
toCalendarDate as intlToCalendarDate,
|
|
18
|
+
toCalendarDateTime,
|
|
19
|
+
} from '@internationalized/date';
|
|
20
|
+
import { access, type MaybeAccessor } from '../utils';
|
|
21
|
+
import type { ValidationState } from './createCalendarState';
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// TYPES
|
|
25
|
+
// ============================================
|
|
26
|
+
|
|
27
|
+
export type DateSegmentType =
|
|
28
|
+
| 'year'
|
|
29
|
+
| 'month'
|
|
30
|
+
| 'day'
|
|
31
|
+
| 'hour'
|
|
32
|
+
| 'minute'
|
|
33
|
+
| 'second'
|
|
34
|
+
| 'dayPeriod'
|
|
35
|
+
| 'era'
|
|
36
|
+
| 'timeZoneName'
|
|
37
|
+
| 'literal';
|
|
38
|
+
|
|
39
|
+
export interface DateSegment {
|
|
40
|
+
/** The type of segment. */
|
|
41
|
+
type: DateSegmentType;
|
|
42
|
+
/** The text content of the segment. */
|
|
43
|
+
text: string;
|
|
44
|
+
/** The numeric value of the segment (if applicable). */
|
|
45
|
+
value?: number;
|
|
46
|
+
/** The minimum value for the segment. */
|
|
47
|
+
minValue?: number;
|
|
48
|
+
/** The maximum value for the segment. */
|
|
49
|
+
maxValue?: number;
|
|
50
|
+
/** Whether this segment is editable. */
|
|
51
|
+
isEditable: boolean;
|
|
52
|
+
/** Whether this segment is a placeholder. */
|
|
53
|
+
isPlaceholder: boolean;
|
|
54
|
+
/** A placeholder string for the segment. */
|
|
55
|
+
placeholder: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface DateFieldStateProps<T extends DateValue = DateValue> {
|
|
59
|
+
/** The current value (controlled). */
|
|
60
|
+
value?: MaybeAccessor<T | null>;
|
|
61
|
+
/** The default value (uncontrolled). */
|
|
62
|
+
defaultValue?: T | null;
|
|
63
|
+
/** Handler called when the value changes. */
|
|
64
|
+
onChange?: (value: T | null) => void;
|
|
65
|
+
/** The minimum allowed date. */
|
|
66
|
+
minValue?: MaybeAccessor<DateValue | undefined>;
|
|
67
|
+
/** The maximum allowed date. */
|
|
68
|
+
maxValue?: MaybeAccessor<DateValue | undefined>;
|
|
69
|
+
/** Whether the field is disabled. */
|
|
70
|
+
isDisabled?: MaybeAccessor<boolean>;
|
|
71
|
+
/** Whether the field is read-only. */
|
|
72
|
+
isReadOnly?: MaybeAccessor<boolean>;
|
|
73
|
+
/** Whether the field is required. */
|
|
74
|
+
isRequired?: MaybeAccessor<boolean>;
|
|
75
|
+
/** The locale to use for formatting. */
|
|
76
|
+
locale?: string;
|
|
77
|
+
/** The granularity of the date/time (day, hour, minute, second). */
|
|
78
|
+
granularity?: 'day' | 'hour' | 'minute' | 'second';
|
|
79
|
+
/** Whether to show the hour in 12 or 24 hour format. */
|
|
80
|
+
hourCycle?: 12 | 24;
|
|
81
|
+
/** Whether to hide the time zone. */
|
|
82
|
+
hideTimeZone?: boolean;
|
|
83
|
+
/** The placeholder date (determines segment structure). */
|
|
84
|
+
placeholderValue?: DateValue;
|
|
85
|
+
/** Validation state. */
|
|
86
|
+
validationState?: MaybeAccessor<ValidationState | undefined>;
|
|
87
|
+
/** Description text. */
|
|
88
|
+
description?: string;
|
|
89
|
+
/** Error message. */
|
|
90
|
+
errorMessage?: string;
|
|
91
|
+
/** Whether dates outside the min/max range are allowed. */
|
|
92
|
+
allowsNonContiguousRanges?: boolean;
|
|
93
|
+
/** Whether to create a date or datetime. */
|
|
94
|
+
createCalendar?: (name: string) => unknown;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface DateFieldState<T extends DateValue = DateValue> {
|
|
98
|
+
/** The current value. */
|
|
99
|
+
value: Accessor<T | null>;
|
|
100
|
+
/** The date value (may be partial during editing). */
|
|
101
|
+
dateValue: Accessor<DateValue | null>;
|
|
102
|
+
/** Sets the date value. */
|
|
103
|
+
setValue: (value: T | null) => void;
|
|
104
|
+
/** The segments that make up the date. */
|
|
105
|
+
segments: Accessor<DateSegment[]>;
|
|
106
|
+
/** The format string. */
|
|
107
|
+
formatValue: (fieldOptions?: Intl.DateTimeFormatOptions) => string;
|
|
108
|
+
/** Sets a segment value. */
|
|
109
|
+
setSegment: (type: DateSegmentType, value: number) => void;
|
|
110
|
+
/** Increments a segment. */
|
|
111
|
+
incrementSegment: (type: DateSegmentType) => void;
|
|
112
|
+
/** Decrements a segment. */
|
|
113
|
+
decrementSegment: (type: DateSegmentType) => void;
|
|
114
|
+
/** Clears a segment. */
|
|
115
|
+
clearSegment: (type: DateSegmentType) => void;
|
|
116
|
+
/** Confirms the value (after typing). */
|
|
117
|
+
confirmPlaceholder: () => void;
|
|
118
|
+
/** Whether the field is disabled. */
|
|
119
|
+
isDisabled: Accessor<boolean>;
|
|
120
|
+
/** Whether the field is read-only. */
|
|
121
|
+
isReadOnly: Accessor<boolean>;
|
|
122
|
+
/** Whether the field is required. */
|
|
123
|
+
isRequired: Accessor<boolean>;
|
|
124
|
+
/** The validation state. */
|
|
125
|
+
validationState: Accessor<ValidationState | undefined>;
|
|
126
|
+
/** The granularity. */
|
|
127
|
+
granularity: 'day' | 'hour' | 'minute' | 'second';
|
|
128
|
+
/** Whether the value is invalid. */
|
|
129
|
+
isInvalid: Accessor<boolean>;
|
|
130
|
+
/** The locale. */
|
|
131
|
+
locale: string;
|
|
132
|
+
/** The time zone. */
|
|
133
|
+
timeZone: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================
|
|
137
|
+
// IMPLEMENTATION
|
|
138
|
+
// ============================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Provides state management for a date field component.
|
|
142
|
+
*/
|
|
143
|
+
export function createDateFieldState<T extends DateValue = CalendarDate>(
|
|
144
|
+
props: DateFieldStateProps<T> = {}
|
|
145
|
+
): DateFieldState<T> {
|
|
146
|
+
const timeZone = getLocalTimeZone();
|
|
147
|
+
const locale = props.locale ?? 'en-US';
|
|
148
|
+
const granularity = props.granularity ?? 'day';
|
|
149
|
+
|
|
150
|
+
// State signals
|
|
151
|
+
const [internalValue, setInternalValue] = createSignal<T | null>(
|
|
152
|
+
props.defaultValue ?? null
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Track partial values during editing
|
|
156
|
+
const [placeholderDate, setPlaceholderDate] = createSignal<Partial<DateParts>>({});
|
|
157
|
+
|
|
158
|
+
// Controlled vs uncontrolled value
|
|
159
|
+
const value = createMemo<T | null>(() => {
|
|
160
|
+
const controlled = access(props.value);
|
|
161
|
+
return controlled !== undefined ? controlled : internalValue();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// The effective date value
|
|
165
|
+
const dateValue = createMemo<DateValue | null>(() => {
|
|
166
|
+
const v = value();
|
|
167
|
+
if (v) return v;
|
|
168
|
+
|
|
169
|
+
// Build from placeholder parts
|
|
170
|
+
const parts = placeholderDate();
|
|
171
|
+
const placeholder = props.placeholderValue ?? today(timeZone);
|
|
172
|
+
|
|
173
|
+
if (Object.keys(parts).length === 0) return null;
|
|
174
|
+
|
|
175
|
+
// Create a date from the parts
|
|
176
|
+
const year = parts.year ?? placeholder.year;
|
|
177
|
+
const month = parts.month ?? placeholder.month;
|
|
178
|
+
const day = parts.day ?? placeholder.day;
|
|
179
|
+
|
|
180
|
+
if (granularity === 'day') {
|
|
181
|
+
return intlToCalendarDate(placeholder).set({ year, month, day });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// For time granularities
|
|
185
|
+
const hour = parts.hour ?? 0;
|
|
186
|
+
const minute = parts.minute ?? 0;
|
|
187
|
+
const second = parts.second ?? 0;
|
|
188
|
+
|
|
189
|
+
return toCalendarDateTime(placeholder).set({
|
|
190
|
+
year,
|
|
191
|
+
month,
|
|
192
|
+
day,
|
|
193
|
+
hour,
|
|
194
|
+
minute,
|
|
195
|
+
second,
|
|
196
|
+
}) as unknown as T;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Derived states
|
|
200
|
+
const isDisabled = createMemo(() => access(props.isDisabled) ?? false);
|
|
201
|
+
const isReadOnly = createMemo(() => access(props.isReadOnly) ?? false);
|
|
202
|
+
const isRequired = createMemo(() => access(props.isRequired) ?? false);
|
|
203
|
+
const validationState = createMemo(() => access(props.validationState));
|
|
204
|
+
|
|
205
|
+
// Check if value is invalid
|
|
206
|
+
const isInvalid = createMemo(() => {
|
|
207
|
+
const v = value();
|
|
208
|
+
if (!v) return false;
|
|
209
|
+
|
|
210
|
+
const minValue = access(props.minValue);
|
|
211
|
+
const maxValue = access(props.maxValue);
|
|
212
|
+
|
|
213
|
+
if (minValue && v.compare(minValue) < 0) return true;
|
|
214
|
+
if (maxValue && v.compare(maxValue) > 0) return true;
|
|
215
|
+
|
|
216
|
+
return validationState() === 'invalid';
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Generate segments based on granularity and locale
|
|
220
|
+
const segments = createMemo<DateSegment[]>(() => {
|
|
221
|
+
const v = value();
|
|
222
|
+
const placeholder = props.placeholderValue ?? today(timeZone);
|
|
223
|
+
const parts = placeholderDate();
|
|
224
|
+
|
|
225
|
+
const segs: DateSegment[] = [];
|
|
226
|
+
|
|
227
|
+
// Determine format options based on granularity
|
|
228
|
+
const formatOptions: Intl.DateTimeFormatOptions = {
|
|
229
|
+
year: 'numeric',
|
|
230
|
+
month: '2-digit',
|
|
231
|
+
day: '2-digit',
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
if (granularity !== 'day') {
|
|
235
|
+
formatOptions.hour = '2-digit';
|
|
236
|
+
formatOptions.minute = '2-digit';
|
|
237
|
+
if (granularity === 'second') {
|
|
238
|
+
formatOptions.second = '2-digit';
|
|
239
|
+
}
|
|
240
|
+
if (props.hourCycle) {
|
|
241
|
+
formatOptions.hourCycle = props.hourCycle === 12 ? 'h12' : 'h23';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const formatter = new DateFormatter(locale, formatOptions);
|
|
246
|
+
const dateToFormat = v ?? placeholder;
|
|
247
|
+
const formattedParts = formatter.formatToParts(dateToFormat.toDate(timeZone));
|
|
248
|
+
|
|
249
|
+
for (const part of formattedParts) {
|
|
250
|
+
const type = mapPartType(part.type);
|
|
251
|
+
|
|
252
|
+
if (type === 'literal') {
|
|
253
|
+
segs.push({
|
|
254
|
+
type: 'literal',
|
|
255
|
+
text: part.value,
|
|
256
|
+
isEditable: false,
|
|
257
|
+
isPlaceholder: false,
|
|
258
|
+
placeholder: part.value,
|
|
259
|
+
});
|
|
260
|
+
} else if (type) {
|
|
261
|
+
const segValue = getSegmentValue(v, type);
|
|
262
|
+
const placeholderValue = getSegmentValue(placeholder, type);
|
|
263
|
+
const partValue = parts[type as keyof DateParts];
|
|
264
|
+
const hasValue = v !== null || partValue !== undefined;
|
|
265
|
+
|
|
266
|
+
segs.push({
|
|
267
|
+
type,
|
|
268
|
+
text: hasValue ? part.value : getPlaceholderText(type),
|
|
269
|
+
value: segValue ?? partValue,
|
|
270
|
+
minValue: getMinValue(type),
|
|
271
|
+
maxValue: getMaxValue(type, v ?? placeholder),
|
|
272
|
+
isEditable: !isDisabled() && !isReadOnly(),
|
|
273
|
+
isPlaceholder: !hasValue,
|
|
274
|
+
placeholder: getPlaceholderText(type),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return segs;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Set value with onChange callback
|
|
283
|
+
const setValue = (newValue: T | null) => {
|
|
284
|
+
if (isDisabled() || isReadOnly()) return;
|
|
285
|
+
|
|
286
|
+
const controlled = access(props.value);
|
|
287
|
+
if (controlled === undefined) {
|
|
288
|
+
setInternalValue(() => newValue);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (props.onChange) {
|
|
292
|
+
props.onChange(newValue);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Clear placeholder parts
|
|
296
|
+
setPlaceholderDate({});
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Set a specific segment value
|
|
300
|
+
const setSegment = (type: DateSegmentType, newValue: number) => {
|
|
301
|
+
if (isDisabled() || isReadOnly()) return;
|
|
302
|
+
|
|
303
|
+
const v = value();
|
|
304
|
+
|
|
305
|
+
if (v) {
|
|
306
|
+
// Update existing value
|
|
307
|
+
const updated = updateDatePart(v, type, newValue);
|
|
308
|
+
setValue(updated as T);
|
|
309
|
+
} else {
|
|
310
|
+
// Update placeholder parts
|
|
311
|
+
setPlaceholderDate((prev) => ({
|
|
312
|
+
...prev,
|
|
313
|
+
[type]: newValue,
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
// Increment a segment
|
|
319
|
+
const incrementSegment = (type: DateSegmentType) => {
|
|
320
|
+
if (isDisabled() || isReadOnly()) return;
|
|
321
|
+
|
|
322
|
+
const v = value();
|
|
323
|
+
const current = v
|
|
324
|
+
? getSegmentValue(v, type)
|
|
325
|
+
: placeholderDate()[type as keyof DateParts];
|
|
326
|
+
const max = getMaxValue(type, v ?? props.placeholderValue ?? today(timeZone));
|
|
327
|
+
const min = getMinValue(type);
|
|
328
|
+
|
|
329
|
+
const newValue = current !== undefined ? current + 1 : min;
|
|
330
|
+
setSegment(type, newValue > max ? min : newValue);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// Decrement a segment
|
|
334
|
+
const decrementSegment = (type: DateSegmentType) => {
|
|
335
|
+
if (isDisabled() || isReadOnly()) return;
|
|
336
|
+
|
|
337
|
+
const v = value();
|
|
338
|
+
const current = v
|
|
339
|
+
? getSegmentValue(v, type)
|
|
340
|
+
: placeholderDate()[type as keyof DateParts];
|
|
341
|
+
const max = getMaxValue(type, v ?? props.placeholderValue ?? today(timeZone));
|
|
342
|
+
const min = getMinValue(type);
|
|
343
|
+
|
|
344
|
+
const newValue = current !== undefined ? current - 1 : max;
|
|
345
|
+
setSegment(type, newValue < min ? max : newValue);
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Clear a segment
|
|
349
|
+
const clearSegment = (type: DateSegmentType) => {
|
|
350
|
+
if (isDisabled() || isReadOnly()) return;
|
|
351
|
+
|
|
352
|
+
const v = value();
|
|
353
|
+
if (v) {
|
|
354
|
+
// Clear entire value if any segment is cleared
|
|
355
|
+
setValue(null);
|
|
356
|
+
} else {
|
|
357
|
+
setPlaceholderDate((prev) => {
|
|
358
|
+
const next = { ...prev };
|
|
359
|
+
delete next[type as keyof DateParts];
|
|
360
|
+
return next;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// Confirm placeholder value
|
|
366
|
+
const confirmPlaceholder = () => {
|
|
367
|
+
const parts = placeholderDate();
|
|
368
|
+
if (Object.keys(parts).length > 0) {
|
|
369
|
+
const dv = dateValue();
|
|
370
|
+
if (dv) {
|
|
371
|
+
setValue(dv as T);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Format the current value
|
|
377
|
+
const formatValue = (fieldOptions?: Intl.DateTimeFormatOptions): string => {
|
|
378
|
+
const v = value();
|
|
379
|
+
if (!v) return '';
|
|
380
|
+
|
|
381
|
+
const options: Intl.DateTimeFormatOptions = fieldOptions ?? {
|
|
382
|
+
year: 'numeric',
|
|
383
|
+
month: '2-digit',
|
|
384
|
+
day: '2-digit',
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
if (granularity !== 'day') {
|
|
388
|
+
options.hour = '2-digit';
|
|
389
|
+
options.minute = '2-digit';
|
|
390
|
+
if (granularity === 'second') {
|
|
391
|
+
options.second = '2-digit';
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const formatter = new DateFormatter(locale, options);
|
|
396
|
+
return formatter.format(v.toDate(timeZone));
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
value,
|
|
401
|
+
dateValue,
|
|
402
|
+
setValue,
|
|
403
|
+
segments,
|
|
404
|
+
formatValue,
|
|
405
|
+
setSegment,
|
|
406
|
+
incrementSegment,
|
|
407
|
+
decrementSegment,
|
|
408
|
+
clearSegment,
|
|
409
|
+
confirmPlaceholder,
|
|
410
|
+
isDisabled,
|
|
411
|
+
isReadOnly,
|
|
412
|
+
isRequired,
|
|
413
|
+
validationState,
|
|
414
|
+
granularity,
|
|
415
|
+
isInvalid,
|
|
416
|
+
locale,
|
|
417
|
+
timeZone,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ============================================
|
|
422
|
+
// HELPER TYPES & FUNCTIONS
|
|
423
|
+
// ============================================
|
|
424
|
+
|
|
425
|
+
interface DateParts {
|
|
426
|
+
year?: number;
|
|
427
|
+
month?: number;
|
|
428
|
+
day?: number;
|
|
429
|
+
hour?: number;
|
|
430
|
+
minute?: number;
|
|
431
|
+
second?: number;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function mapPartType(type: Intl.DateTimeFormatPartTypes): DateSegmentType | null {
|
|
435
|
+
switch (type) {
|
|
436
|
+
case 'year':
|
|
437
|
+
return 'year';
|
|
438
|
+
case 'month':
|
|
439
|
+
return 'month';
|
|
440
|
+
case 'day':
|
|
441
|
+
return 'day';
|
|
442
|
+
case 'hour':
|
|
443
|
+
return 'hour';
|
|
444
|
+
case 'minute':
|
|
445
|
+
return 'minute';
|
|
446
|
+
case 'second':
|
|
447
|
+
return 'second';
|
|
448
|
+
case 'dayPeriod':
|
|
449
|
+
return 'dayPeriod';
|
|
450
|
+
case 'era':
|
|
451
|
+
return 'era';
|
|
452
|
+
case 'timeZoneName':
|
|
453
|
+
return 'timeZoneName';
|
|
454
|
+
case 'literal':
|
|
455
|
+
return 'literal';
|
|
456
|
+
default:
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function getSegmentValue(
|
|
462
|
+
date: DateValue | null,
|
|
463
|
+
type: DateSegmentType
|
|
464
|
+
): number | undefined {
|
|
465
|
+
if (!date) return undefined;
|
|
466
|
+
|
|
467
|
+
switch (type) {
|
|
468
|
+
case 'year':
|
|
469
|
+
return date.year;
|
|
470
|
+
case 'month':
|
|
471
|
+
return date.month;
|
|
472
|
+
case 'day':
|
|
473
|
+
return date.day;
|
|
474
|
+
case 'hour':
|
|
475
|
+
return 'hour' in date ? (date as CalendarDateTime).hour : undefined;
|
|
476
|
+
case 'minute':
|
|
477
|
+
return 'minute' in date ? (date as CalendarDateTime).minute : undefined;
|
|
478
|
+
case 'second':
|
|
479
|
+
return 'second' in date ? (date as CalendarDateTime).second : undefined;
|
|
480
|
+
default:
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function getMinValue(type: DateSegmentType): number {
|
|
486
|
+
switch (type) {
|
|
487
|
+
case 'year':
|
|
488
|
+
return 1;
|
|
489
|
+
case 'month':
|
|
490
|
+
return 1;
|
|
491
|
+
case 'day':
|
|
492
|
+
return 1;
|
|
493
|
+
case 'hour':
|
|
494
|
+
return 0;
|
|
495
|
+
case 'minute':
|
|
496
|
+
return 0;
|
|
497
|
+
case 'second':
|
|
498
|
+
return 0;
|
|
499
|
+
default:
|
|
500
|
+
return 0;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function getMaxValue(type: DateSegmentType, date: DateValue): number {
|
|
505
|
+
switch (type) {
|
|
506
|
+
case 'year':
|
|
507
|
+
return 9999;
|
|
508
|
+
case 'month':
|
|
509
|
+
return 12;
|
|
510
|
+
case 'day':
|
|
511
|
+
// Get days in month
|
|
512
|
+
return date.calendar.getDaysInMonth?.(date) ?? 31;
|
|
513
|
+
case 'hour':
|
|
514
|
+
return 23;
|
|
515
|
+
case 'minute':
|
|
516
|
+
return 59;
|
|
517
|
+
case 'second':
|
|
518
|
+
return 59;
|
|
519
|
+
default:
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function getPlaceholderText(type: DateSegmentType): string {
|
|
525
|
+
switch (type) {
|
|
526
|
+
case 'year':
|
|
527
|
+
return 'yyyy';
|
|
528
|
+
case 'month':
|
|
529
|
+
return 'mm';
|
|
530
|
+
case 'day':
|
|
531
|
+
return 'dd';
|
|
532
|
+
case 'hour':
|
|
533
|
+
return '––';
|
|
534
|
+
case 'minute':
|
|
535
|
+
return '––';
|
|
536
|
+
case 'second':
|
|
537
|
+
return '––';
|
|
538
|
+
case 'dayPeriod':
|
|
539
|
+
return 'AM';
|
|
540
|
+
default:
|
|
541
|
+
return '';
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function updateDatePart(date: DateValue, type: DateSegmentType, value: number): DateValue {
|
|
546
|
+
switch (type) {
|
|
547
|
+
case 'year':
|
|
548
|
+
return (date as CalendarDate).set({ year: value });
|
|
549
|
+
case 'month':
|
|
550
|
+
return (date as CalendarDate).set({ month: value });
|
|
551
|
+
case 'day':
|
|
552
|
+
return (date as CalendarDate).set({ day: value });
|
|
553
|
+
case 'hour':
|
|
554
|
+
return (date as CalendarDateTime).set({ hour: value });
|
|
555
|
+
case 'minute':
|
|
556
|
+
return (date as CalendarDateTime).set({ minute: value });
|
|
557
|
+
case 'second':
|
|
558
|
+
return (date as CalendarDateTime).set({ second: value });
|
|
559
|
+
default:
|
|
560
|
+
return date;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RangeCalendarState for Solid-Stately
|
|
3
|
+
*
|
|
4
|
+
* Provides state management for range calendar components.
|
|
5
|
+
* Based on @react-stately/calendar useRangeCalendarState
|
|
6
|
+
*/
|
|
7
|
+
import { type Accessor } from 'solid-js';
|
|
8
|
+
import { type CalendarDate, type DateValue } from '@internationalized/date';
|
|
9
|
+
import { type MaybeAccessor } from '../utils';
|
|
10
|
+
import type { ValidationState } from './createCalendarState';
|
|
11
|
+
export interface DateRange {
|
|
12
|
+
start: CalendarDate;
|
|
13
|
+
end: CalendarDate;
|
|
14
|
+
}
|
|
15
|
+
export interface RangeValue<T> {
|
|
16
|
+
start: T;
|
|
17
|
+
end: T;
|
|
18
|
+
}
|
|
19
|
+
export interface RangeCalendarStateProps<T extends DateValue = DateValue> {
|
|
20
|
+
/** The current value (controlled). */
|
|
21
|
+
value?: MaybeAccessor<RangeValue<T> | null>;
|
|
22
|
+
/** The default value (uncontrolled). */
|
|
23
|
+
defaultValue?: RangeValue<T> | null;
|
|
24
|
+
/** Handler called when the value changes. */
|
|
25
|
+
onChange?: (value: RangeValue<T>) => void;
|
|
26
|
+
/** The minimum allowed date. */
|
|
27
|
+
minValue?: MaybeAccessor<DateValue | undefined>;
|
|
28
|
+
/** The maximum allowed date. */
|
|
29
|
+
maxValue?: MaybeAccessor<DateValue | undefined>;
|
|
30
|
+
/** Whether the calendar is disabled. */
|
|
31
|
+
isDisabled?: MaybeAccessor<boolean>;
|
|
32
|
+
/** Whether the calendar is read-only. */
|
|
33
|
+
isReadOnly?: MaybeAccessor<boolean>;
|
|
34
|
+
/** The date that is focused when the calendar first mounts. */
|
|
35
|
+
focusedValue?: MaybeAccessor<DateValue | undefined>;
|
|
36
|
+
/** The default focused date (uncontrolled). */
|
|
37
|
+
defaultFocusedValue?: DateValue;
|
|
38
|
+
/** Handler called when the focused date changes. */
|
|
39
|
+
onFocusChange?: (date: CalendarDate) => void;
|
|
40
|
+
/** The locale to use for formatting. */
|
|
41
|
+
locale?: string;
|
|
42
|
+
/** Callback to determine if a date is unavailable. */
|
|
43
|
+
isDateUnavailable?: (date: DateValue) => boolean;
|
|
44
|
+
/** The number of months to display at once. */
|
|
45
|
+
visibleMonths?: number;
|
|
46
|
+
/** Controls which days are disabled. */
|
|
47
|
+
isDateDisabled?: (date: DateValue) => boolean;
|
|
48
|
+
/** Validation state. */
|
|
49
|
+
validationState?: MaybeAccessor<ValidationState | undefined>;
|
|
50
|
+
/** Whether to allow selecting the same date for start and end. */
|
|
51
|
+
allowsNonContiguousRanges?: boolean;
|
|
52
|
+
/** The first day of the week (0 = Sunday, 1 = Monday, etc.). */
|
|
53
|
+
firstDayOfWeek?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
54
|
+
}
|
|
55
|
+
export interface RangeCalendarState<T extends DateValue = DateValue> {
|
|
56
|
+
/** The currently selected date range. */
|
|
57
|
+
value: Accessor<RangeValue<T> | null>;
|
|
58
|
+
/** Sets the selected date range. */
|
|
59
|
+
setValue: (value: RangeValue<T> | null) => void;
|
|
60
|
+
/** The currently focused date. */
|
|
61
|
+
focusedDate: Accessor<CalendarDate>;
|
|
62
|
+
/** Sets the focused date. */
|
|
63
|
+
setFocusedDate: (date: CalendarDate) => void;
|
|
64
|
+
/** The anchor date when selecting a range (first click). */
|
|
65
|
+
anchorDate: Accessor<CalendarDate | null>;
|
|
66
|
+
/** Sets the anchor date. */
|
|
67
|
+
setAnchorDate: (date: CalendarDate | null) => void;
|
|
68
|
+
/** The highlighted range during selection. */
|
|
69
|
+
highlightedRange: Accessor<RangeValue<CalendarDate> | null>;
|
|
70
|
+
/** Whether the calendar is disabled. */
|
|
71
|
+
isDisabled: Accessor<boolean>;
|
|
72
|
+
/** Whether the calendar is read-only. */
|
|
73
|
+
isReadOnly: Accessor<boolean>;
|
|
74
|
+
/** The visible date range (first and last day of visible month(s)). */
|
|
75
|
+
visibleRange: Accessor<{
|
|
76
|
+
start: CalendarDate;
|
|
77
|
+
end: CalendarDate;
|
|
78
|
+
}>;
|
|
79
|
+
/** The timezone used for date calculations. */
|
|
80
|
+
timeZone: string;
|
|
81
|
+
/** The validation state. */
|
|
82
|
+
validationState: Accessor<ValidationState | undefined>;
|
|
83
|
+
/** Whether a date is within the selected range. */
|
|
84
|
+
isSelected: (date: DateValue) => boolean;
|
|
85
|
+
/** Whether a date is the start of the selection. */
|
|
86
|
+
isSelectionStart: (date: DateValue) => boolean;
|
|
87
|
+
/** Whether a date is the end of the selection. */
|
|
88
|
+
isSelectionEnd: (date: DateValue) => boolean;
|
|
89
|
+
/** Whether a date is focused. */
|
|
90
|
+
isCellFocused: (date: DateValue) => boolean;
|
|
91
|
+
/** Whether a date is unavailable. */
|
|
92
|
+
isCellUnavailable: (date: DateValue) => boolean;
|
|
93
|
+
/** Whether a date is disabled. */
|
|
94
|
+
isCellDisabled: (date: DateValue) => boolean;
|
|
95
|
+
/** Whether a date is outside the visible range. */
|
|
96
|
+
isOutsideVisibleRange: (date: DateValue) => boolean;
|
|
97
|
+
/** Whether a date is invalid. */
|
|
98
|
+
isInvalid: (date: DateValue) => boolean;
|
|
99
|
+
/** Moves focus to the previous page (month). */
|
|
100
|
+
focusPreviousPage: () => void;
|
|
101
|
+
/** Moves focus to the next page (month). */
|
|
102
|
+
focusNextPage: () => void;
|
|
103
|
+
/** Moves focus to the previous section (year). */
|
|
104
|
+
focusPreviousSection: () => void;
|
|
105
|
+
/** Moves focus to the next section (year). */
|
|
106
|
+
focusNextSection: () => void;
|
|
107
|
+
/** Moves focus to the previous day. */
|
|
108
|
+
focusPreviousDay: () => void;
|
|
109
|
+
/** Moves focus to the next day. */
|
|
110
|
+
focusNextDay: () => void;
|
|
111
|
+
/** Moves focus to the previous week. */
|
|
112
|
+
focusPreviousWeek: () => void;
|
|
113
|
+
/** Moves focus to the next week. */
|
|
114
|
+
focusNextWeek: () => void;
|
|
115
|
+
/** Moves focus to the start of the month. */
|
|
116
|
+
focusPageStart: () => void;
|
|
117
|
+
/** Moves focus to the end of the month. */
|
|
118
|
+
focusPageEnd: () => void;
|
|
119
|
+
/** Selects the focused date. */
|
|
120
|
+
selectFocusedDate: () => void;
|
|
121
|
+
/** Selects a specific date. */
|
|
122
|
+
selectDate: (date: CalendarDate) => void;
|
|
123
|
+
/** Whether focus is currently within the calendar. */
|
|
124
|
+
isFocused: Accessor<boolean>;
|
|
125
|
+
/** Sets whether focus is within the calendar. */
|
|
126
|
+
setFocused: (focused: boolean) => void;
|
|
127
|
+
/** Gets the dates for a week in the visible range. */
|
|
128
|
+
getDatesInWeek: (weekIndex: number, monthStartDate?: CalendarDate) => (CalendarDate | null)[];
|
|
129
|
+
/** Gets the number of weeks in a month. */
|
|
130
|
+
getWeeksInMonth: (date?: CalendarDate) => number;
|
|
131
|
+
/** The week day headers. */
|
|
132
|
+
weekDays: Accessor<string[]>;
|
|
133
|
+
/** The title for the calendar (formatted month and year). */
|
|
134
|
+
title: Accessor<string>;
|
|
135
|
+
/** The number of visible months. */
|
|
136
|
+
visibleMonths: number;
|
|
137
|
+
/** Whether the user is currently selecting a range. */
|
|
138
|
+
isDragging: Accessor<boolean>;
|
|
139
|
+
/** Sets whether the user is dragging to select. */
|
|
140
|
+
setDragging: (dragging: boolean) => void;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Provides state management for a range calendar component.
|
|
144
|
+
*/
|
|
145
|
+
export declare function createRangeCalendarState<T extends DateValue = CalendarDate>(props?: RangeCalendarStateProps<T>): RangeCalendarState<T>;
|
|
146
|
+
//# sourceMappingURL=createRangeCalendarState.d.ts.map
|