bits-ui 1.0.0-next.17 → 1.0.0-next.19

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 (97) hide show
  1. package/dist/bits/combobox/components/combobox-input.svelte +2 -2
  2. package/dist/bits/combobox/components/combobox-trigger.svelte +2 -2
  3. package/dist/bits/combobox/components/combobox.svelte +9 -7
  4. package/dist/bits/combobox/exports.d.ts +8 -8
  5. package/dist/bits/combobox/exports.js +8 -8
  6. package/dist/bits/combobox/types.d.ts +1 -1
  7. package/dist/bits/dialog/components/dialog-content.svelte +1 -1
  8. package/dist/bits/index.d.ts +0 -1
  9. package/dist/bits/index.js +0 -1
  10. package/dist/bits/menu/menu.svelte.js +2 -2
  11. package/dist/bits/menubar/menubar.svelte.js +1 -1
  12. package/dist/bits/{listbox/components/listbox-content-static.svelte → select/components/select-content-static.svelte} +6 -6
  13. package/dist/bits/{listbox/components/listbox-group-heading.svelte.d.ts → select/components/select-content-static.svelte.d.ts} +4 -4
  14. package/dist/bits/select/components/select-content.svelte +56 -35
  15. package/dist/bits/select/components/select-group-heading.svelte +3 -3
  16. package/dist/bits/select/components/select-group.svelte +4 -3
  17. package/dist/bits/{listbox/components/listbox-hidden-input.svelte → select/components/select-hidden-input.svelte} +4 -4
  18. package/dist/bits/{listbox/components/listbox-hidden-input.svelte.d.ts → select/components/select-hidden-input.svelte.d.ts} +4 -4
  19. package/dist/bits/select/components/select-item.svelte +14 -9
  20. package/dist/bits/select/components/select-scroll-down-button.svelte +20 -9
  21. package/dist/bits/select/components/select-scroll-down-button.svelte.d.ts +2 -2
  22. package/dist/bits/select/components/select-scroll-up-button.svelte +21 -10
  23. package/dist/bits/select/components/select-trigger.svelte +5 -7
  24. package/dist/bits/select/components/select-viewport.svelte +10 -0
  25. package/dist/bits/select/components/select.svelte +54 -56
  26. package/dist/bits/select/exports.d.ts +4 -7
  27. package/dist/bits/select/exports.js +3 -6
  28. package/dist/bits/select/select.svelte.d.ts +272 -258
  29. package/dist/bits/select/select.svelte.js +764 -831
  30. package/dist/bits/select/types.d.ts +128 -79
  31. package/dist/bits/slider/components/slider.svelte +2 -2
  32. package/dist/bits/slider/slider.svelte.d.ts +2 -2
  33. package/dist/bits/slider/slider.svelte.js +4 -4
  34. package/dist/bits/slider/types.d.ts +3 -2
  35. package/dist/bits/utilities/popper-layer/popper-layer.svelte +1 -3
  36. package/dist/internal/arrays.d.ts +23 -0
  37. package/dist/internal/arrays.js +35 -0
  38. package/dist/internal/date-time/calendar-helpers.svelte.js +6 -1
  39. package/dist/internal/use-body-scroll-lock.svelte.js +53 -42
  40. package/dist/internal/use-data-typeahead.svelte.d.ts +12 -0
  41. package/dist/internal/use-data-typeahead.svelte.js +30 -0
  42. package/dist/internal/use-dom-typeahead.svelte.d.ts +11 -0
  43. package/dist/internal/use-dom-typeahead.svelte.js +30 -0
  44. package/dist/types.d.ts +1 -1
  45. package/package.json +2 -2
  46. package/dist/bits/listbox/components/listbox-content-static.svelte.d.ts +0 -19
  47. package/dist/bits/listbox/components/listbox-content.svelte +0 -76
  48. package/dist/bits/listbox/components/listbox-content.svelte.d.ts +0 -19
  49. package/dist/bits/listbox/components/listbox-group-heading.svelte +0 -32
  50. package/dist/bits/listbox/components/listbox-group.svelte +0 -32
  51. package/dist/bits/listbox/components/listbox-group.svelte.d.ts +0 -19
  52. package/dist/bits/listbox/components/listbox-item.svelte +0 -43
  53. package/dist/bits/listbox/components/listbox-item.svelte.d.ts +0 -19
  54. package/dist/bits/listbox/components/listbox-scroll-down-button.svelte +0 -39
  55. package/dist/bits/listbox/components/listbox-scroll-down-button.svelte.d.ts +0 -19
  56. package/dist/bits/listbox/components/listbox-scroll-up-button.svelte +0 -39
  57. package/dist/bits/listbox/components/listbox-scroll-up-button.svelte.d.ts +0 -19
  58. package/dist/bits/listbox/components/listbox-trigger.svelte +0 -35
  59. package/dist/bits/listbox/components/listbox-trigger.svelte.d.ts +0 -19
  60. package/dist/bits/listbox/components/listbox-viewport.svelte +0 -54
  61. package/dist/bits/listbox/components/listbox-viewport.svelte.d.ts +0 -19
  62. package/dist/bits/listbox/components/listbox.svelte +0 -81
  63. package/dist/bits/listbox/components/listbox.svelte.d.ts +0 -18
  64. package/dist/bits/listbox/exports.d.ts +0 -12
  65. package/dist/bits/listbox/exports.js +0 -11
  66. package/dist/bits/listbox/index.d.ts +0 -1
  67. package/dist/bits/listbox/index.js +0 -1
  68. package/dist/bits/listbox/listbox.svelte.d.ts +0 -399
  69. package/dist/bits/listbox/listbox.svelte.js +0 -973
  70. package/dist/bits/listbox/types.d.ts +0 -177
  71. package/dist/bits/listbox/types.js +0 -1
  72. package/dist/bits/select/components/select-arrow.svelte +0 -21
  73. package/dist/bits/select/components/select-arrow.svelte.d.ts +0 -18
  74. package/dist/bits/select/components/select-content-floating.svelte +0 -73
  75. package/dist/bits/select/components/select-content-floating.svelte.d.ts +0 -33
  76. package/dist/bits/select/components/select-content-impl.svelte +0 -90
  77. package/dist/bits/select/components/select-content-impl.svelte.d.ts +0 -90
  78. package/dist/bits/select/components/select-content-item-aligned.svelte +0 -37
  79. package/dist/bits/select/components/select-content-item-aligned.svelte.d.ts +0 -29
  80. package/dist/bits/select/components/select-icon.svelte +0 -36
  81. package/dist/bits/select/components/select-icon.svelte.d.ts +0 -19
  82. package/dist/bits/select/components/select-item-text.svelte +0 -39
  83. package/dist/bits/select/components/select-item-text.svelte.d.ts +0 -19
  84. package/dist/bits/select/components/select-native.svelte +0 -36
  85. package/dist/bits/select/components/select-native.svelte.d.ts +0 -30
  86. package/dist/bits/select/components/select-provider.svelte +0 -10
  87. package/dist/bits/select/components/select-provider.svelte.d.ts +0 -23
  88. package/dist/bits/select/components/select-scroll-down-button-mounted.svelte +0 -33
  89. package/dist/bits/select/components/select-scroll-down-button-mounted.svelte.d.ts +0 -29
  90. package/dist/bits/select/components/select-scroll-up-button-mounted.svelte +0 -33
  91. package/dist/bits/select/components/select-scroll-up-button-mounted.svelte.d.ts +0 -29
  92. package/dist/bits/select/components/select-separator.svelte +0 -32
  93. package/dist/bits/select/components/select-separator.svelte.d.ts +0 -19
  94. package/dist/bits/select/components/select-value.svelte +0 -35
  95. package/dist/bits/select/components/select-value.svelte.d.ts +0 -19
  96. package/dist/internal/use-typeahead.svelte.d.ts +0 -34
  97. package/dist/internal/use-typeahead.svelte.js +0 -64
