bits-ui 1.3.18 → 1.4.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.
@@ -7,7 +7,7 @@ import { getAriaDisabled, getAriaHidden, getAriaReadonly, getAriaSelected, getDa
7
7
  import { useId } from "../../internal/use-id.js";
8
8
  import { getAnnouncer } from "../../internal/date-time/announcer.js";
9
9
  import { createFormatter } from "../../internal/date-time/formatter.js";
10
- import { createAccessibleHeading, createMonths, getCalendarElementProps, getCalendarHeadingValue, getIsNextButtonDisabled, getIsPrevButtonDisabled, getWeekdays, handleCalendarKeydown, handleCalendarNextPage, handleCalendarPrevPage, shiftCalendarFocus, useEnsureNonDisabledPlaceholder, useMonthViewOptionsSync, useMonthViewPlaceholderSync, } from "../../internal/date-time/calendar-helpers.svelte.js";
10
+ import { createAccessibleHeading, createMonths, getCalendarElementProps, getCalendarHeadingValue, getDateWithPreviousTime, getIsNextButtonDisabled, getIsPrevButtonDisabled, getWeekdays, handleCalendarKeydown, handleCalendarNextPage, handleCalendarPrevPage, shiftCalendarFocus, useEnsureNonDisabledPlaceholder, useMonthViewOptionsSync, useMonthViewPlaceholderSync, } from "../../internal/date-time/calendar-helpers.svelte.js";
11
11
  import { getDateValueType, isBefore, toDate } from "../../internal/date-time/utils.js";
12
12
  export class CalendarRootState {
13
13
  opts;
@@ -258,9 +258,7 @@ export class CalendarRootState {
258
258
  else if (!value) {
259
259
  return false;
260
260
  }
261
- else {
262
- return isSameDay(value, date);
263
- }
261
+ return isSameDay(value, date);
264
262
  }
265
263
  shiftFocus(node, add) {
266
264
  return shiftCalendarFocus({
@@ -275,13 +273,12 @@ export class CalendarRootState {
275
273
  });
276
274
  }
277
275
  handleCellClick(_, date) {
278
- const readonly = this.opts.readonly.current;
279
- if (readonly)
276
+ if (this.opts.readonly.current)
280
277
  return;
281
- const isDateDisabled = this.opts.isDateDisabled.current;
282
- const isDateUnavailable = this.opts.isDateUnavailable.current;
283
- if (isDateDisabled?.(date) || isDateUnavailable?.(date))
278
+ if (this.opts.isDateDisabled.current?.(date) ||
279
+ this.opts.isDateUnavailable.current?.(date)) {
284
280
  return;
281
+ }
285
282
  const prev = this.opts.value.current;
286
283
  const multiple = this.opts.type.current === "multiple";
287
284
  if (multiple) {
@@ -289,19 +286,17 @@ export class CalendarRootState {
289
286
  this.opts.value.current = this.handleMultipleUpdate(prev, date);
290
287
  }
291
288
  }
292
- else {
293
- if (!Array.isArray(prev)) {
294
- const next = this.handleSingleUpdate(prev, date);
295
- if (!next) {
296
- this.announcer.announce("Selected date is now empty.", "polite", 5000);
297
- }
298
- else {
299
- this.announcer.announce(`Selected Date: ${this.formatter.selectedDate(next, false)}`, "polite");
300
- }
301
- this.opts.value.current = next;
302
- if (next !== undefined) {
303
- this.opts.onDateSelect?.current?.();
304
- }
289
+ else if (!Array.isArray(prev)) {
290
+ const next = this.handleSingleUpdate(prev, date);
291
+ if (!next) {
292
+ this.announcer.announce("Selected date is now empty.", "polite", 5000);
293
+ }
294
+ else {
295
+ this.announcer.announce(`Selected Date: ${this.formatter.selectedDate(next, false)}`, "polite");
296
+ }
297
+ this.opts.value.current = getDateWithPreviousTime(next, prev);
298
+ if (next !== undefined) {
299
+ this.opts.onDateSelect?.current?.();
305
300
  }
306
301
  }
307
302
  }
@@ -45,7 +45,7 @@
45
45
  }
46
46
 
47
47
  if (value === undefined) {
48
- const defaultValue = type === "single" ? "" : [];
48
+ const defaultValue = type === "single" ? undefined : [];
49
49
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
50
  value = defaultValue as any;
51
51
  }
@@ -29,6 +29,20 @@ const COMMAND_VALID_ITEM_SELECTOR = `${COMMAND_ITEM_SELECTOR}:not([aria-disabled
29
29
  const CommandRootContext = new Context("Command.Root");
30
30
  const CommandListContext = new Context("Command.List");
31
31
  const CommandGroupContainerContext = new Context("Command.Group");
32
+ const defaultState = {
33
+ /** Value of the search query */
34
+ search: "",
35
+ /** Currently selected item value */
36
+ value: "",
37
+ filtered: {
38
+ /** The count of all visible items. */
39
+ count: 0,
40
+ /** Map from visible item id to its search store. */
41
+ items: new Map(),
42
+ /** Set of groups with at least one visible item. */
43
+ groups: new Set(),
44
+ },
45
+ };
32
46
  class CommandRootState {
33
47
  opts;
34
48
  #updateScheduled = false;
@@ -43,9 +57,9 @@ class CommandRootState {
43
57
  inputNode = $state(null);
44
58
  labelNode = $state(null);
45
59
  // published state that the components and other things can react to
46
- commandState = $state.raw(null);
60
+ commandState = $state.raw(defaultState);
47
61
  // internal state that we mutate in batches and publish to the `state` at once
48
- _commandState = $state(null);
62
+ _commandState = $state(defaultState);
49
63
  // whether the search has had a value other than ""
50
64
  searchHasHadValue = $state(false);
51
65
  #snapshot() {
@@ -89,22 +103,9 @@ class CommandRootState {
89
103
  }
90
104
  constructor(opts) {
91
105
  this.opts = opts;
92
- const defaultState = {
93
- /** Value of the search query */
94
- search: "",
95
- /** Currently selected item value */
96
- value: this.opts.value.current ?? "",
97
- filtered: {
98
- /** The count of all visible items. */
99
- count: 0,
100
- /** Map from visible item id to its search store. */
101
- items: new Map(),
102
- /** Set of groups with at least one visible item. */
103
- groups: new Set(),
104
- },
105
- };
106
- this._commandState = defaultState;
107
- this.commandState = defaultState;
106
+ const defaults = { ...this._commandState, value: this.opts.value.current ?? "" };
107
+ this._commandState = defaults;
108
+ this.commandState = defaults;
108
109
  useRefById(opts);
109
110
  this.onkeydown = this.onkeydown.bind(this);
110
111
  $effect(() => {
@@ -5,5 +5,4 @@ export { default as ContentStatic } from "./components/popover-content-static.sv
5
5
  export { default as Trigger } from "./components/popover-trigger.svelte";
6
6
  export { default as Close } from "./components/popover-close.svelte";
7
7
  export { default as Portal } from "../utilities/portal/portal.svelte";
8
- export type { PopoverRootProps as RootProps, PopoverArrowProps as ArrowProps, PopoverContentProps as ContentProps, PopoverContentStaticProps as ContentStaticProps, PopoverTriggerProps as TriggerProps, PopoverCloseProps as CloseProps, } from "./types.js";
9
- export type { PortalProps } from "../utilities/portal/types.js";
8
+ export type { PopoverRootProps as RootProps, PopoverArrowProps as ArrowProps, PopoverContentProps as ContentProps, PopoverContentStaticProps as ContentStaticProps, PopoverTriggerProps as TriggerProps, PopoverCloseProps as CloseProps, PopoverPortalProps as PortalProps, } from "./types.js";
@@ -3,6 +3,7 @@ import type { PopperLayerProps, PopperLayerStaticProps } from "../utilities/popp
3
3
  import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
4
4
  import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
5
5
  import type { FloatingContentSnippetProps, StaticContentSnippetProps } from "../../shared/types.js";
6
+ import type { PortalProps } from "../../types.js";
6
7
  export type PopoverRootPropsWithoutHTML = WithChildren<{
7
8
  /**
8
9
  * The open state of the popover.
@@ -24,3 +25,5 @@ export type PopoverClosePropsWithoutHTML = WithChild;
24
25
  export type PopoverCloseProps = PopoverClosePropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, PopoverClosePropsWithoutHTML>;
25
26
  export type PopoverArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
26
27
  export type PopoverArrowProps = ArrowProps;
28
+ export type PopoverPortalPropsWithoutHTML = PortalProps;
29
+ export type PopoverPortalProps = PortalProps;
@@ -8,6 +8,7 @@
8
8
  let {
9
9
  id = useId(),
10
10
  ref = $bindable(null),
11
+ delay = () => 50,
11
12
  child,
12
13
  children,
13
14
  ...restProps
@@ -19,6 +20,7 @@
19
20
  () => ref,
20
21
  (v) => (ref = v)
21
22
  ),
23
+ delay: box.with(() => delay),
22
24
  });
23
25
 
24
26
  const mergedProps = $derived(mergeProps(restProps, scrollButtonState.props));
@@ -8,6 +8,7 @@
8
8
  let {
9
9
  id = useId(),
10
10
  ref = $bindable(null),
11
+ delay = () => 50,
11
12
  child,
12
13
  children,
13
14
  ...restProps
@@ -19,6 +20,7 @@
19
20
  () => ref,
20
21
  (v) => (ref = v)
21
22
  ),
23
+ delay: box.with(() => delay),
22
24
  });
23
25
 
24
26
  const mergedProps = $derived(mergeProps(restProps, scrollButtonState.props));
@@ -304,12 +304,14 @@ declare class SelectViewportState {
304
304
  };
305
305
  };
306
306
  }
307
- type SelectScrollButtonImplStateProps = WithRefProps;
307
+ type SelectScrollButtonImplStateProps = WithRefProps & ReadableBoxedValues<{
308
+ delay: (tick: number) => number;
309
+ }>;
308
310
  declare class SelectScrollButtonImplState {
309
311
  readonly opts: SelectScrollButtonImplStateProps;
310
312
  readonly content: SelectContentState;
311
313
  root: SelectBaseRootState;
312
- autoScrollInterval: number | null;
314
+ autoScrollTimer: number | null;
313
315
  userScrollTimer: number;
314
316
  isUserScrolling: boolean;
315
317
  onAutoScroll: () => void;
@@ -318,7 +320,7 @@ declare class SelectScrollButtonImplState {
318
320
  handleUserScroll(): void;
319
321
  clearAutoScrollInterval(): void;
320
322
  onpointerdown(_: BitsPointerEvent): void;
321
- onpointermove(_: BitsPointerEvent): void;
323
+ onpointermove(e: BitsPointerEvent): void;
322
324
  onpointerleave(_: BitsPointerEvent): void;
323
325
  props: {
324
326
  readonly id: string;
@@ -327,7 +329,7 @@ declare class SelectScrollButtonImplState {
327
329
  readonly flexShrink: 0;
328
330
  };
329
331
  readonly onpointerdown: (_: BitsPointerEvent) => void;
330
- readonly onpointermove: (_: BitsPointerEvent) => void;
332
+ readonly onpointermove: (e: BitsPointerEvent) => void;
331
333
  readonly onpointerleave: (_: BitsPointerEvent) => void;
332
334
  };
333
335
  }
@@ -351,7 +353,7 @@ declare class SelectScrollDownButtonState {
351
353
  readonly flexShrink: 0;
352
354
  };
353
355
  readonly onpointerdown: (_: BitsPointerEvent) => void;
354
- readonly onpointermove: (_: BitsPointerEvent) => void;
356
+ readonly onpointermove: (e: BitsPointerEvent) => void;
355
357
  readonly onpointerleave: (_: BitsPointerEvent) => void;
356
358
  };
357
359
  }
@@ -374,7 +376,7 @@ declare class SelectScrollUpButtonState {
374
376
  readonly flexShrink: 0;
375
377
  };
376
378
  readonly onpointerdown: (_: BitsPointerEvent) => void;
377
- readonly onpointermove: (_: BitsPointerEvent) => void;
379
+ readonly onpointermove: (e: BitsPointerEvent) => void;
378
380
  readonly onpointerleave: (_: BitsPointerEvent) => void;
379
381
  };
380
382
  }
@@ -55,7 +55,7 @@ class SelectBaseRootState {
55
55
  setHighlightedNode(node, initial = false) {
56
56
  this.highlightedNode = node;
57
57
  if (node && (this.isUsingKeyboard || initial)) {
58
- node.scrollIntoView({ block: "nearest" });
58
+ node.scrollIntoView({ block: this.opts.scrollAlignment.current });
59
59
  }
60
60
  }
61
61
  getCandidateNodes() {
@@ -921,7 +921,7 @@ class SelectScrollButtonImplState {
921
921
  opts;
922
922
  content;
923
923
  root;
924
- autoScrollInterval = null;
924
+ autoScrollTimer = null;
925
925
  userScrollTimer = -1;
926
926
  isUserScrolling = false;
927
927
  onAutoScroll = noop;
@@ -959,24 +959,22 @@ class SelectScrollButtonImplState {
959
959
  }, 200);
960
960
  }
961
961
  clearAutoScrollInterval() {
962
- if (this.autoScrollInterval === null)
962
+ if (this.autoScrollTimer === null)
963
963
  return;
964
- window.clearInterval(this.autoScrollInterval);
965
- this.autoScrollInterval = null;
964
+ window.clearTimeout(this.autoScrollTimer);
965
+ this.autoScrollTimer = null;
966
966
  }
967
967
  onpointerdown(_) {
968
- if (this.autoScrollInterval !== null)
968
+ if (this.autoScrollTimer !== null)
969
969
  return;
970
- this.autoScrollInterval = window.setInterval(() => {
970
+ const autoScroll = (tick) => {
971
971
  this.onAutoScroll();
972
- }, 50);
972
+ this.autoScrollTimer = window.setTimeout(() => autoScroll(tick + 1), this.opts.delay.current(tick));
973
+ };
974
+ this.autoScrollTimer = window.setTimeout(() => autoScroll(1), this.opts.delay.current(0));
973
975
  }
974
- onpointermove(_) {
975
- if (this.autoScrollInterval !== null)
976
- return;
977
- this.autoScrollInterval = window.setInterval(() => {
978
- this.onAutoScroll();
979
- }, 50);
976
+ onpointermove(e) {
977
+ this.onpointerdown(e);
980
978
  }
981
979
  onpointerleave(_) {
982
980
  this.clearAutoScrollInterval();
@@ -1018,7 +1016,7 @@ class SelectScrollDownButtonState {
1018
1016
  }
1019
1017
  this.scrollIntoViewTimer = afterSleep(5, () => {
1020
1018
  const activeItem = this.root.highlightedNode;
1021
- activeItem?.scrollIntoView({ block: "nearest" });
1019
+ activeItem?.scrollIntoView({ block: this.root.opts.scrollAlignment.current });
1022
1020
  });
1023
1021
  });
1024
1022
  }
@@ -177,7 +177,16 @@ export type SelectArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
177
177
  export type SelectArrowProps = ArrowProps;
178
178
  export type SelectViewportPropsWithoutHTML = WithChild;
179
179
  export type SelectViewportProps = SelectViewportPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectViewportPropsWithoutHTML>;
180
- export type SelectScrollUpButtonPropsWithoutHTML = WithChild;
180
+ export type SelectScrollButtonPropsWithoutHTML = WithChild<{
181
+ /**
182
+ * Controls the initial delay (tick 0) and delay between auto-scrolls in milliseconds.
183
+ * The default function always returns 50ms.
184
+ *
185
+ * @param tick current tick number
186
+ */
187
+ delay?: (tick: number) => number;
188
+ }>;
189
+ export type SelectScrollUpButtonPropsWithoutHTML = SelectScrollButtonPropsWithoutHTML;
181
190
  export type SelectScrollUpButtonProps = SelectScrollUpButtonPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectScrollUpButtonPropsWithoutHTML>;
182
- export type SelectScrollDownButtonPropsWithoutHTML = WithChild;
191
+ export type SelectScrollDownButtonPropsWithoutHTML = SelectScrollButtonPropsWithoutHTML;
183
192
  export type SelectScrollDownButtonProps = SelectScrollDownButtonPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectScrollDownButtonPropsWithoutHTML>;
@@ -229,8 +229,8 @@ class SliderSingleRootState extends SliderBaseRootState {
229
229
  }
230
230
  const currValue = this.opts.value.current;
231
231
  return Array.from({ length: count }, (_, i) => {
232
- const tickPosition = i * (step / difference) * 100;
233
- const scale = linearScale([this.opts.min.current, this.opts.max.current], this.getThumbScale());
232
+ const tickPosition = i * step;
233
+ const scale = linearScale([0, (count - 1) * step], this.getThumbScale());
234
234
  const isFirst = i === 0;
235
235
  const isLast = i === count - 1;
236
236
  const offsetPercentage = isFirst ? 0 : isLast ? -100 : -50;
@@ -484,11 +484,12 @@ class SliderMultiRootState extends SliderBaseRootState {
484
484
  }
485
485
  const currValue = this.opts.value.current;
486
486
  return Array.from({ length: count }, (_, i) => {
487
- const tickPosition = i * (step / difference) * 100;
487
+ const tickPosition = i * step;
488
+ const scale = linearScale([0, (count - 1) * step], this.getThumbScale());
488
489
  const isFirst = i === 0;
489
490
  const isLast = i === count - 1;
490
491
  const offsetPercentage = isFirst ? 0 : isLast ? -100 : -50;
491
- const style = getTickStyles(this.direction, tickPosition, offsetPercentage);
492
+ const style = getTickStyles(this.direction, scale(tickPosition), offsetPercentage);
492
493
  const tickValue = min + i * step;
493
494
  const bounded = currValue.length === 1
494
495
  ? tickValue <= currValue[0]
@@ -125,7 +125,7 @@ export function useFocusScope({ id, loop, enabled, onOpenAutoFocus, onCloseAutoF
125
125
  }
126
126
  function handleClose(prevFocusedElement) {
127
127
  const destroyEvent = AutoFocusOnDestroyEvent.createEvent();
128
- onCloseAutoFocus.current(destroyEvent);
128
+ onCloseAutoFocus.current?.(destroyEvent);
129
129
  const shouldIgnore = ctx.ignoreCloseAutoFocus;
130
130
  afterSleep(0, () => {
131
131
  if (!destroyEvent.defaultPrevented && prevFocusedElement && !shouldIgnore) {
@@ -197,4 +197,5 @@ export declare function useEnsureNonDisabledPlaceholder({ ref, placeholder, defa
197
197
  maxValue: ReadableBox<DateValue | undefined>;
198
198
  defaultPlaceholder: DateValue;
199
199
  }): void;
200
+ export declare function getDateWithPreviousTime(date: DateValue | undefined, prev: DateValue | undefined): DateValue | undefined;
200
201
  export {};
@@ -1,7 +1,7 @@
1
1
  import { endOfMonth, isSameDay, isSameMonth, startOfMonth, } from "@internationalized/date";
2
2
  import { afterTick, styleToString } from "svelte-toolbelt";
3
3
  import { untrack } from "svelte";
4
- import { getDaysInMonth, getLastFirstDayOfWeek, getNextLastDayOfWeek, isAfter, isBefore, parseAnyDateValue, parseStringToDateValue, toDate, } from "./utils.js";
4
+ import { getDaysInMonth, getLastFirstDayOfWeek, getNextLastDayOfWeek, hasTime, isAfter, isBefore, parseAnyDateValue, parseStringToDateValue, toDate, } from "./utils.js";
5
5
  import { getDataDisabled, getDataInvalid, getDataReadonly } from "../attrs.js";
6
6
  import { chunk, isValidIndex } from "../arrays.js";
7
7
  import { isBrowser, isHTMLElement } from "../is.js";
@@ -495,3 +495,16 @@ export function useEnsureNonDisabledPlaceholder({ ref, placeholder, defaultPlace
495
495
  }
496
496
  });
497
497
  }
498
+ export function getDateWithPreviousTime(date, prev) {
499
+ if (!date || !prev)
500
+ return date;
501
+ if (hasTime(date) && hasTime(prev)) {
502
+ return date.set({
503
+ hour: prev.hour,
504
+ minute: prev.minute,
505
+ millisecond: prev.millisecond,
506
+ second: prev.second,
507
+ });
508
+ }
509
+ return date;
510
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bits-ui",
3
- "version": "1.3.18",
3
+ "version": "1.4.0",
4
4
  "license": "MIT",
5
5
  "repository": "github:huntabyte/bits-ui",
6
6
  "funding": "https://github.com/sponsors/huntabyte",