bits-ui 2.4.1 → 2.5.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.
Files changed (41) hide show
  1. package/dist/bits/calendar/calendar.svelte.d.ts +77 -1
  2. package/dist/bits/calendar/calendar.svelte.js +169 -9
  3. package/dist/bits/calendar/components/calendar-month-select.svelte +54 -0
  4. package/dist/bits/calendar/components/calendar-month-select.svelte.d.ts +4 -0
  5. package/dist/bits/calendar/components/calendar-year-select.svelte +51 -0
  6. package/dist/bits/calendar/components/calendar-year-select.svelte.d.ts +4 -0
  7. package/dist/bits/calendar/components/calendar.svelte +6 -0
  8. package/dist/bits/calendar/exports.d.ts +3 -1
  9. package/dist/bits/calendar/exports.js +2 -0
  10. package/dist/bits/calendar/types.d.ts +94 -12
  11. package/dist/bits/combobox/types.d.ts +1 -1
  12. package/dist/bits/date-field/date-field.svelte.js +5 -1
  13. package/dist/bits/date-picker/components/date-picker-calendar.svelte +3 -0
  14. package/dist/bits/date-picker/components/date-picker.svelte +4 -0
  15. package/dist/bits/date-picker/date-picker.svelte.d.ts +2 -0
  16. package/dist/bits/date-picker/exports.d.ts +3 -1
  17. package/dist/bits/date-picker/exports.js +2 -0
  18. package/dist/bits/date-picker/types.d.ts +13 -1
  19. package/dist/bits/date-range-field/date-range-field.svelte.js +5 -1
  20. package/dist/bits/date-range-picker/components/date-range-picker-calendar.svelte +5 -0
  21. package/dist/bits/date-range-picker/components/date-range-picker.svelte +10 -0
  22. package/dist/bits/date-range-picker/date-range-picker.svelte.d.ts +5 -0
  23. package/dist/bits/date-range-picker/exports.d.ts +3 -1
  24. package/dist/bits/date-range-picker/exports.js +2 -0
  25. package/dist/bits/date-range-picker/types.d.ts +33 -1
  26. package/dist/bits/range-calendar/components/range-calendar.svelte +10 -0
  27. package/dist/bits/range-calendar/exports.d.ts +3 -1
  28. package/dist/bits/range-calendar/exports.js +2 -0
  29. package/dist/bits/range-calendar/range-calendar.svelte.d.ts +20 -0
  30. package/dist/bits/range-calendar/range-calendar.svelte.js +120 -6
  31. package/dist/bits/range-calendar/types.d.ts +44 -12
  32. package/dist/bits/select/components/select-hidden-input.svelte +6 -2
  33. package/dist/bits/select/components/select-hidden-input.svelte.d.ts +2 -1
  34. package/dist/bits/select/components/select.svelte +3 -2
  35. package/dist/bits/select/types.d.ts +5 -0
  36. package/dist/internal/date-time/calendar-helpers.svelte.d.ts +8 -2
  37. package/dist/internal/date-time/calendar-helpers.svelte.js +47 -15
  38. package/dist/internal/date-time/formatter.d.ts +8 -1
  39. package/dist/internal/date-time/formatter.js +16 -3
  40. package/dist/shared/attributes.d.ts +3 -1
  41. package/package.json +1 -1
@@ -6,9 +6,9 @@ import { useId } from "../../internal/use-id.js";
6
6
  import { getAriaDisabled, getAriaSelected, getDataDisabled, getDataSelected, getDataUnavailable, } from "../../internal/attrs.js";
7
7
  import { getAnnouncer } from "../../internal/date-time/announcer.js";
8
8
  import { createFormatter } from "../../internal/date-time/formatter.js";
9
- import { calendarAttrs, createMonths, getCalendarElementProps, getCalendarHeadingValue, getIsNextButtonDisabled, getIsPrevButtonDisabled, getWeekdays, handleCalendarKeydown, handleCalendarNextPage, handleCalendarPrevPage, shiftCalendarFocus, useEnsureNonDisabledPlaceholder, useMonthViewOptionsSync, useMonthViewPlaceholderSync, } from "../../internal/date-time/calendar-helpers.svelte.js";
9
+ import { calendarAttrs, createMonths, getCalendarElementProps, getCalendarHeadingValue, getDefaultYears, getIsNextButtonDisabled, getIsPrevButtonDisabled, getWeekdays, handleCalendarKeydown, handleCalendarNextPage, handleCalendarPrevPage, shiftCalendarFocus, useEnsureNonDisabledPlaceholder, useMonthViewOptionsSync, useMonthViewPlaceholderSync, } from "../../internal/date-time/calendar-helpers.svelte.js";
10
10
  import { areAllDaysBetweenValid, getDateValueType, isAfter, isBefore, isBetweenInclusive, toDate, } from "../../internal/date-time/utils.js";