@@ -1,52 +1,50 @@
1
- import type { HTMLSelectAttributes } from "svelte/elements";
2
1
  import type { Expand } from "svelte-toolbelt";
3
- import type { PopperLayerProps } from "../utilities/popper-layer/types.js";
2
+ import type { PortalProps } from "../utilities/portal/types.js";
3
+ import type { PopperLayerProps, PopperLayerStaticProps } from "../utilities/popper-layer/types.js";
4
4
  import type { ArrowProps, ArrowPropsWithoutHTML } from "../utilities/arrow/types.js";
5
- import type { OnChangeFn, WithChild, WithChildren, Without } from "../../internal/types.js";
6
- import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes, BitsPrimitiveSpanAttributes } from "../../shared/attributes.js";
7
- import type { Direction } from "../../shared/index.js";
8
- import type { PortalProps } from "../utilities/portal/index.js";
9
- export type SelectRootPropsWithoutHTML = WithChildren<{
5
+ import type { BitsPrimitiveButtonAttributes, BitsPrimitiveDivAttributes } from "../../shared/attributes.js";
6
+ import type { OnChangeFn, WithChild, WithChildNoChildrenSnippetProps, WithChildren, Without } from "../../internal/types.js";
7
+ export type SelectBaseRootPropsWithoutHTML = WithChildren<{
10
8
  /**
11
- * The open state of the select.
12
- */
13
- open?: boolean;
14
- /**
15
- * A callback that is called when the select's open state changes.
16
- */
17
- onOpenChange?: OnChangeFn<boolean>;
18
- /**
19
- * The value of the select.
20
- */
21
- value?: string;
22
- /**
23
- * A callback that is called when the select's value changes.
9
+ * Whether the combobox is disabled.
10
+ *
11
+ * @defaultValue `false`
24
12
  */
25
- onValueChange?: OnChangeFn<string>;
13
+ disabled?: boolean;
26
14
  /**
27
- * The reading direction of the select.
15
+ * Whether the combobox is required (for form submission).
16
+ *
17
+ * @defaultValue `false`
28
18
  */
29
- dir?: Direction;
19
+ required?: boolean;
30
20
  /**
31
- * The name of the select used in form submission.
21
+ * The name to apply to the hidden input element for form submission.
22
+ * If not provided, a hidden input will not be rendered and the combobox will not be part of a form.
32
23
  */
33
24
  name?: string;
34
25
  /**
35
- * The native HTML select autocomplete attribute.
26
+ * Whether the combobox popover is open.
27
+ *
28
+ * @defaultValue `false`
29
+ * @bindable
36
30
  */
37
- autocomplete?: HTMLSelectAttributes["autocomplete"];
31
+ open?: boolean;
38
32
  /**
39
- * The native HTML select `form` attribute.
33
+ * A callback function called when the open state changes.
40
34
  */
41
- form?: string;
35
+ onOpenChange?: OnChangeFn<boolean>;
42
36
  /**
43
- * Whether the select is disabled.
37
+ * Whether or not the combobox menu should loop through the items when navigating with the keyboard.
38
+ *
39
+ * @defaultValue `false`
44
40
  */
45
- disabled?: boolean;
41
+ loop?: boolean;
46
42
  /**
47
- * Whether the select is required.
43
+ * How to scroll the combobox items into view when navigating with the keyboard.
44
+ *
45
+ * @defaultValue `"nearest"`
48
46
  */
49
- required?: boolean;
47
+ scrollAlignment?: "nearest" | "center";
50
48
  /**
51
49
  * Whether or not the open state is controlled or not. If `true`, the component will not update
52
50
  * the open state internally, instead it will call `onOpenChange` when it would have
@@ -63,7 +61,69 @@ export type SelectRootPropsWithoutHTML = WithChildren<{
63
61
  * @defaultValue false
64
62
  */
65
63
  controlledValue?: boolean;
64
+ /**
65
+ * Optionally provide an array of `value` and `label` pairs that will be used to match
66
+ * and trigger selection when the trigger is focused and a key is pressed while the content
67
+ * is closed. It's also used to handle form autofill.
68
+ *
69
+ * By providing this value, you enable selecting a value when the trigger is focused and a key
70
+ * is pressed without the content being open, similar to how a native `<select>` works.
71
+ * For this to work, you must
72
+ *
73
+ * The label is what the user will potentially search for via typeahead, and the value is what
74
+ * is set as the selected value when a typeahead match is found.
75
+ *
76
+ * We can't rely on the individual `Item` components to do this because they may not ever be
77
+ * mounted to do the DOM if using a conditional block with a Svelte transition or certain
78
+ * animation libraries.
79
+ *
80
+ * IMPORTANT: This functionality is only available for single-select listboxes.
81
+ */
82
+ items?: {
83
+ value: string;
84
+ label: string;
85
+ disabled?: boolean;
86
+ }[];
66
87
  }>;
88
+ export type SelectSingleRootPropsWithoutHTML = {
89
+ /**
90
+ * The value of the selected combobox item.
91
+ *
92
+ * @bindable
93
+ */
94
+ value?: string;
95
+ /**
96
+ * A callback function called when the value changes.
97
+ */
98
+ onValueChange?: OnChangeFn<string>;
99
+ /**
100
+ * The type of combobox.
101
+ *
102
+ * @required
103
+ */
104
+ type: "single";
105
+ };
106
+ export type SelectMultipleRootPropsWithoutHTML = {
107
+ /**
108
+ * The value of the selected combobox item.
109
+ *
110
+ * @bindable
111
+ */
112
+ value?: string[];
113
+ /**
114
+ * A callback function called when the value changes.
115
+ */
116
+ onValueChange?: OnChangeFn<string[]>;
117
+ /**
118
+ * The type of combobox.
119
+ *
120
+ * @required
121
+ */
122
+ type: "multiple";
123
+ };
124
+ export type SelectSingleRootProps = SelectBaseRootPropsWithoutHTML & SelectSingleRootPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectSingleRootPropsWithoutHTML | SelectBaseRootPropsWithoutHTML>;
125
+ export type SelectMultipleRootProps = SelectBaseRootPropsWithoutHTML & SelectMultipleRootPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectMultipleRootPropsWithoutHTML | SelectBaseRootPropsWithoutHTML>;
126
+ export type SelectRootPropsWithoutHTML = SelectBaseRootPropsWithoutHTML & (SelectSingleRootPropsWithoutHTML | SelectMultipleRootPropsWithoutHTML);
67
127
  export type SelectRootProps = SelectRootPropsWithoutHTML;
68
128
  export type _SharedSelectContentProps = {
69
129
  /**
@@ -73,79 +133,68 @@ export type _SharedSelectContentProps = {
73
133
  */
74
134
  loop?: boolean;
75
135
  };
76
- export type SelectContentImplPropsWithoutHTML = Expand<WithChild<Omit<PopperLayerProps, "content"> & {
136
+ export type SelectContentSnippetProps = {
77
137
  /**
78
- * The positioning mode to use
79
- *
80
- * `item-aligned` - behaves similarly to a native MacOS menu by positioning content relative to the active item. <br>
81
- * `floating (default)` - positions content in the same way as our other primitives, for example `Popover` or `DropdownMenu`.
138
+ * Whether the content is open or closed. Used alongside the `forceMount` prop to conditionally
139
+ * render the content using Svelte transitions.
82
140
  */
83
- position?: "item-aligned" | "floating";
84
- } & _SharedSelectContentProps>>;
85
- export type SelectContentImplProps = SelectContentImplPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectContentImplPropsWithoutHTML>;
86
- export type SelectContentPropsWithoutHTML = SelectContentImplPropsWithoutHTML & {
87
- /**
88
- * Whether to force mount the content for use with certain animation libraries.
89
- */
90
- forceMount?: boolean;
141
+ open: boolean;
91
142
  };
143
+ export type SelectContentPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerProps, "content" | "onOpenAutoFocus" | "trapFocus"> & _SharedSelectContentProps, SelectContentSnippetProps>>;
92
144
  export type SelectContentProps = SelectContentPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectContentPropsWithoutHTML>;
145
+ export type SelectContentStaticPropsWithoutHTML = Expand<WithChildNoChildrenSnippetProps<Omit<PopperLayerStaticProps, "content" | "onOpenAutoFocus" | "trapFocus"> & _SharedSelectContentProps, SelectContentSnippetProps>>;
146
+ export type SelectContentStaticProps = SelectContentStaticPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectContentStaticPropsWithoutHTML>;
147
+ export type SelectTriggerPropsWithoutHTML = WithChild;
148
+ export type SelectTriggerProps = SelectTriggerPropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, SelectTriggerPropsWithoutHTML>;
93
149
  export type SelectItemSnippetProps = {
94
150
  selected: boolean;
151
+ highlighted: boolean;
95
152
  };
96
153
  export type SelectItemPropsWithoutHTML = WithChild<{
97
154
  /**
98
155
  * The value of the item.
156
+ *
157
+ * @required
99
158
  */
100
159
  value: string;
101
160
  /**
102
- * Whether the item is disabled.
103
- *
104
- * @defaultValue false
161
+ * The label of the item. If provided, this is the item that users will search for.
162
+ * If not provided, the value will be used as the label.
105
163
  */
106
- disabled?: boolean;
164
+ label?: string;
107
165
  /**
108
- * Optionally provide text to use for typeahead purposes.
166
+ * Whether the item is disabled.
109
167
  *
110
- * By default, the typeahead behavior will use the `textContent` of the `SelectItemText`
111
- * component. Use this prop if the text content is not suitable for typeahead.
168
+ * @defaultValue `false`
112
169
  */
113
- textValue?: string;
114
- }, SelectItemSnippetProps>;
115
- export type SelectItemProps = SelectItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectItemPropsWithoutHTML>;
116
- export type SelectTriggerPropsWithoutHTML = WithChild<{
170
+ disabled?: boolean;
117
171
  /**
118
- * Whether the trigger is disabled.
119
- *
120
- * @defaultValue false
172
+ * A callback function called when the item is highlighted. This can be used as a
173
+ * replacement for `onfocus` since we don't actually focus the item and instead
174
+ * rely on the `aria-activedescendant` attribute to indicate the highlighted item.
121
175
  */
122
- disabled?: boolean | null | undefined;
123
- }>;
124
- export type SelectTriggerProps = SelectTriggerPropsWithoutHTML & Without<BitsPrimitiveButtonAttributes, SelectTriggerPropsWithoutHTML>;
125
- export type SelectValuePropsWithoutHTML = WithChild<{
176
+ onHighlight?: () => void;
126
177
  /**
127
- * The content to render inside the `Select.Value` when no `value` is set.
178
+ * A callback function called when the item is unhighlighted. This can be used as a
179
+ * replacement for `onblur` since we don't actually focus the item and instead
180
+ * rely on the `aria-activedescendant` attribute to indicate the highlighted item.
128
181
  */
129
- placeholder?: string;
130
- }>;
131
- export type SelectValueProps = SelectValuePropsWithoutHTML & Without<Omit<BitsPrimitiveSpanAttributes, "id">, SelectValuePropsWithoutHTML>;
132
- export type SelectItemTextPropsWithoutHTML = WithChild;
133
- export type SelectItemTextProps = SelectItemTextPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SelectItemTextPropsWithoutHTML>;
134
- export type SelectViewportPropsWithoutHTML = WithChild;
135
- export type SelectViewportProps = SelectViewportPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectViewportPropsWithoutHTML>;
136
- export type SelectPortalPropsWithoutHTML = PortalProps;
137
- export type SelectPortalProps = SelectPortalPropsWithoutHTML;
138
- export type SelectScrollUpButtonPropsWithoutHTML = WithChild;
139
- export type SelectScrollUpButtonProps = SelectScrollUpButtonPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectScrollUpButtonPropsWithoutHTML>;
140
- export type SelectScrollDownButtonPropsWithoutHTML = WithChild;
141
- export type SelectScrollDownButtonProps = SelectScrollDownButtonPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectScrollDownButtonPropsWithoutHTML>;
142
- export type SelectIconPropsWithoutHTML = WithChild;
143
- export type SelectIconProps = SelectIconPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SelectIconPropsWithoutHTML>;
182
+ onUnhighlight?: () => void;
183
+ }, SelectItemSnippetProps>;
184
+ export type SelectItemProps = SelectItemPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectItemPropsWithoutHTML>;
144
185
  export type SelectGroupPropsWithoutHTML = WithChild;
145
186
  export type SelectGroupProps = SelectGroupPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectGroupPropsWithoutHTML>;
146
187
  export type SelectGroupHeadingPropsWithoutHTML = WithChild;
147
188
  export type SelectGroupHeadingProps = SelectGroupHeadingPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectGroupHeadingPropsWithoutHTML>;
148
189
  export type SelectSeparatorPropsWithoutHTML = WithChild;
149
190
  export type SelectSeparatorProps = SelectSeparatorPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectSeparatorPropsWithoutHTML>;
191
+ export type SelectPortalPropsWithoutHTML = PortalProps;
192
+ export type SelectPortalProps = SelectPortalPropsWithoutHTML;
150
193
  export type SelectArrowPropsWithoutHTML = ArrowPropsWithoutHTML;
151
194
  export type SelectArrowProps = ArrowProps;
195
+ export type SelectViewportPropsWithoutHTML = WithChild;
196
+ export type SelectViewportProps = SelectViewportPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectViewportPropsWithoutHTML>;
197
+ export type SelectScrollUpButtonPropsWithoutHTML = WithChild;
198
+ export type SelectScrollUpButtonProps = SelectScrollUpButtonPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectScrollUpButtonPropsWithoutHTML>;
199
+ export type SelectScrollDownButtonPropsWithoutHTML = WithChild;
200
+ export type SelectScrollDownButtonProps = SelectScrollDownButtonPropsWithoutHTML & Without<BitsPrimitiveDivAttributes, SelectScrollDownButtonPropsWithoutHTML>;
@@ -12,7 +12,7 @@
12
12
  ref = $bindable(null),
13
13
  value = $bindable([]),
14
14
  onValueChange = noop,
15
- onValueChangeEnd = noop,
15
+ onValueCommit = noop,
16
16
  disabled = false,
17
17
  min = 0,
18
18
  max = 100,
@@ -41,7 +41,7 @@
41
41
  }