11
- import { onMount } from "svelte";
11
+ import { onMount, untrack } from "svelte";
12
12
  export class RangeCalendarRootState {
13
13
  opts;
14
14
  visibleMonths = $derived.by(() => this.months.map((month) => month.value));
@@ -69,6 +69,8 @@ export class RangeCalendarRootState {
69
69
  });
70
70
  });
71
71
  headingValue = $derived.by(() => {
72
+ this.opts.monthFormat.current;
73
+ this.opts.yearFormat.current;
72
74
  return getCalendarHeadingValue({
73
75
  months: this.months,
74
76
  formatter: this.formatter,
@@ -93,11 +95,23 @@ export class RangeCalendarRootState {
93
95
  return range;
94
96
  return null;
95
97
  });
98
+ initialPlaceholderYear = $derived.by(() => untrack(() => this.opts.placeholder.current.year));
99
+ defaultYears = $derived.by(() => {
100
+ return getDefaultYears({
101
+ minValue: this.opts.minValue.current,
102
+ maxValue: this.opts.maxValue.current,
103
+ placeholderYear: this.initialPlaceholderYear,
104
+ });
105
+ });
96
106
  constructor(opts) {
97
107
  this.opts = opts;
98
108
  this.domContext = new DOMContext(opts.ref);
99
109
  this.announcer = getAnnouncer(null);
100
- this.formatter = createFormatter(this.opts.locale.current);
110
+ this.formatter = createFormatter({
111
+ initialLocale: this.opts.locale.current,
112
+ monthFormat: this.opts.monthFormat,
113
+ yearFormat: this.opts.yearFormat,
114
+ });
101
115
  this.months = createMonths({
102
116
  dateObj: this.opts.placeholder.current,
103
117
  weekStartsOn: this.opts.weekStartsOn.current,
@@ -105,7 +119,7 @@ export class RangeCalendarRootState {
105
119
  fixedWeeks: this.opts.fixedWeeks.current,
106
120
  numberOfMonths: this.opts.numberOfMonths.current,
107
121
  });
108
- $effect(() => {
122
+ $effect.pre(() => {
109
123
  if (this.formatter.getLocale() === this.opts.locale.current)
110
124
  return;
111
125
  this.formatter.setLocale(this.opts.locale.current);
@@ -175,6 +189,22 @@ export class RangeCalendarRootState {
175
189
  this.opts.placeholder.current = startValue;
176
190
  }
177
191
  });
192
+ /**
193
+ * Check for disabled dates in the selected range when excludeDisabled is enabled
194
+ */
195
+ watch([
196
+ () => this.opts.startValue.current,
197
+ () => this.opts.endValue.current,
198
+ () => this.opts.excludeDisabled.current,
199
+ ], ([startValue, endValue, excludeDisabled]) => {
200
+ if (!excludeDisabled || !startValue || !endValue)
201
+ return;
202
+ if (this.#hasDisabledDatesInRange(startValue, endValue)) {
203
+ this.#setStartValue(undefined);
204
+ this.#setEndValue(undefined);
205
+ this.#announceEmpty();
206
+ }
207
+ });
178
208
  watch([() => this.opts.startValue.current, () => this.opts.endValue.current], ([startValue, endValue]) => {
179
209
  if (this.opts.value.current &&
180
210
  this.opts.value.current.start === startValue &&
@@ -191,9 +221,19 @@ export class RangeCalendarRootState {
191
221
  const end = endValue;
192
222
  this.#setStartValue(end);
193
223
  this.#setEndValue(start);
224
+ if (!this.#isRangeValid(endValue, startValue)) {
225
+ this.#setStartValue(startValue);
226
+ this.#setEndValue(undefined);
227
+ return { start: startValue, end: undefined };
228
+ }
194
229
  return { start: endValue, end: startValue };
195
230
  }
196
231
  else {
232
+ if (!this.#isRangeValid(startValue, endValue)) {
233
+ this.#setStartValue(endValue);
234
+ this.#setEndValue(undefined);
235
+ return { start: endValue, end: undefined };
236
+ }
197
237
  return {
198
238
  start: startValue,
199
239
  end: endValue,
@@ -240,9 +280,19 @@ export class RangeCalendarRootState {
240
280
  }
241
281
  #setStartValue(value) {
242
282
  this.opts.startValue.current = value;
283
+ // update the main value prop immediately for external consumers
284
+ this.#updateValue((prev) => ({
285
+ ...prev,
286
+ start: value,
287
+ }));
243
288
  }
244
289
  #setEndValue(value) {
245
290
  this.opts.endValue.current = value;
291
+ // update the main value prop immediately for external consumers
292
+ this.#updateValue((prev) => ({
293
+ ...prev,
294
+ end: value,
295
+ }));
246
296
  }
247
297
  setMonths = (months) => {
248
298
  this.months = months;
@@ -286,6 +336,26 @@ export class RangeCalendarRootState {
286
336
  }
287
337
  return false;
288
338
  }
339
+ #isRangeValid(start, end) {
340
+ // ensure we always use the correct order for calculation
341
+ const orderedStart = isBefore(end, start) ? end : start;
342
+ const orderedEnd = isBefore(end, start) ? start : end;
343
+ const startDate = orderedStart.toDate(getLocalTimeZone());
344
+ const endDate = orderedEnd.toDate(getLocalTimeZone());
345
+ const timeDifference = endDate.getTime() - startDate.getTime();
346
+ const daysDifference = Math.floor(timeDifference / (1000 * 60 * 60 * 24));
347
+ const daysInRange = daysDifference + 1; // +1 to include both start and end days
348
+ if (this.opts.minDays.current && daysInRange < this.opts.minDays.current)
349
+ return false;
350
+ if (this.opts.maxDays.current && daysInRange > this.opts.maxDays.current)
351
+ return false;
352
+ // check for disabled dates in range if excludeDisabled is enabled
353
+ if (this.opts.excludeDisabled.current &&
354
+ this.#hasDisabledDatesInRange(orderedStart, orderedEnd)) {
355
+ return false;
356
+ }
357
+ return true;
358
+ }
289
359
  shiftFocus(node, add) {
290
360
  return shiftCalendarFocus({
291
361
  node,
@@ -344,8 +414,32 @@ export class RangeCalendarRootState {
344
414
  this.#setStartValue(date);
345
415
  }
346
416
  else if (!this.opts.endValue.current) {
347
- this.#announceSelectedRange(this.opts.startValue.current, date);
348
- this.#setEndValue(date);
417
+ // determine the start and end dates for validation
418
+ const startDate = this.opts.startValue.current;
419
+ const endDate = date;
420
+ const orderedStart = isBefore(endDate, startDate) ? endDate : startDate;
421
+ const orderedEnd = isBefore(endDate, startDate) ? startDate : endDate;
422
+ // check if the range violates constraints
423
+ if (!this.#isRangeValid(orderedStart, orderedEnd)) {
424
+ // reset to just the clicked date
425
+ this.#setStartValue(date);
426
+ this.#setEndValue(undefined);
427
+ this.#announceSelectedDate(date);
428
+ }
429
+ else {
430
+ // ensure start and end are properly ordered
431
+ if (isBefore(endDate, startDate)) {
432
+ // backward selection - reorder the values
433
+ this.#setStartValue(endDate);
434
+ this.#setEndValue(startDate);
435
+ this.#announceSelectedRange(endDate, startDate);
436
+ }
437
+ else {
438
+ // forward selection - keep original order
439
+ this.#setEndValue(date);
440
+ this.#announceSelectedRange(this.opts.startValue.current, date);
441
+ }
442
+ }
349
443
  }
350
444
  else if (this.opts.endValue.current && this.opts.startValue.current) {
351
445
  this.#setEndValue(undefined);
@@ -423,6 +517,13 @@ export class RangeCalendarRootState {
423
517
  onkeydown: this.onkeydown,
424
518
  ...attachRef(this.opts.ref),
425
519
  }));
520
+ #hasDisabledDatesInRange(start, end) {
521
+ for (let date = start; isBefore(date, end) || isSameDay(date, end); date = date.add({ days: 1 })) {
522
+ if (this.isDateDisabled(date))
523
+ return true;
524
+ }
525
+ return false;
526
+ }
426
527
  }
427
528
  export class RangeCalendarCellState {
428
529
  opts;
@@ -436,6 +537,16 @@ export class RangeCalendarCellState {
436
537
  isFocusedDate = $derived.by(() => isSameDay(this.opts.date.current, this.root.opts.placeholder.current));
437
538
  isSelectedDate = $derived.by(() => this.root.isSelected(this.opts.date.current));
438
539
  isSelectionStart = $derived.by(() => this.root.isSelectionStart(this.opts.date.current));
540
+ isRangeStart = $derived.by(() => this.root.isSelectionStart(this.opts.date.current));
541
+ isRangeEnd = $derived.by(() => {
542
+ if (!this.root.opts.endValue.current)
543
+ return this.root.isSelectionStart(this.opts.date.current);
544
+ return this.root.isSelectionEnd(this.opts.date.current);
545
+ });
546
+ isRangeMiddle = $derived.by(() => this.isSelectionMiddle);
547
+ isSelectionMiddle = $derived.by(() => {
548
+ return this.isSelectedDate && !this.isSelectionStart && !this.isSelectionEnd;
549
+ });
439
550
  isSelectionEnd = $derived.by(() => this.root.isSelectionEnd(this.opts.date.current));
440
551
  isHighlighted = $derived.by(() => this.root.highlightedRange
441
552
  ? isBetweenInclusive(this.opts.date.current, this.root.highlightedRange.start, this.root.highlightedRange.end)
@@ -468,6 +579,9 @@ export class RangeCalendarCellState {
468
579
  "data-focused": this.isFocusedDate ? "" : undefined,
469
580
  "data-selection-start": this.isSelectionStart ? "" : undefined,
470
581
  "data-selection-end": this.isSelectionEnd ? "" : undefined,
582
+ "data-range-start": this.isRangeStart ? "" : undefined,
583
+ "data-range-end": this.isRangeEnd ? "" : undefined,
584
+ "data-range-middle": this.isRangeMiddle ? "" : undefined,
471
585
  "data-highlighted": this.isHighlighted ? "" : undefined,
472
586
  "data-selected": getDataSelected(this.isSelectedDate),
473
587
  "data-value": this.opts.date.current.toString(),
@@ -20,7 +20,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
20
20
  * The placeholder date, used to control the view of the
21
21
  * calendar when no value is present.
22
22
  *
23
- * @defaultValue the current date
23
+ * @default the current date
24
24
  */
25
25
  placeholder?: DateValue;
26
26
  /**
@@ -28,11 +28,23 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
28
28
  * changes.
29
29
  */
30
30
  onPlaceholderChange?: OnChangeFn<DateValue>;
31
+ /**
32
+ * The minimum number of days that can be selected in a range.
33
+ *
34
+ * @default undefined
35
+ */
36
+ minDays?: number;
37
+ /**
38
+ * The maximum number of days that can be selected in a range.
39
+ *
40
+ * @default undefined
41
+ */
42
+ maxDays?: number;
31
43
  /**
32
44
  * Whether or not users can deselect a date once selected
33
45
  * without selecting another date.
34
46
  *
35
- * @defaultValue false
47
+ * @default false
36
48
  */
37
49
  preventDeselect?: boolean;
38
50
  /**
@@ -46,7 +58,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
46
58
  /**
47
59
  * Whether or not the calendar is disabled.
48
60
  *
49
- * @defaultValue false
61
+ * @default false
50
62
  */
51
63
  disabled?: boolean;
52
64
  /**
@@ -60,7 +72,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
60
72
  * February), clicking the next button changes the view to March and April. If `pagedNavigation`
61
73
  * is `false`, the view shifts to February and March.
62
74
  *
63
- * @defaultValue false
75
+ * @default false
64
76
  */
65
77
  pagedNavigation?: boolean;
66
78
  /**
@@ -68,7 +80,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
68
80
  * be a number between 0 and 6, where 0 is Sunday and 6 is
69
81
  * Saturday.
70
82
  *
71
- * @defaultValue 0 (Sunday)
83
+ * @default 0 (Sunday)
72
84
  */
73
85
  weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
74
86
  /**
@@ -81,7 +93,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
81
93
  * - "narrow": "S", "M", "T", etc.
82
94
  *```
83
95
  *
84
- * @defaultValue "narrow"
96
+ * @default "narrow"
85
97
  *
86
98
  * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#weekday
87
99
  */
@@ -119,14 +131,14 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
119
131
  * To display 6 weeks per month, you will need to render out the previous
120
132
  * and next month's dates in the calendar as well.
121
133
  *
122
- * @defaultValue false
134
+ * @default false
123
135
  */
124
136
  fixedWeeks?: boolean;
125
137
  /**
126
138
  * Determines the number of months to display on the calendar simultaneously.
127
139
  * For navigation between months, refer to the `pagedNavigation` prop.
128
140
  *
129
- * @defaultValue 1
141
+ * @default 1
130
142
  */
131
143
  numberOfMonths?: number;
132
144
  /**
@@ -143,7 +155,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
143
155
  /**
144
156
  * The default locale setting.
145
157
  *
146
- * @defaultValue 'en'
158
+ * @default 'en'
147
159
  */
148
160
  locale?: string;
149
161
  /**
@@ -152,7 +164,7 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
152
164
  * dates. @see disabled for a similar prop that prevents focusing
153
165
  * and selecting dates.
154
166
  *
155
- * @defaultValue false
167
+ * @default false
156
168
  */
157
169
  readonly?: boolean;
158
170
  /**
@@ -160,9 +172,17 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
160
172
  * days outside the current month are rendered to fill the calendar grid, but they
161
173
  * are not selectable. Setting this prop to `true` will disable this behavior.
162
174
  *
163
- * @defaultValue false
175
+ * @default false
164
176
  */
165
177
  disableDaysOutsideMonth?: boolean;
178
+ /**
179
+ * Whether to automatically reset the range if any date within the selected range
180
+ * becomes disabled. When true, the entire range will be cleared if a disabled
181
+ * date is found between the start and end dates.
182
+ *
183
+ * @default false
184
+ */
185
+ excludeDisabled?: boolean;
166
186
  /**
167
187
  * A callback function called when the start value changes. This doesn't necessarily mean
168
188
  * the `value` has updated and should be used to apply cosmetic changes to the calendar when
@@ -175,6 +195,18 @@ export type RangeCalendarRootPropsWithoutHTML = WithChild<{
175
195
  * only part of the value is changed/completed.
176
196
  */
177
197
  onEndValueChange?: OnChangeFn<DateValue | undefined>;
198
+ /**
199
+ * The format of the month names in the calendar.
200
+ *
201
+ * @default "long"
202
+ */
203
+ monthFormat?: Intl.DateTimeFormatOptions["month"] | ((month: number) => string);
204
+ /**
205
+ * The format of the year names in the calendar.
206
+ *
207
+ * @default "numeric"
208
+ */
209
+ yearFormat?: Intl.DateTimeFormatOptions["year"] | ((year: number) => string);
178
210
  }, RangeCalendarRootSnippetProps>;
179
211
  export type RangeCalendarRootProps = RangeCalendarRootPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, RangeCalendarRootPropsWithoutHTML>;
180
- export type { CalendarPrevButtonProps as RangeCalendarPrevButtonProps, CalendarPrevButtonPropsWithoutHTML as RangeCalendarPrevButtonPropsWithoutHTML, CalendarNextButtonProps as RangeCalendarNextButtonProps, CalendarNextButtonPropsWithoutHTML as RangeCalendarNextButtonPropsWithoutHTML, CalendarHeadingProps as RangeCalendarHeadingProps, CalendarHeadingPropsWithoutHTML as RangeCalendarHeadingPropsWithoutHTML, CalendarGridProps as RangeCalendarGridProps, CalendarGridPropsWithoutHTML as RangeCalendarGridPropsWithoutHTML, CalendarCellProps as RangeCalendarCellProps, CalendarCellPropsWithoutHTML as RangeCalendarCellPropsWithoutHTML, CalendarDayProps as RangeCalendarDayProps, CalendarDayPropsWithoutHTML as RangeCalendarDayPropsWithoutHTML, CalendarGridBodyProps as RangeCalendarGridBodyProps, CalendarGridBodyPropsWithoutHTML as RangeCalendarGridBodyPropsWithoutHTML, CalendarGridHeadProps as RangeCalendarGridHeadProps, CalendarGridHeadPropsWithoutHTML as RangeCalendarGridHeadPropsWithoutHTML, CalendarGridRowProps as RangeCalendarGridRowProps, CalendarGridRowPropsWithoutHTML as RangeCalendarGridRowPropsWithoutHTML, CalendarHeadCellProps as RangeCalendarHeadCellProps, CalendarHeadCellPropsWithoutHTML as RangeCalendarHeadCellPropsWithoutHTML, CalendarHeaderProps as RangeCalendarHeaderProps, CalendarHeaderPropsWithoutHTML as RangeCalendarHeaderPropsWithoutHTML, } from "../calendar/types.js";
212
+ export type { CalendarPrevButtonProps as RangeCalendarPrevButtonProps, CalendarPrevButtonPropsWithoutHTML as RangeCalendarPrevButtonPropsWithoutHTML, CalendarNextButtonProps as RangeCalendarNextButtonProps, CalendarNextButtonPropsWithoutHTML as RangeCalendarNextButtonPropsWithoutHTML, CalendarHeadingProps as RangeCalendarHeadingProps, CalendarHeadingPropsWithoutHTML as RangeCalendarHeadingPropsWithoutHTML, CalendarGridProps as RangeCalendarGridProps, CalendarGridPropsWithoutHTML as RangeCalendarGridPropsWithoutHTML, CalendarCellProps as RangeCalendarCellProps, CalendarCellPropsWithoutHTML as RangeCalendarCellPropsWithoutHTML, CalendarDayProps as RangeCalendarDayProps, CalendarDayPropsWithoutHTML as RangeCalendarDayPropsWithoutHTML, CalendarGridBodyProps as RangeCalendarGridBodyProps, CalendarGridBodyPropsWithoutHTML as RangeCalendarGridBodyPropsWithoutHTML, CalendarGridHeadProps as RangeCalendarGridHeadProps, CalendarGridHeadPropsWithoutHTML as RangeCalendarGridHeadPropsWithoutHTML, CalendarGridRowProps as RangeCalendarGridRowProps, CalendarGridRowPropsWithoutHTML as RangeCalendarGridRowPropsWithoutHTML, CalendarHeadCellProps as RangeCalendarHeadCellProps, CalendarHeadCellPropsWithoutHTML as RangeCalendarHeadCellPropsWithoutHTML, CalendarHeaderProps as RangeCalendarHeaderProps, CalendarHeaderPropsWithoutHTML as RangeCalendarHeaderPropsWithoutHTML, CalendarMonthSelectProps as RangeCalendarMonthSelectProps, CalendarMonthSelectPropsWithoutHTML as RangeCalendarMonthSelectPropsWithoutHTML, CalendarYearSelectProps as RangeCalendarYearSelectProps, CalendarYearSelectPropsWithoutHTML as RangeCalendarYearSelectPropsWithoutHTML, } from "../calendar/types.js";
@@ -1,9 +1,13 @@
1
1
  <script lang="ts">
2
2
  import { box } from "svelte-toolbelt";
3
3
  import { useSelectHiddenInput } from "../select.svelte.js";
4
+ import type { HTMLInputAttributes } from "svelte/elements";
4
5
  import HiddenInput from "../../utilities/hidden-input.svelte";
5
6
 
6
- let { value = $bindable("") }: { value?: string } = $props();
7
+ let {
8
+ value = $bindable(""),
9
+ autocomplete,
10
+ }: { value?: string } & Omit<HTMLInputAttributes, "value"> = $props();
7
11
 
8
12
  const hiddenInputState = useSelectHiddenInput({
9
13
  value: box.with(() => value),
@@ -11,5 +15,5 @@
11
15
  </script>
12
16
 
13
17
  {#if hiddenInputState.shouldRender}
14
- <HiddenInput {...hiddenInputState.props} bind:value />
18
+ <HiddenInput {...hiddenInputState.props} bind:value {autocomplete} />
15
19
  {/if}
@@ -1,6 +1,7 @@
1
+ import type { HTMLInputAttributes } from "svelte/elements";
1
2
  type $$ComponentProps = {
2
3
  value?: string;
3
- };
4
+ } & Omit<HTMLInputAttributes, "value">;
4
5
  declare const SelectHiddenInput: import("svelte").Component<$$ComponentProps, {}, "value">;
5
6
  type SelectHiddenInput = ReturnType<typeof SelectHiddenInput>;
6
7
  export default SelectHiddenInput;
@@ -20,6 +20,7 @@
20
20
  required = false,
21
21
  items = [],
22
22
  allowDeselect = false,
23
+ autocomplete,
23
24
  children,
24
25
  }: SelectRootProps = $props();
25
26
 
@@ -79,9 +80,9 @@
79
80
  {#if Array.isArray(rootState.opts.value.current)}
80
81
  {#if rootState.opts.value.current.length}
81
82
  {#each rootState.opts.value.current as item (item)}
82
- <SelectHiddenInput value={item} />
83
+ <SelectHiddenInput value={item} {autocomplete} />
83
84
  {/each}
84
85
  {/if}
85
86
  {:else}
86
- <SelectHiddenInput bind:value={rootState.opts.value.current as string} />
87
+ <SelectHiddenInput bind:value={rootState.opts.value.current as string} {autocomplete} />
87
88
  {/if}
@@ -5,6 +5,7 @@ import type { ArrowProps, ArrowPropsWithoutHTML } from "../utilities/arrow/types
5
5
  import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
6
6
  import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
7
7
  import type { FloatingContentSnippetProps, StaticContentSnippetProps } from "../../shared/types.js";
8
+ import type { HTMLInputAttributes } from "svelte/elements";
8
9
  export type SelectBaseRootPropsWithoutHTML = WithChildren<{
9
10
  /**
10
11
  * Whether the combobox is disabled.
@@ -74,6 +75,10 @@ export type SelectBaseRootPropsWithoutHTML = WithChildren<{
74
75
  * This is only applicable to `type="single"` selects/comboboxes.
75
76
  */
76
77
  allowDeselect?: boolean;
78
+ /**
79
+ * The autocomplete attribute to forward to the hidden input element.
80
+ */
81
+ autocomplete?: HTMLInputAttributes["autocomplete"];
77
82
  }>;
78
83
  export type SelectSingleRootPropsWithoutHTML = {
79
84
  /**
@@ -181,7 +181,7 @@ export declare function getCalendarElementProps({ fullCalendarLabel, id, isInval
181
181
  readonly "data-disabled": "" | undefined;
182
182
  readonly "data-readonly": "" | undefined;
183
183
  };
184
- export type CalendarParts = "root" | "grid" | "cell" | "next-button" | "prev-button" | "day" | "grid-body" | "grid-head" | "grid-row" | "head-cell" | "header" | "heading";
184
+ export type CalendarParts = "root" | "grid" | "cell" | "next-button" | "prev-button" | "day" | "grid-body" | "grid-head" | "grid-row" | "head-cell" | "header" | "heading" | "month-select" | "year-select";
185
185
  export declare function pickerOpenFocus(e: Event): void;
186
186
  export declare function getFirstNonDisabledDateInView(calendarRef: HTMLElement): DateValue | undefined;
187
187
  /**
@@ -198,5 +198,11 @@ export declare function useEnsureNonDisabledPlaceholder({ ref, placeholder, defa
198
198
  defaultPlaceholder: DateValue;
199
199
  }): void;
200
200
  export declare function getDateWithPreviousTime(date: DateValue | undefined, prev: DateValue | undefined): DateValue | undefined;
201
- export declare const calendarAttrs: import("../attrs.js").BitsAttrs<readonly ["root", "grid", "cell", "next-button", "prev-button", "day", "grid-body", "grid-head", "grid-row", "head-cell", "header", "heading"]>;
201
+ export declare const calendarAttrs: import("../attrs.js").BitsAttrs<readonly ["root", "grid", "cell", "next-button", "prev-button", "day", "grid-body", "grid-head", "grid-row", "head-cell", "header", "heading", "month-select", "year-select"]>;
202
+ type GetDefaultYearsProps = {
203
+ placeholderYear: number;
204
+ minValue: DateValue | undefined;
205
+ maxValue: DateValue | undefined;
206
+ };
207
+ export declare function getDefaultYears(opts: GetDefaultYearsProps): number[];
202
208
  export {};
@@ -309,21 +309,23 @@ export function getWeekdays({ months, formatter, weekdayFormat }) {
309
309
  * which determines the month to show in the calendar.
310
310
  */
311
311
  export function useMonthViewOptionsSync(props) {
312
- const weekStartsOn = props.weekStartsOn.current;
313
- const locale = props.locale.current;
314
- const fixedWeeks = props.fixedWeeks.current;
315
- const numberOfMonths = props.numberOfMonths.current;
316
- untrack(() => {
317
- const placeholder = props.placeholder.current;
318
- if (!placeholder)
319
- return;
320
- const defaultMonthProps = {
321
- weekStartsOn,
322
- locale,
323
- fixedWeeks,
324
- numberOfMonths,
325
- };
326
- props.setMonths(createMonths({ ...defaultMonthProps, dateObj: placeholder }));
312
+ $effect(() => {
313
+ const weekStartsOn = props.weekStartsOn.current;
314
+ const locale = props.locale.current;
315
+ const fixedWeeks = props.fixedWeeks.current;
316
+ const numberOfMonths = props.numberOfMonths.current;
317
+ untrack(() => {
318
+ const placeholder = props.placeholder.current;
319
+ if (!placeholder)
320
+ return;
321
+ const defaultMonthProps = {
322
+ weekStartsOn,
323
+ locale,
324
+ fixedWeeks,
325
+ numberOfMonths,
326
+ };
327
+ props.setMonths(createMonths({ ...defaultMonthProps, dateObj: placeholder }));
328
+ });
327
329
  });
328
330
  }
329
331
  /**
@@ -529,5 +531,35 @@ export const calendarAttrs = createBitsAttrs({
529
531
  "head-cell",
530
532
  "header",
531
533
  "heading",
534
+ "month-select",
535
+ "year-select",
532
536
  ],
533
537
  });
538
+ export function getDefaultYears(opts) {
539
+ const currentYear = new Date().getFullYear();
540
+ const latestYear = Math.max(opts.placeholderYear, currentYear);
541
+ // use minValue/maxValue as boundaries if provided, otherwise calculate default range
542
+ let minYear;
543
+ let maxYear;
544
+ if (opts.minValue) {
545
+ minYear = opts.minValue.year;
546
+ }
547
+ else {
548
+ // (111 years: latestYear - 100 to latestYear + 10)
549
+ const initialMinYear = latestYear - 100;
550
+ minYear =
551
+ opts.placeholderYear < initialMinYear ? opts.placeholderYear - 10 : initialMinYear;
552
+ }
553
+ if (opts.maxValue) {
554
+ maxYear = opts.maxValue.year;
555
+ }
556
+ else {
557
+ maxYear = latestYear + 10;
558
+ }
559
+ // ensure we have at least one year and minYear <= maxYear
560
+ if (minYear > maxYear) {
561
+ minYear = maxYear;
562
+ }
563
+ const totalYears = maxYear - minYear + 1;
564
+ return Array.from({ length: totalYears }, (_, i) => minYear + i);
565
+ }
@@ -1,7 +1,13 @@
1
1
  import { type DateValue } from "@internationalized/date";
2
2
  import type { HourCycle, TimeValue } from "../../shared/date/types.js";
3
+ import type { ReadableBox } from "svelte-toolbelt";
3
4
  export type Formatter = ReturnType<typeof createFormatter>;
4
5
  export type TimeFormatter = ReturnType<typeof createTimeFormatter>;
6
+ type CreateFormatterOptions = {
7
+ initialLocale: string;
8
+ monthFormat: ReadableBox<Intl.DateTimeFormatOptions["month"] | ((month: number) => string)>;
9
+ yearFormat: ReadableBox<Intl.DateTimeFormatOptions["year"] | ((year: number) => string)>;
10
+ };
5
11
  /**
6
12
  * Creates a wrapper around the `DateFormatter`, which is
7
13
  * an improved version of the {@link Intl.DateTimeFormat} API,
@@ -10,7 +16,7 @@ export type TimeFormatter = ReturnType<typeof createTimeFormatter>;
10
16
  *
11
17
  * @see [DateFormatter](https://react-spectrum.adobe.com/internationalized/date/DateFormatter.html)
12
18
  */
13
- export declare function createFormatter(initialLocale: string): {
19
+ export declare function createFormatter(opts: CreateFormatterOptions): {
14
20
  setLocale: (newLocale: string) => void;
15
21
  getLocale: () => string;
16
22
  fullMonth: (date: Date) => string;
@@ -32,3 +38,4 @@ export declare function createTimeFormatter(initialLocale: string): {
32
38
  dayPeriod: (date: Date, hourCycle?: HourCycle | undefined) => "AM" | "PM";
33
39
  selectedTime: (date: TimeValue) => string;
34
40
  };
41
+ export {};
@@ -17,8 +17,8 @@ const defaultPartOptions = {
17
17
  *
18
18
  * @see [DateFormatter](https://react-spectrum.adobe.com/internationalized/date/DateFormatter.html)
19
19
  */
20
- export function createFormatter(initialLocale) {
21
- let locale = initialLocale;
20
+ export function createFormatter(opts) {
21
+ let locale = opts.initialLocale;
22
22
  function setLocale(newLocale) {
23
23
  locale = newLocale;
24
24
  }
@@ -42,7 +42,20 @@ export function createFormatter(initialLocale) {
42
42
  }
43
43
  }
44
44
  function fullMonthAndYear(date) {
45
- return new DateFormatter(locale, { month: "long", year: "numeric" }).format(date);
45
+ if (typeof opts.monthFormat.current !== "function" &&
46
+ typeof opts.yearFormat.current !== "function") {
47
+ return new DateFormatter(locale, {
48
+ month: opts.monthFormat.current,
49
+ year: opts.yearFormat.current,
50
+ }).format(date);
51
+ }
52
+ const formattedMonth = typeof opts.monthFormat.current === "function"
53
+ ? opts.monthFormat.current(date.getMonth() + 1)
54
+ : new DateFormatter(locale, { month: opts.monthFormat.current }).format(date);
55
+ const formattedYear = typeof opts.yearFormat.current === "function"
56
+ ? opts.yearFormat.current(date.getFullYear())
57
+ : new DateFormatter(locale, { year: opts.yearFormat.current }).format(date);
58
+ return `${formattedMonth} ${formattedYear}`;
46
59
  }
47
60
  function fullMonth(date) {
48
61
  return new DateFormatter(locale, { month: "long" }).format(date);
@@ -1,4 +1,4 @@
1
- import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes, HTMLImgAttributes, HTMLInputAttributes, HTMLLabelAttributes, HTMLLiAttributes, HTMLTableAttributes, HTMLTdAttributes, HTMLThAttributes, SVGAttributes } from "svelte/elements";
1
+ import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes, HTMLImgAttributes, HTMLInputAttributes, HTMLLabelAttributes, HTMLLiAttributes, HTMLSelectAttributes, HTMLTableAttributes, HTMLTdAttributes, HTMLThAttributes, SVGAttributes } from "svelte/elements";
2
2
  export type BitsDivAttributes = HTMLAttributes<HTMLDivElement>;
3
3
  export type BitsSpanAttributes = HTMLAttributes<HTMLSpanElement>;
4
4
  export type BitsHeadingAttributes = HTMLAttributes<HTMLHeadingElement>;
@@ -7,6 +7,7 @@ export type BitsElementAttributes = HTMLAttributes<HTMLElement>;
7
7
  export type BitsTableSectionAttributes = HTMLAttributes<HTMLTableSectionElement>;
8
8
  export type BitsTableRowAttributes = HTMLAttributes<HTMLTableRowElement>;
9
9
  export type BitsSVGElementAttributes = SVGAttributes<SVGElement>;
10
+ export type BitsSelectAttributes = HTMLSelectAttributes;
10
11
  /**
11
12
  * We override the `id` prop type to not allow it to be `null`. We rely on the
12
13
  * `id` heavily in the internals of Bits UI for node references. We also override
@@ -35,4 +36,5 @@ export type BitsPrimitiveTbodyAttributes = BitsPrimitive<BitsTableSectionAttribu
35
36
  export type BitsPrimitiveTrAttributes = BitsPrimitive<BitsTableRowAttributes>;
36
37
  export type BitsPrimitiveTheadAttributes = BitsPrimitive<BitsTableSectionAttributes>;
37
38
  export type BitsPrimitiveHeaderAttributes = BitsPrimitive<BitsElementAttributes>;
39
+ export type BitsPrimitiveSelectAttributes = BitsPrimitive<BitsSelectAttributes>;
38
40
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "2.4.1",
3
+ "version": "2.5.0",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",