42
42
  }
43
43
  ),
44
- onValueChangeEnd: box.with(() => onValueChangeEnd),
44
+ onValueCommit: box.with(() => onValueCommit),
45
45
  disabled: box.with(() => disabled),
46
46
  min: box.with(() => min),
47
47
  max: box.with(() => max),
@@ -13,7 +13,7 @@ type SliderRootStateProps = WithRefProps<ReadableBoxedValues<{
13
13
  step: number;
14
14
  dir: Direction;
15
15
  autoSort: boolean;
16
- onValueChangeEnd: OnChangeFn<number[]>;
16
+ onValueCommit: OnChangeFn<number[]>;
17
17
  }> & WritableBoxedValues<{
18
18
  value: number[];
19
19
  }>>;
@@ -35,7 +35,7 @@ declare class SliderRootState {
35
35
  isActive: boolean;
36
36
  currentThumbIdx: number;
37
37
  direction: "rl" | "lr" | "tb" | "bt";
38
- onValueChangeEnd: SliderRootStateProps["onValueChangeEnd"];
38
+ onValueCommit: SliderRootStateProps["onValueCommit"];
39
39
  constructor(props: SliderRootStateProps);
40
40
  applyPosition: ({ clientXY, activeThumbIdx, start, end, }: {
41
41
  clientXY: number;
@@ -38,7 +38,7 @@ class SliderRootState {
38
38
  return this.dir.current === "rtl" ? "tb" : "bt";
39
39
  }
40
40
  });
41
- onValueChangeEnd;
41
+ onValueCommit;
42
42
  constructor(props) {
43
43
  this.id = props.id;
44
44
  this.ref = props.ref;
@@ -50,7 +50,7 @@ class SliderRootState {
50
50
  this.dir = props.dir;
51
51
  this.autoSort = props.autoSort;
52
52
  this.value = props.value;
53
- this.onValueChangeEnd = props.onValueChangeEnd;
53
+ this.onValueCommit = props.onValueCommit;
54
54
  useRefById({
55
55
  id: this.id,
56
56
  ref: this.ref,
@@ -187,7 +187,7 @@ class SliderRootState {
187
187
  };
188
188
  handlePointerUp = () => {
189
189
  if (this.isActive) {
190
- this.onValueChangeEnd.current(untrack(() => this.value.current));
190
+ this.onValueCommit.current(untrack(() => this.value.current));
191
191
  }
192
192
  this.isActive = false;
193
193
  };
@@ -477,7 +477,7 @@ class SliderThumbState {
477
477
  }
478
478
  break;
479
479
  }
480
- this.#root.onValueChangeEnd.current(this.#root.value.current);
480
+ this.#root.onValueCommit.current(this.#root.value.current);
481
481
  };
482
482
  props = $derived.by(() => ({
483
483
  ...this.#root.thumbsPropsArr[this.#index.current],
@@ -17,9 +17,10 @@ export type SliderRootPropsWithoutHTML = WithChild<{
17
17
  onValueChange?: OnChangeFn<number[]>;
18
18
  /**
19
19
  * A callback function called when the user stops dragging the thumb,
20
- * which is useful for knowing when the user has finished interacting with the slider.
20
+ * which is useful for knowing when the user has finished interacting with the
21
+ * slider and _commits_ the value.
21
22
  */
22
- onValueChangeEnd?: OnChangeFn<number[]>;
23
+ onValueCommit?: OnChangeFn<number[]>;
23
24
  /**
24
25
  * Whether to automatically sort the values in the array when moving thumbs past
25
26
  * one another.
@@ -74,9 +74,7 @@
74
74
  {customAnchor}
75
75
  >
76
76
  {#snippet content({ props: floatingProps })}
77
- {#if preventScroll}
78
- <ScrollLock {preventScroll} />
79
- {/if}
77
+ <ScrollLock {preventScroll} />
80
78
  <FocusScope
81
79
  {id}
82
80
  {onOpenAutoFocus}
@@ -65,3 +65,26 @@ export declare function forward<T>(array: T[], index: number, increment: number,
65
65
  * @param loop loop around the array if the target index is out of bounds?
66
66
  */
67
67
  export declare function backward<T>(array: T[], index: number, decrement: number, loop?: boolean): T | undefined;
68
+ /**
69
+ * This is the "meat" of the typeahead matching logic. It takes in all the values,
70
+ * the search and the current match, and returns the next match (or `undefined`).
71
+ *
72
+ * We normalize the search because if a user has repeatedly pressed a character,
73
+ * we want the exact same behavior as if we only had that one character
74
+ * (ie. cycle through options starting with that character)
75
+ *
76
+ * We also reorder the values by wrapping the array around the current match.
77
+ * This is so we always look forward from the current match, and picking the first
78
+ * match will always be the correct one.
79
+ *
80
+ * Finally, if the normalized search is exactly one character, we exclude the
81
+ * current match from the values because otherwise it would be the first to match always
82
+ * and focus would never move. This is as opposed to the regular case, where we
83
+ * don't want focus to move if the current match still matches.
84
+ */
85
+ export declare function getNextMatch(values: string[], search: string, currentMatch?: string): string | undefined;
86
+ /**
87
+ * Wraps an array around itself at a given start index
88
+ * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']`
89
+ */
90
+ export declare function wrapArray<T>(array: T[], startIndex: number): T[];
@@ -168,3 +168,38 @@ export function backward(array, index, decrement, loop = true) {
168
168
  }
169
169
  return array[targetIndex];
170
170
  }
171
+ /**
172
+ * This is the "meat" of the typeahead matching logic. It takes in all the values,
173
+ * the search and the current match, and returns the next match (or `undefined`).
174
+ *
175
+ * We normalize the search because if a user has repeatedly pressed a character,
176
+ * we want the exact same behavior as if we only had that one character
177
+ * (ie. cycle through options starting with that character)
178
+ *
179
+ * We also reorder the values by wrapping the array around the current match.
180
+ * This is so we always look forward from the current match, and picking the first
181
+ * match will always be the correct one.
182
+ *
183
+ * Finally, if the normalized search is exactly one character, we exclude the
184
+ * current match from the values because otherwise it would be the first to match always
185
+ * and focus would never move. This is as opposed to the regular case, where we
186
+ * don't want focus to move if the current match still matches.
187
+ */
188
+ export function getNextMatch(values, search, currentMatch) {
189
+ const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
190
+ const normalizedSearch = isRepeated ? search[0] : search;
191
+ const currentMatchIndex = currentMatch ? values.indexOf(currentMatch) : -1;
192
+ let wrappedValues = wrapArray(values, Math.max(currentMatchIndex, 0));
193
+ const excludeCurrentMatch = normalizedSearch.length === 1;
194
+ if (excludeCurrentMatch)
195
+ wrappedValues = wrappedValues.filter((v) => v !== currentMatch);
196
+ const nextMatch = wrappedValues.find((value) => value?.toLowerCase().startsWith(normalizedSearch.toLowerCase()));
197
+ return nextMatch !== currentMatch ? nextMatch : undefined;
198
+ }
199
+ /**
200
+ * Wraps an array around itself at a given start index
201
+ * Example: `wrapArray(['a', 'b', 'c', 'd'], 2) === ['c', 'd', 'a', 'b']`
202
+ */
203
+ export function wrapArray(array, startIndex) {
204
+ return array.map((_, index) => array[(startIndex + index) % array.length]);
205
+ }
@@ -62,7 +62,12 @@ function createMonth(props) {
62
62
  if (!startFrom) {
63
63
  startFrom = dateObj.add({ months: 1 }).set({ day: 1 });
64
64
  }
65
- const extraDaysArray = Array.from({ length: extraDays }, (_, i) => {
65
+ let length = extraDays;
66
+ if (nextMonthDays.length === 0) {
67
+ length = extraDays - 1;
68
+ nextMonthDays.push(startFrom);
69
+ }
70
+ const extraDaysArray = Array.from({ length }, (_, i) => {
66
71
  const incr = i + 1;
67
72
  return startFrom.add({ days: incr });
68
73
  });
@@ -1,10 +1,11 @@
1
1
  import { SvelteMap } from "svelte/reactivity";
2
2
  import { afterTick, box } from "svelte-toolbelt";
3
+ import { Previous, watch } from "runed";
4
+ import { untrack } from "svelte";
3
5
  import { isBrowser, isIOS } from "./is.js";
4
6
  import { addEventListener } from "./events.js";
5
7
  import { useId } from "./use-id.js";
6
8
  import { createSharedHook } from "./create-shared-hook.svelte.js";
7
- import { watch } from "./box.svelte.js";
8
9
  const useBodyLockStackCount = createSharedHook(() => {
9
10
  const map = new SvelteMap();
10
11
  const locked = $derived.by(() => {
@@ -15,6 +16,7 @@ const useBodyLockStackCount = createSharedHook(() => {
15
16
  }
16
17
  return false;
17
18
  });
19
+ const prevLocked = new Previous(() => locked);
18
20
  let initialBodyStyle = $state({});
19
21
  let stopTouchMoveListener = null;
20
22
  function resetBodyStyle() {
@@ -27,43 +29,43 @@ const useBodyLockStackCount = createSharedHook(() => {
27
29
  document.body.style.overflow = initialBodyStyle.overflow ?? "";
28
30
  isIOS && stopTouchMoveListener?.();
29
31
  }
30
- watch(box.with(() => locked), (curr, prev) => {
31
- if (!curr) {
32
- if (prev) {
33
- resetBodyStyle();
32
+ $effect(() => {
33
+ const curr = locked;
34
+ return untrack(() => {
35
+ if (!curr) {
36
+ return;
34
37
  }
35
- return;
36
- }
37
- const bodyStyle = getComputedStyle(document.body);
38
- initialBodyStyle.overflow = bodyStyle.overflow;
39
- initialBodyStyle.paddingRight = bodyStyle.paddingRight;
40
- initialBodyStyle.marginRight = bodyStyle.marginRight;
41
- initialBodyStyle.pointerEvents = bodyStyle.pointerEvents;
42
- // TODO: account for RTL direction, etc.
43
- const verticalScrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
44
- const paddingRight = Number.parseInt(initialBodyStyle.paddingRight ?? "0", 10);
45
- const config = {
46
- padding: paddingRight + verticalScrollbarWidth,
47
- margin: Number.parseInt(initialBodyStyle.marginRight ?? "0", 10),
48
- };
49
- if (verticalScrollbarWidth > 0) {
50
- document.body.style.paddingRight = `${config.padding}px`;
51
- document.body.style.marginRight = `${config.margin}px`;
52
- document.body.style.setProperty("--scrollbar-width", `${verticalScrollbarWidth}px`);
53
- document.body.style.overflow = "hidden";
54
- }
55
- if (isIOS) {
56
- stopTouchMoveListener = addEventListener(document, "touchmove", (e) => {
57
- if (e.target !== document.documentElement)
58
- return;
59
- if (e.touches.length > 1)
60
- return;
61
- e.preventDefault();
62
- }, { passive: false });
63
- }
64
- afterTick(() => {
65
- document.body.style.pointerEvents = "none";
66
- document.body.style.overflow = "hidden";
38
+ const bodyStyle = getComputedStyle(document.body);
39
+ initialBodyStyle.overflow = bodyStyle.overflow;
40
+ initialBodyStyle.paddingRight = bodyStyle.paddingRight;
41
+ initialBodyStyle.marginRight = bodyStyle.marginRight;
42
+ initialBodyStyle.pointerEvents = bodyStyle.pointerEvents;
43
+ // TODO: account for RTL direction, etc.
44
+ const verticalScrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
45
+ const paddingRight = Number.parseInt(initialBodyStyle.paddingRight ?? "0", 10);
46
+ const config = {
47
+ padding: paddingRight + verticalScrollbarWidth,
48
+ margin: Number.parseInt(initialBodyStyle.marginRight ?? "0", 10),
49
+ };
50
+ if (verticalScrollbarWidth > 0) {
51
+ document.body.style.paddingRight = `${config.padding}px`;
52
+ document.body.style.marginRight = `${config.margin}px`;
53
+ document.body.style.setProperty("--scrollbar-width", `${verticalScrollbarWidth}px`);
54
+ document.body.style.overflow = "hidden";
55
+ }
56
+ if (isIOS) {
57
+ stopTouchMoveListener = addEventListener(document, "touchmove", (e) => {
58
+ if (e.target !== document.documentElement)
59
+ return;
60
+ if (e.touches.length > 1)
61
+ return;
62
+ e.preventDefault();
63
+ }, { passive: false });
64
+ }
65
+ afterTick(() => {
66
+ document.body.style.pointerEvents = "none";
67
+ document.body.style.overflow = "hidden";
68
+ });
67
69
  });
68
70
  });
69
71
  $effect(() => {
@@ -71,16 +73,25 @@ const useBodyLockStackCount = createSharedHook(() => {
71
73
  stopTouchMoveListener?.();
72
74
  };
73
75
  });
74
- return map;
76
+ return {
77
+ get map() {
78
+ return map;
79
+ },
80
+ resetBodyStyle,
81
+ };
75
82
  });
76
83
  export function useBodyScrollLock(initialState) {
77
84
  const id = useId();
78
- const map = useBodyLockStackCount();
79
- map.set(id, initialState ?? false);
80
- const locked = box.with(() => map.get(id) ?? false, (v) => map.set(id, v));
85
+ const countState = useBodyLockStackCount();
86
+ countState.map.set(id, initialState ?? false);
87
+ const locked = box.with(() => countState.map.get(id) ?? false, (v) => countState.map.set(id, v));
81
88
  $effect(() => {
82
89
  return () => {
83
- map.delete(id);
90
+ countState.map.delete(id);
91
+ const length = Array.from(countState.map.values()).length;
92
+ if (length === 0) {
93
+ countState.resetBodyStyle();
94
+ }
84
95
  };
85
96
  });
86
97
  return locked;
@@ -0,0 +1,12 @@
1
+ export type DataTypeahead = ReturnType<typeof useDataTypeahead>;
2
+ type UseDataTypeaheadOpts = {
3
+ onMatch: (value: string) => void;
4
+ getCurrentItem: () => string;
5
+ enabled: boolean;
6
+ };
7
+ export declare function useDataTypeahead(opts: UseDataTypeaheadOpts): {
8
+ search: import("svelte-toolbelt").WritableBox<string>;
9
+ handleTypeaheadSearch: (key: string, candidateValues: string[]) => string | undefined;
10
+ resetTypeahead: () => void;
11
+ };
12
+ export {};
@@ -0,0 +1,30 @@
1
+ import { getNextMatch } from "./arrays.js";
2
+ import { boxAutoReset } from "./box-auto-reset.svelte.js";
3
+ export function useDataTypeahead(opts) {
4
+ // Reset `search` 1 second after it was last updated
5
+ const search = boxAutoReset("", 1000);
6
+ function handleTypeaheadSearch(key, candidateValues) {
7
+ if (!opts.enabled)
8
+ return;
9
+ if (!candidateValues.length)
10
+ return;
11
+ search.current = search.current + key;
12
+ const currentItem = opts.getCurrentItem();
13
+ const currentMatch = candidateValues.find((item) => item === currentItem) ?? "";
14
+ const values = candidateValues.map((item) => item ?? "");
15
+ const nextMatch = getNextMatch(values, search.current, currentMatch);
16
+ const newItem = candidateValues.find((item) => item === nextMatch);
17
+ if (newItem) {
18
+ opts.onMatch(newItem);
19
+ }
20
+ return newItem;
21
+ }
22
+ function resetTypeahead() {
23
+ search.current = "";
24
+ }
25
+ return {
26
+ search,
27
+ handleTypeaheadSearch,
28
+ resetTypeahead,
29
+ };
30
+ }
@@ -0,0 +1,11 @@
1
+ export type DOMTypeahead = ReturnType<typeof useDOMTypeahead>;
2
+ type UseDOMTypeaheadOpts = {
3
+ onMatch?: (item: HTMLElement) => void;
4
+ getCurrentItem?: () => HTMLElement | null;
5
+ };
6
+ export declare function useDOMTypeahead(opts?: UseDOMTypeaheadOpts): {
7
+ search: import("svelte-toolbelt").WritableBox<string>;
8
+ handleTypeaheadSearch: (key: string, candidates: HTMLElement[]) => HTMLElement | undefined;
9
+ resetTypeahead: () => void;
10
+ };
11
+ export {};