bits-ui 1.0.0-next.82 → 1.0.0-next.83
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/dist/app.d.ts +1 -0
- package/dist/bits/pin-input/pin-input.svelte.d.ts +2 -4
- package/dist/bits/pin-input/pin-input.svelte.js +26 -29
- package/dist/bits/select/components/select-scroll-down-button.svelte +4 -7
- package/dist/bits/select/components/select-scroll-up-button.svelte +4 -7
- package/dist/bits/select/select.svelte.d.ts +20 -14
- package/dist/bits/select/select.svelte.js +177 -169
- package/dist/bits/utilities/floating-layer/components/floating-layer-content.svelte +2 -1
- package/dist/bits/utilities/floating-layer/types.d.ts +1 -0
- package/dist/bits/utilities/popper-layer/popper-layer-inner.svelte +1 -0
- package/dist/internal/use-after-animations.svelte.d.ts +5 -0
- package/dist/internal/use-after-animations.svelte.js +27 -0
- package/package.json +2 -2
package/dist/app.d.ts
CHANGED
|
@@ -22,8 +22,7 @@ declare class PinInputRootState {
|
|
|
22
22
|
#private;
|
|
23
23
|
value: PinInputRootStateProps["value"];
|
|
24
24
|
constructor(props: PinInputRootStateProps);
|
|
25
|
-
|
|
26
|
-
onkeydown(e: BitsKeyboardEvent): void;
|
|
25
|
+
onkeydown: (e: BitsKeyboardEvent) => void;
|
|
27
26
|
rootProps: {
|
|
28
27
|
readonly id: string;
|
|
29
28
|
readonly "data-pin-input-root": "";
|
|
@@ -42,7 +41,7 @@ declare class PinInputRootState {
|
|
|
42
41
|
readonly pointerEvents: "none";
|
|
43
42
|
};
|
|
44
43
|
};
|
|
45
|
-
oninput(e: BitsEvent<Event, HTMLInputElement>)
|
|
44
|
+
oninput: (e: BitsEvent<Event, HTMLInputElement>) => void;
|
|
46
45
|
onfocus: (_: BitsFocusEvent<HTMLInputElement>) => void;
|
|
47
46
|
onpaste: (e: BitsEvent<ClipboardEvent>) => void;
|
|
48
47
|
onmouseover: (_: BitsMouseEvent) => void;
|
|
@@ -77,7 +76,6 @@ declare class PinInputRootState {
|
|
|
77
76
|
"data-pin-input-input-mss": number | null;
|
|
78
77
|
"data-pin-input-input-mse": number | null;
|
|
79
78
|
inputmode: "none" | "search" | "text" | "email" | "tel" | "url" | "numeric" | "decimal" | null | undefined;
|
|
80
|
-
pattern: any;
|
|
81
79
|
maxlength: number;
|
|
82
80
|
value: string;
|
|
83
81
|
disabled: true | undefined;
|
|
@@ -10,6 +10,22 @@ export const REGEXP_ONLY_CHARS = "^[a-zA-Z]+$";
|
|
|
10
10
|
export const REGEXP_ONLY_DIGITS_AND_CHARS = "^[a-zA-Z0-9]+$";
|
|
11
11
|
const ROOT_ATTR = "data-pin-input-root";
|
|
12
12
|
const CELL_ATTR = "data-pin-input-cell";
|
|
13
|
+
const KEYS_TO_IGNORE = [
|
|
14
|
+
"Backspace",
|
|
15
|
+
"Delete",
|
|
16
|
+
"ArrowLeft",
|
|
17
|
+
"ArrowRight",
|
|
18
|
+
"ArrowUp",
|
|
19
|
+
"ArrowDown",
|
|
20
|
+
"Home",
|
|
21
|
+
"End",
|
|
22
|
+
"Escape",
|
|
23
|
+
"Enter",
|
|
24
|
+
"Tab",
|
|
25
|
+
"Shift",
|
|
26
|
+
"Control",
|
|
27
|
+
"Meta",
|
|
28
|
+
];
|
|
13
29
|
class PinInputRootState {
|
|
14
30
|
#id;
|
|
15
31
|
#ref;
|
|
@@ -146,38 +162,19 @@ class PinInputRootState {
|
|
|
146
162
|
onComplete(value);
|
|
147
163
|
}
|
|
148
164
|
});
|
|
149
|
-
this.onkeydown = this.onkeydown.bind(this);
|
|
150
|
-
this.oninput = this.oninput.bind(this);
|
|
151
|
-
this.onfocus = this.onfocus.bind(this);
|
|
152
|
-
this.onmouseover = this.onmouseover.bind(this);
|
|
153
|
-
this.onmouseleave = this.onmouseleave.bind(this);
|
|
154
|
-
this.onblur = this.onblur.bind(this);
|
|
155
|
-
this.onpaste = this.onpaste.bind(this);
|
|
156
165
|
}
|
|
157
|
-
|
|
158
|
-
"Backspace",
|
|
159
|
-
"Delete",
|
|
160
|
-
"ArrowLeft",
|
|
161
|
-
"ArrowRight",
|
|
162
|
-
"ArrowUp",
|
|
163
|
-
"ArrowDown",
|
|
164
|
-
"Home",
|
|
165
|
-
"End",
|
|
166
|
-
"Escape",
|
|
167
|
-
"Enter",
|
|
168
|
-
"Tab",
|
|
169
|
-
"Shift",
|
|
170
|
-
"Control",
|
|
171
|
-
"Meta",
|
|
172
|
-
];
|
|
173
|
-
onkeydown(e) {
|
|
166
|
+
onkeydown = (e) => {
|
|
174
167
|
const key = e.key;
|
|
175
|
-
if (
|
|
168
|
+
if (KEYS_TO_IGNORE.includes(key))
|
|
169
|
+
return;
|
|
170
|
+
// if ctrl or cmd is pressed, they are likely to be shortcuts and should not be tested
|
|
171
|
+
// against the regex
|
|
172
|
+
if (e.ctrlKey || e.metaKey)
|
|
176
173
|
return;
|
|
177
174
|
if (key && this.#regexPattern && !this.#regexPattern.test(key)) {
|
|
178
175
|
e.preventDefault();
|
|
179
176
|
}
|
|
180
|
-
}
|
|
177
|
+
};
|
|
181
178
|
#rootStyles = $derived.by(() => ({
|
|
182
179
|
position: "relative",
|
|
183
180
|
cursor: this.#disabled.current ? "default" : "text",
|
|
@@ -297,7 +294,7 @@ class PinInputRootState {
|
|
|
297
294
|
this.#mirrorSelectionEnd = e;
|
|
298
295
|
this.#prevInputMetadata.prev = [s, e, dir];
|
|
299
296
|
};
|
|
300
|
-
oninput(e) {
|
|
297
|
+
oninput = (e) => {
|
|
301
298
|
const newValue = e.currentTarget.value.slice(0, this.#maxLength.current);
|
|
302
299
|
if (newValue.length > 0 && this.#regexPattern && !this.#regexPattern.test(newValue)) {
|
|
303
300
|
e.preventDefault();
|
|
@@ -313,7 +310,7 @@ class PinInputRootState {
|
|
|
313
310
|
document.dispatchEvent(new Event("selectionchange"));
|
|
314
311
|
}
|
|
315
312
|
this.value.current = newValue;
|
|
316
|
-
}
|
|
313
|
+
};
|
|
317
314
|
onfocus = (_) => {
|
|
318
315
|
const input = this.#inputRef.current;
|
|
319
316
|
if (input) {
|
|
@@ -389,7 +386,7 @@ class PinInputRootState {
|
|
|
389
386
|
"data-pin-input-input-mss": this.#mirrorSelectionStart,
|
|
390
387
|
"data-pin-input-input-mse": this.#mirrorSelectionEnd,
|
|
391
388
|
inputmode: this.#inputmode.current,
|
|
392
|
-
pattern: this.#regexPattern?.source,
|
|
389
|
+
// pattern: this.#regexPattern?.source,
|
|
393
390
|
maxlength: this.#maxLength.current,
|
|
394
391
|
value: this.value.current,
|
|
395
392
|
disabled: getDisabled(this.#disabled.current),
|
|
@@ -13,22 +13,19 @@
|
|
|
13
13
|
...restProps
|
|
14
14
|
}: SelectScrollDownButtonProps = $props();
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const scrollDownButtonState = useSelectScrollDownButton({
|
|
16
|
+
const scrollButtonState = useSelectScrollDownButton({
|
|
19
17
|
id: box.with(() => id),
|
|
20
|
-
mounted: box.with(() => mounted),
|
|
21
18
|
ref: box.with(
|
|
22
19
|
() => ref,
|
|
23
20
|
(v) => (ref = v)
|
|
24
21
|
),
|
|
25
22
|
});
|
|
26
23
|
|
|
27
|
-
const mergedProps = $derived(mergeProps(restProps,
|
|
24
|
+
const mergedProps = $derived(mergeProps(restProps, scrollButtonState.props));
|
|
28
25
|
</script>
|
|
29
26
|
|
|
30
|
-
{#if
|
|
31
|
-
<Mounted onMountedChange={(
|
|
27
|
+
{#if scrollButtonState.canScrollDown}
|
|
28
|
+
<Mounted onMountedChange={(v) => (scrollButtonState.state.mounted = v)} />
|
|
32
29
|
{#if child}
|
|
33
30
|
{@render child({ props: restProps })}
|
|
34
31
|
{:else}
|
|
@@ -13,22 +13,19 @@
|
|
|
13
13
|
...restProps
|
|
14
14
|
}: SelectScrollUpButtonProps = $props();
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const scrollDownButtonState = useSelectScrollUpButton({
|
|
16
|
+
const scrollButtonState = useSelectScrollUpButton({
|
|
19
17
|
id: box.with(() => id),
|
|
20
|
-
mounted: box.with(() => mounted),
|
|
21
18
|
ref: box.with(
|
|
22
19
|
() => ref,
|
|
23
20
|
(v) => (ref = v)
|
|
24
21
|
),
|
|
25
22
|
});
|
|
26
23
|
|
|
27
|
-
const mergedProps = $derived(mergeProps(restProps,
|
|
24
|
+
const mergedProps = $derived(mergeProps(restProps, scrollButtonState.props));
|
|
28
25
|
</script>
|
|
29
26
|
|
|
30
|
-
{#if
|
|
31
|
-
<Mounted onMountedChange={(
|
|
27
|
+
{#if scrollButtonState.canScrollUp}
|
|
28
|
+
<Mounted onMountedChange={(v) => (scrollButtonState.state.mounted = v)} />
|
|
32
29
|
{#if child}
|
|
33
30
|
{@render child({ props: restProps })}
|
|
34
31
|
{:else}
|
|
@@ -46,12 +46,8 @@ declare class SelectBaseRootState {
|
|
|
46
46
|
isUsingKeyboard: boolean;
|
|
47
47
|
isCombobox: boolean;
|
|
48
48
|
bitsAttrs: SelectBitsAttrs;
|
|
49
|
-
triggerPointerDownPos: {
|
|
50
|
-
x: number;
|
|
51
|
-
y: number;
|
|
52
|
-
} | null;
|
|
53
49
|
constructor(props: SelectBaseRootStateProps);
|
|
54
|
-
setHighlightedNode(node: HTMLElement | null): void;
|
|
50
|
+
setHighlightedNode(node: HTMLElement | null, initial?: boolean): void;
|
|
55
51
|
getCandidateNodes(): HTMLElement[];
|
|
56
52
|
setHighlightedToFirstCandidate(): void;
|
|
57
53
|
getNodeByValue(value: string): HTMLElement | null;
|
|
@@ -143,8 +139,8 @@ declare class SelectTriggerState {
|
|
|
143
139
|
* `pointerdown` fires before the `focus` event, so we can prevent the default
|
|
144
140
|
* behavior of focusing the button and keep focus on the input.
|
|
145
141
|
*/
|
|
146
|
-
onpointerdown
|
|
147
|
-
onpointerup
|
|
142
|
+
onpointerdown(e: BitsPointerEvent): void;
|
|
143
|
+
onpointerup(e: BitsPointerEvent): void;
|
|
148
144
|
props: {
|
|
149
145
|
readonly [x: string]: string | true | ((e: BitsKeyboardEvent) => void) | ((e: BitsPointerEvent) => void) | undefined;
|
|
150
146
|
readonly id: string;
|
|
@@ -264,7 +260,6 @@ declare class SelectItemState {
|
|
|
264
260
|
isSelected: boolean;
|
|
265
261
|
isHighlighted: boolean;
|
|
266
262
|
prevHighlighted: Previous<boolean>;
|
|
267
|
-
textId: string;
|
|
268
263
|
mounted: boolean;
|
|
269
264
|
constructor(props: SelectItemStateProps, root: SelectRootState);
|
|
270
265
|
snippetProps: {
|
|
@@ -358,19 +353,20 @@ declare class SelectViewportState {
|
|
|
358
353
|
};
|
|
359
354
|
};
|
|
360
355
|
}
|
|
361
|
-
type SelectScrollButtonImplStateProps = WithRefProps
|
|
362
|
-
mounted: boolean;
|
|
363
|
-
}>>;
|
|
356
|
+
type SelectScrollButtonImplStateProps = WithRefProps;
|
|
364
357
|
declare class SelectScrollButtonImplState {
|
|
365
358
|
id: SelectScrollButtonImplStateProps["id"];
|
|
366
359
|
ref: SelectScrollButtonImplStateProps["ref"];
|
|
367
360
|
content: SelectContentState;
|
|
368
361
|
root: SelectBaseRootState;
|
|
369
|
-
|
|
362
|
+
autoScrollInterval: number | null;
|
|
363
|
+
userScrollTimer: number;
|
|
364
|
+
isUserScrolling: boolean;
|
|
370
365
|
onAutoScroll: () => void;
|
|
371
|
-
mounted:
|
|
366
|
+
mounted: boolean;
|
|
372
367
|
constructor(props: SelectScrollButtonImplStateProps, content: SelectContentState);
|
|
373
|
-
|
|
368
|
+
handleUserScroll(): void;
|
|
369
|
+
clearAutoScrollInterval(): void;
|
|
374
370
|
onpointerdown(_: BitsPointerEvent): void;
|
|
375
371
|
onpointermove(_: BitsPointerEvent): void;
|
|
376
372
|
onpointerleave(_: BitsPointerEvent): void;
|
|
@@ -391,6 +387,11 @@ declare class SelectScrollDownButtonState {
|
|
|
391
387
|
root: SelectBaseRootState;
|
|
392
388
|
canScrollDown: boolean;
|
|
393
389
|
constructor(state: SelectScrollButtonImplState);
|
|
390
|
+
/**
|
|
391
|
+
* @param manual - if true, it means the function was invoked manually outside of an event
|
|
392
|
+
* listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
|
|
393
|
+
*/
|
|
394
|
+
handleScroll: (manual?: boolean) => void;
|
|
394
395
|
handleAutoScroll: () => void;
|
|
395
396
|
props: {
|
|
396
397
|
readonly id: string;
|
|
@@ -409,6 +410,11 @@ declare class SelectScrollUpButtonState {
|
|
|
409
410
|
root: SelectBaseRootState;
|
|
410
411
|
canScrollUp: boolean;
|
|
411
412
|
constructor(state: SelectScrollButtonImplState);
|
|
413
|
+
/**
|
|
414
|
+
* @param manual - if true, it means the function was invoked manually outside of an event
|
|
415
|
+
* listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
|
|
416
|
+
*/
|
|
417
|
+
handleScroll: (manual?: boolean) => void;
|
|
412
418
|
handleAutoScroll: () => void;
|
|
413
419
|
props: {
|
|
414
420
|
readonly id: string;
|
|
@@ -45,10 +45,9 @@ class SelectBaseRootState {
|
|
|
45
45
|
return null;
|
|
46
46
|
return this.highlightedNode.getAttribute("data-label");
|
|
47
47
|
});
|
|
48
|
-
isUsingKeyboard =
|
|
49
|
-
isCombobox =
|
|
48
|
+
isUsingKeyboard = false;
|
|
49
|
+
isCombobox = false;
|
|
50
50
|
bitsAttrs;
|
|
51
|
-
triggerPointerDownPos = $state.raw({ x: 0, y: 0 });
|
|
52
51
|
constructor(props) {
|
|
53
52
|
this.disabled = props.disabled;
|
|
54
53
|
this.required = props.required;
|
|
@@ -66,12 +65,10 @@ class SelectBaseRootState {
|
|
|
66
65
|
}
|
|
67
66
|
});
|
|
68
67
|
}
|
|
69
|
-
setHighlightedNode(node) {
|
|
68
|
+
setHighlightedNode(node, initial = false) {
|
|
70
69
|
this.highlightedNode = node;
|
|
71
|
-
if (node) {
|
|
72
|
-
|
|
73
|
-
node.scrollIntoView({ block: "nearest" });
|
|
74
|
-
}
|
|
70
|
+
if (node && (this.isUsingKeyboard || initial)) {
|
|
71
|
+
node.scrollIntoView({ block: "nearest" });
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
getCandidateNodes() {
|
|
@@ -140,12 +137,10 @@ class SelectSingleRootState extends SelectBaseRootState {
|
|
|
140
137
|
this.setHighlightedNode(null);
|
|
141
138
|
}
|
|
142
139
|
});
|
|
143
|
-
watch(() => this.open.current, (
|
|
144
|
-
if (!
|
|
140
|
+
watch(() => this.open.current, () => {
|
|
141
|
+
if (!this.open.current)
|
|
145
142
|
return;
|
|
146
|
-
|
|
147
|
-
this.setInitialHighlightedNode();
|
|
148
|
-
});
|
|
143
|
+
this.setInitialHighlightedNode();
|
|
149
144
|
});
|
|
150
145
|
}
|
|
151
146
|
includesItem(itemValue) {
|
|
@@ -156,20 +151,22 @@ class SelectSingleRootState extends SelectBaseRootState {
|
|
|
156
151
|
this.inputValue = itemLabel;
|
|
157
152
|
}
|
|
158
153
|
setInitialHighlightedNode() {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (this.value.current !== "") {
|
|
162
|
-
const node = this.getNodeByValue(this.value.current);
|
|
163
|
-
if (node) {
|
|
164
|
-
this.setHighlightedNode(node);
|
|
154
|
+
afterTick(() => {
|
|
155
|
+
if (this.highlightedNode && document.contains(this.highlightedNode))
|
|
165
156
|
return;
|
|
157
|
+
if (this.value.current !== "") {
|
|
158
|
+
const node = this.getNodeByValue(this.value.current);
|
|
159
|
+
if (node) {
|
|
160
|
+
this.setHighlightedNode(node, true);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
166
163
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
// if no value is set, we want to highlight the first item
|
|
165
|
+
const firstCandidate = this.getCandidateNodes()[0];
|
|
166
|
+
if (!firstCandidate)
|
|
167
|
+
return;
|
|
168
|
+
this.setHighlightedNode(firstCandidate, true);
|
|
169
|
+
});
|
|
173
170
|
}
|
|
174
171
|
}
|
|
175
172
|
class SelectMultipleRootState extends SelectBaseRootState {
|
|
@@ -179,14 +176,10 @@ class SelectMultipleRootState extends SelectBaseRootState {
|
|
|
179
176
|
constructor(props) {
|
|
180
177
|
super(props);
|
|
181
178
|
this.value = props.value;
|
|
182
|
-
watch(() => this.open.current, (
|
|
183
|
-
if (!
|
|
179
|
+
watch(() => this.open.current, () => {
|
|
180
|
+
if (!this.open.current)
|
|
184
181
|
return;
|
|
185
|
-
|
|
186
|
-
if (!this.highlightedNode) {
|
|
187
|
-
this.setInitialHighlightedNode();
|
|
188
|
-
}
|
|
189
|
-
});
|
|
182
|
+
this.setInitialHighlightedNode();
|
|
190
183
|
});
|
|
191
184
|
}
|
|
192
185
|
includesItem(itemValue) {
|
|
@@ -202,20 +195,22 @@ class SelectMultipleRootState extends SelectBaseRootState {
|
|
|
202
195
|
this.inputValue = itemLabel;
|
|
203
196
|
}
|
|
204
197
|
setInitialHighlightedNode() {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (this.value.current.length && this.value.current[0] !== "") {
|
|
208
|
-
const node = this.getNodeByValue(this.value.current[0]);
|
|
209
|
-
if (node) {
|
|
210
|
-
this.setHighlightedNode(node);
|
|
198
|
+
afterTick(() => {
|
|
199
|
+
if (this.highlightedNode && document.contains(this.highlightedNode))
|
|
211
200
|
return;
|
|
201
|
+
if (this.value.current.length && this.value.current[0] !== "") {
|
|
202
|
+
const node = this.getNodeByValue(this.value.current[0]);
|
|
203
|
+
if (node) {
|
|
204
|
+
this.setHighlightedNode(node, true);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
212
207
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
208
|
+
// if no value is set, we want to highlight the first item
|
|
209
|
+
const firstCandidate = this.getCandidateNodes()[0];
|
|
210
|
+
if (!firstCandidate)
|
|
211
|
+
return;
|
|
212
|
+
this.setHighlightedNode(firstCandidate, true);
|
|
213
|
+
});
|
|
219
214
|
}
|
|
220
215
|
}
|
|
221
216
|
class SelectInputState {
|
|
@@ -240,36 +235,32 @@ class SelectInputState {
|
|
|
240
235
|
this.root.isUsingKeyboard = true;
|
|
241
236
|
if (e.key === kbd.ESCAPE)
|
|
242
237
|
return;
|
|
243
|
-
const open = this.root.open.current;
|
|
244
|
-
const inputValue = this.root.inputValue;
|
|
245
238
|
// prevent arrow up/down from moving the position of the cursor in the input
|
|
246
239
|
if (e.key === kbd.ARROW_UP || e.key === kbd.ARROW_DOWN)
|
|
247
240
|
e.preventDefault();
|
|
248
|
-
if (!open) {
|
|
241
|
+
if (!this.root.open.current) {
|
|
249
242
|
if (INTERACTION_KEYS.includes(e.key))
|
|
250
243
|
return;
|
|
251
244
|
if (e.key === kbd.TAB)
|
|
252
245
|
return;
|
|
253
|
-
if (e.key === kbd.BACKSPACE && inputValue === "")
|
|
246
|
+
if (e.key === kbd.BACKSPACE && this.root.inputValue === "")
|
|
254
247
|
return;
|
|
255
248
|
this.root.handleOpen();
|
|
256
249
|
// we need to wait for a tick after the menu opens to ensure the highlighted nodes are
|
|
257
250
|
// set correctly.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
});
|
|
251
|
+
if (this.root.hasValue)
|
|
252
|
+
return;
|
|
253
|
+
const candidateNodes = this.root.getCandidateNodes();
|
|
254
|
+
if (!candidateNodes.length)
|
|
255
|
+
return;
|
|
256
|
+
if (e.key === kbd.ARROW_DOWN) {
|
|
257
|
+
const firstCandidate = candidateNodes[0];
|
|
258
|
+
this.root.setHighlightedNode(firstCandidate);
|
|
259
|
+
}
|
|
260
|
+
else if (e.key === kbd.ARROW_UP) {
|
|
261
|
+
const lastCandidate = candidateNodes[candidateNodes.length - 1];
|
|
262
|
+
this.root.setHighlightedNode(lastCandidate);
|
|
263
|
+
}
|
|
273
264
|
return;
|
|
274
265
|
}
|
|
275
266
|
if (e.key === kbd.TAB) {
|
|
@@ -278,14 +269,13 @@ class SelectInputState {
|
|
|
278
269
|
}
|
|
279
270
|
if (e.key === kbd.ENTER && !e.isComposing) {
|
|
280
271
|
e.preventDefault();
|
|
281
|
-
const
|
|
282
|
-
const isCurrentSelectedValue = highlightedValue === this.root.value.current;
|
|
272
|
+
const isCurrentSelectedValue = this.root.highlightedValue === this.root.value.current;
|
|
283
273
|
if (!this.root.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {
|
|
284
274
|
this.root.handleClose();
|
|
285
275
|
return;
|
|
286
276
|
}
|
|
287
|
-
if (highlightedValue) {
|
|
288
|
-
this.root.toggleItem(highlightedValue, this.root.highlightedLabel ?? undefined);
|
|
277
|
+
if (this.root.highlightedValue) {
|
|
278
|
+
this.root.toggleItem(this.root.highlightedValue, this.root.highlightedLabel ?? undefined);
|
|
289
279
|
}
|
|
290
280
|
if (!this.root.isMulti && !isCurrentSelectedValue) {
|
|
291
281
|
this.root.handleClose();
|
|
@@ -331,13 +321,10 @@ class SelectInputState {
|
|
|
331
321
|
if (!this.root.highlightedNode) {
|
|
332
322
|
this.root.setHighlightedToFirstCandidate();
|
|
333
323
|
}
|
|
334
|
-
// this.root.setHighlightedToFirstCandidate();
|
|
335
324
|
}
|
|
336
325
|
oninput(e) {
|
|
337
326
|
this.root.inputValue = e.currentTarget.value;
|
|
338
|
-
|
|
339
|
-
this.root.setHighlightedToFirstCandidate();
|
|
340
|
-
});
|
|
327
|
+
this.root.setHighlightedToFirstCandidate();
|
|
341
328
|
}
|
|
342
329
|
props = $derived.by(() => ({
|
|
343
330
|
id: this.#id.current,
|
|
@@ -452,12 +439,8 @@ class SelectTriggerState {
|
|
|
452
439
|
this.#dataTypeahead.resetTypeahead();
|
|
453
440
|
this.#domTypeahead.resetTypeahead();
|
|
454
441
|
}
|
|
455
|
-
#handlePointerOpen(
|
|
442
|
+
#handlePointerOpen(_) {
|
|
456
443
|
this.#handleOpen();
|
|
457
|
-
this.root.triggerPointerDownPos = {
|
|
458
|
-
x: Math.round(e.pageX),
|
|
459
|
-
y: Math.round(e.pageY),
|
|
460
|
-
};
|
|
461
444
|
}
|
|
462
445
|
onkeydown(e) {
|
|
463
446
|
this.root.isUsingKeyboard = true;
|
|
@@ -477,21 +460,19 @@ class SelectTriggerState {
|
|
|
477
460
|
}
|
|
478
461
|
// we need to wait for a tick after the menu opens to ensure
|
|
479
462
|
// the highlighted nodes are set correctly
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
});
|
|
463
|
+
if (this.root.hasValue)
|
|
464
|
+
return;
|
|
465
|
+
const candidateNodes = this.root.getCandidateNodes();
|
|
466
|
+
if (!candidateNodes.length)
|
|
467
|
+
return;
|
|
468
|
+
if (e.key === kbd.ARROW_DOWN) {
|
|
469
|
+
const firstCandidate = candidateNodes[0];
|
|
470
|
+
this.root.setHighlightedNode(firstCandidate);
|
|
471
|
+
}
|
|
472
|
+
else if (e.key === kbd.ARROW_UP) {
|
|
473
|
+
const lastCandidate = candidateNodes[candidateNodes.length - 1];
|
|
474
|
+
this.root.setHighlightedNode(lastCandidate);
|
|
475
|
+
}
|
|
495
476
|
return;
|
|
496
477
|
}
|
|
497
478
|
if (e.key === kbd.TAB) {
|
|
@@ -500,15 +481,14 @@ class SelectTriggerState {
|
|
|
500
481
|
}
|
|
501
482
|
if ((e.key === kbd.ENTER || e.key === kbd.SPACE) && !e.isComposing) {
|
|
502
483
|
e.preventDefault();
|
|
503
|
-
const
|
|
504
|
-
const isCurrentSelectedValue = highlightedValue === this.root.value.current;
|
|
484
|
+
const isCurrentSelectedValue = this.root.highlightedValue === this.root.value.current;
|
|
505
485
|
if (!this.root.allowDeselect.current && isCurrentSelectedValue && !this.root.isMulti) {
|
|
506
486
|
this.root.handleClose();
|
|
507
487
|
return;
|
|
508
488
|
}
|
|
509
489
|
//"" is a valid value for a select item so we need to check for that
|
|
510
|
-
if (highlightedValue !== null) {
|
|
511
|
-
this.root.toggleItem(highlightedValue, this.root.highlightedLabel ?? undefined);
|
|
490
|
+
if (this.root.highlightedValue !== null) {
|
|
491
|
+
this.root.toggleItem(this.root.highlightedValue, this.root.highlightedLabel ?? undefined);
|
|
512
492
|
}
|
|
513
493
|
if (!this.root.isMulti && !isCurrentSelectedValue) {
|
|
514
494
|
this.root.handleClose();
|
|
@@ -578,7 +558,7 @@ class SelectTriggerState {
|
|
|
578
558
|
* `pointerdown` fires before the `focus` event, so we can prevent the default
|
|
579
559
|
* behavior of focusing the button and keep focus on the input.
|
|
580
560
|
*/
|
|
581
|
-
onpointerdown
|
|
561
|
+
onpointerdown(e) {
|
|
582
562
|
if (this.root.disabled.current)
|
|
583
563
|
return;
|
|
584
564
|
// prevent opening on touch down which can be triggered when scrolling on touch devices
|
|
@@ -600,13 +580,13 @@ class SelectTriggerState {
|
|
|
600
580
|
this.root.handleClose();
|
|
601
581
|
}
|
|
602
582
|
}
|
|
603
|
-
}
|
|
604
|
-
onpointerup
|
|
583
|
+
}
|
|
584
|
+
onpointerup(e) {
|
|
605
585
|
e.preventDefault();
|
|
606
586
|
if (e.pointerType === "touch") {
|
|
607
587
|
this.#handlePointerOpen(e);
|
|
608
588
|
}
|
|
609
|
-
}
|
|
589
|
+
}
|
|
610
590
|
props = $derived.by(() => ({
|
|
611
591
|
id: this.#id.current,
|
|
612
592
|
disabled: this.root.disabled.current ? true : undefined,
|
|
@@ -639,9 +619,12 @@ class SelectContentState {
|
|
|
639
619
|
},
|
|
640
620
|
deps: () => this.root.open.current,
|
|
641
621
|
});
|
|
642
|
-
onDestroyEffect(() =>
|
|
643
|
-
|
|
644
|
-
|
|
622
|
+
onDestroyEffect(() => {
|
|
623
|
+
this.root.contentNode = null;
|
|
624
|
+
this.isPositioned = false;
|
|
625
|
+
});
|
|
626
|
+
watch(() => this.root.open.current, () => {
|
|
627
|
+
if (this.root.open.current)
|
|
645
628
|
return;
|
|
646
629
|
this.isPositioned = false;
|
|
647
630
|
});
|
|
@@ -705,7 +688,6 @@ class SelectItemState {
|
|
|
705
688
|
isSelected = $derived.by(() => this.root.includesItem(this.value.current));
|
|
706
689
|
isHighlighted = $derived.by(() => this.root.highlightedValue === this.value.current);
|
|
707
690
|
prevHighlighted = new Previous(() => this.isHighlighted);
|
|
708
|
-
textId = $state("");
|
|
709
691
|
mounted = $state(false);
|
|
710
692
|
constructor(props, root) {
|
|
711
693
|
this.root = root;
|
|
@@ -716,7 +698,12 @@ class SelectItemState {
|
|
|
716
698
|
this.onUnhighlight = props.onUnhighlight;
|
|
717
699
|
this.#id = props.id;
|
|
718
700
|
this.#ref = props.ref;
|
|
719
|
-
|
|
701
|
+
useRefById({
|
|
702
|
+
id: this.#id,
|
|
703
|
+
ref: this.#ref,
|
|
704
|
+
deps: () => this.mounted,
|
|
705
|
+
});
|
|
706
|
+
watch([() => this.isHighlighted, () => this.prevHighlighted.current], () => {
|
|
720
707
|
if (this.isHighlighted) {
|
|
721
708
|
this.onHighlight.current();
|
|
722
709
|
}
|
|
@@ -724,12 +711,8 @@ class SelectItemState {
|
|
|
724
711
|
this.onUnhighlight.current();
|
|
725
712
|
}
|
|
726
713
|
});
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
ref: this.#ref,
|
|
730
|
-
});
|
|
731
|
-
watch(() => this.mounted, (isMounted) => {
|
|
732
|
-
if (!isMounted)
|
|
714
|
+
watch(() => this.mounted, () => {
|
|
715
|
+
if (!this.mounted)
|
|
733
716
|
return;
|
|
734
717
|
this.root.setInitialHighlightedNode();
|
|
735
718
|
});
|
|
@@ -899,52 +882,69 @@ class SelectScrollButtonImplState {
|
|
|
899
882
|
ref;
|
|
900
883
|
content;
|
|
901
884
|
root;
|
|
902
|
-
|
|
885
|
+
autoScrollInterval = null;
|
|
886
|
+
userScrollTimer = -1;
|
|
887
|
+
isUserScrolling = false;
|
|
903
888
|
onAutoScroll = noop;
|
|
904
|
-
mounted;
|
|
889
|
+
mounted = $state(false);
|
|
905
890
|
constructor(props, content) {
|
|
906
891
|
this.ref = props.ref;
|
|
907
892
|
this.id = props.id;
|
|
908
|
-
this.mounted = props.mounted;
|
|
909
893
|
this.content = content;
|
|
910
894
|
this.root = content.root;
|
|
911
895
|
useRefById({
|
|
912
896
|
id: this.id,
|
|
913
897
|
ref: this.ref,
|
|
914
|
-
deps: () => this.mounted
|
|
898
|
+
deps: () => this.mounted,
|
|
915
899
|
});
|
|
916
|
-
watch(() => this.mounted
|
|
917
|
-
if (!
|
|
900
|
+
watch(() => this.mounted, () => {
|
|
901
|
+
if (!this.mounted) {
|
|
902
|
+
this.isUserScrolling = false;
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
if (this.isUserScrolling)
|
|
918
906
|
return;
|
|
919
907
|
const activeItem = this.root.highlightedNode;
|
|
920
908
|
activeItem?.scrollIntoView({ block: "nearest" });
|
|
921
909
|
});
|
|
910
|
+
$effect(() => {
|
|
911
|
+
if (this.mounted)
|
|
912
|
+
return;
|
|
913
|
+
this.clearAutoScrollInterval();
|
|
914
|
+
});
|
|
922
915
|
this.onpointerdown = this.onpointerdown.bind(this);
|
|
923
916
|
this.onpointermove = this.onpointermove.bind(this);
|
|
924
917
|
this.onpointerleave = this.onpointerleave.bind(this);
|
|
925
918
|
}
|
|
926
|
-
|
|
927
|
-
|
|
919
|
+
handleUserScroll() {
|
|
920
|
+
window.clearTimeout(this.userScrollTimer);
|
|
921
|
+
this.isUserScrolling = true;
|
|
922
|
+
this.userScrollTimer = window.setTimeout(() => {
|
|
923
|
+
this.isUserScrolling = false;
|
|
924
|
+
}, 200);
|
|
925
|
+
}
|
|
926
|
+
clearAutoScrollInterval() {
|
|
927
|
+
if (this.autoScrollInterval === null)
|
|
928
928
|
return;
|
|
929
|
-
window.clearInterval(this.
|
|
930
|
-
this.
|
|
929
|
+
window.clearInterval(this.autoScrollInterval);
|
|
930
|
+
this.autoScrollInterval = null;
|
|
931
931
|
}
|
|
932
932
|
onpointerdown(_) {
|
|
933
|
-
if (this.
|
|
933
|
+
if (this.autoScrollInterval !== null)
|
|
934
934
|
return;
|
|
935
|
-
this.
|
|
935
|
+
this.autoScrollInterval = window.setInterval(() => {
|
|
936
936
|
this.onAutoScroll();
|
|
937
937
|
}, 50);
|
|
938
938
|
}
|
|
939
939
|
onpointermove(_) {
|
|
940
|
-
if (this.
|
|
940
|
+
if (this.autoScrollInterval !== null)
|
|
941
941
|
return;
|
|
942
|
-
this.
|
|
942
|
+
this.autoScrollInterval = window.setInterval(() => {
|
|
943
943
|
this.onAutoScroll();
|
|
944
944
|
}, 50);
|
|
945
945
|
}
|
|
946
946
|
onpointerleave(_) {
|
|
947
|
-
this.
|
|
947
|
+
this.clearAutoScrollInterval();
|
|
948
948
|
}
|
|
949
949
|
props = $derived.by(() => ({
|
|
950
950
|
id: this.id.current,
|
|
@@ -967,34 +967,41 @@ class SelectScrollDownButtonState {
|
|
|
967
967
|
this.content = state.content;
|
|
968
968
|
this.root = state.root;
|
|
969
969
|
this.state.onAutoScroll = this.handleAutoScroll;
|
|
970
|
-
watch([
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
Math.ceil(viewportNode.scrollTop) < maxScroll - paddingTop;
|
|
979
|
-
});
|
|
980
|
-
};
|
|
981
|
-
handleScroll();
|
|
982
|
-
return on(viewportNode, "scroll", handleScroll);
|
|
983
|
-
});
|
|
984
|
-
$effect(() => {
|
|
985
|
-
if (this.state.mounted.current)
|
|
970
|
+
watch([
|
|
971
|
+
() => this.content.viewportNode,
|
|
972
|
+
() => this.content.isPositioned,
|
|
973
|
+
() => this.root.open.current,
|
|
974
|
+
], () => {
|
|
975
|
+
if (!this.content.viewportNode ||
|
|
976
|
+
!this.content.isPositioned ||
|
|
977
|
+
!this.root.open.current) {
|
|
986
978
|
return;
|
|
987
|
-
|
|
979
|
+
}
|
|
980
|
+
this.handleScroll(true);
|
|
981
|
+
return on(this.content.viewportNode, "scroll", () => this.handleScroll());
|
|
988
982
|
});
|
|
989
983
|
}
|
|
984
|
+
/**
|
|
985
|
+
* @param manual - if true, it means the function was invoked manually outside of an event
|
|
986
|
+
* listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
|
|
987
|
+
*/
|
|
988
|
+
handleScroll = (manual = false) => {
|
|
989
|
+
if (!manual) {
|
|
990
|
+
this.state.handleUserScroll();
|
|
991
|
+
}
|
|
992
|
+
if (!this.content.viewportNode)
|
|
993
|
+
return;
|
|
994
|
+
const maxScroll = this.content.viewportNode.scrollHeight - this.content.viewportNode.clientHeight;
|
|
995
|
+
const paddingTop = Number.parseInt(getComputedStyle(this.content.viewportNode).paddingTop, 10);
|
|
996
|
+
this.canScrollDown =
|
|
997
|
+
Math.ceil(this.content.viewportNode.scrollTop) < maxScroll - paddingTop;
|
|
998
|
+
};
|
|
990
999
|
handleAutoScroll = () => {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight;
|
|
997
|
-
});
|
|
1000
|
+
const viewport = this.content.viewportNode;
|
|
1001
|
+
const selectedItem = this.root.highlightedNode;
|
|
1002
|
+
if (!viewport || !selectedItem)
|
|
1003
|
+
return;
|
|
1004
|
+
viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight;
|
|
998
1005
|
};
|
|
999
1006
|
props = $derived.by(() => ({ ...this.state.props, [this.root.bitsAttrs["scroll-down-button"]]: "" }));
|
|
1000
1007
|
}
|
|
@@ -1008,30 +1015,31 @@ class SelectScrollUpButtonState {
|
|
|
1008
1015
|
this.content = state.content;
|
|
1009
1016
|
this.root = state.root;
|
|
1010
1017
|
this.state.onAutoScroll = this.handleAutoScroll;
|
|
1011
|
-
watch([() => this.content.viewportNode, () => this.content.isPositioned], (
|
|
1012
|
-
if (!viewportNode || !isPositioned)
|
|
1018
|
+
watch([() => this.content.viewportNode, () => this.content.isPositioned], () => {
|
|
1019
|
+
if (!this.content.viewportNode || !this.content.isPositioned)
|
|
1013
1020
|
return;
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
this.canScrollUp = viewportNode.scrollTop - paddingTop > 0;
|
|
1017
|
-
};
|
|
1018
|
-
handleScroll();
|
|
1019
|
-
return on(viewportNode, "scroll", handleScroll);
|
|
1020
|
-
});
|
|
1021
|
-
$effect(() => {
|
|
1022
|
-
if (this.state.mounted.current)
|
|
1023
|
-
return;
|
|
1024
|
-
this.state.clearAutoScrollTimer();
|
|
1021
|
+
this.handleScroll(true);
|
|
1022
|
+
return on(this.content.viewportNode, "scroll", () => this.handleScroll());
|
|
1025
1023
|
});
|
|
1026
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* @param manual - if true, it means the function was invoked manually outside of an event
|
|
1027
|
+
* listener, so we don't call `handleUserScroll` to prevent the auto scroll from kicking in.
|
|
1028
|
+
*/
|
|
1029
|
+
handleScroll = (manual = false) => {
|
|
1030
|
+
if (!manual) {
|
|
1031
|
+
this.state.handleUserScroll();
|
|
1032
|
+
}
|
|
1033
|
+
if (!this.content.viewportNode)
|
|
1034
|
+
return;
|
|
1035
|
+
const paddingTop = Number.parseInt(getComputedStyle(this.content.viewportNode).paddingTop, 10);
|
|
1036
|
+
this.canScrollUp = this.content.viewportNode.scrollTop - paddingTop > 0.1;
|
|
1037
|
+
};
|
|
1027
1038
|
handleAutoScroll = () => {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
return;
|
|
1033
|
-
viewport.scrollTop = viewport.scrollTop - selectedItem.offsetHeight;
|
|
1034
|
-
});
|
|
1039
|
+
if (!this.content.viewportNode || !this.root.highlightedNode)
|
|
1040
|
+
return;
|
|
1041
|
+
this.content.viewportNode.scrollTop =
|
|
1042
|
+
this.content.viewportNode.scrollTop - this.root.highlightedNode.offsetHeight;
|
|
1035
1043
|
};
|
|
1036
1044
|
props = $derived.by(() => ({ ...this.state.props, [this.root.bitsAttrs["scroll-up-button"]]: "" }));
|
|
1037
1045
|
}
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
style = {},
|
|
25
25
|
wrapperId = useId(),
|
|
26
26
|
customAnchor = null,
|
|
27
|
+
enabled,
|
|
27
28
|
}: ContentImplProps = $props();
|
|
28
29
|
|
|
29
30
|
const contentState = useFloatingContentState({
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
strategy: box.with(() => strategy),
|
|
44
45
|
dir: box.with(() => dir),
|
|
45
46
|
style: box.with(() => style),
|
|
46
|
-
enabled: box.with(() =>
|
|
47
|
+
enabled: box.with(() => enabled),
|
|
47
48
|
wrapperId: box.with(() => wrapperId),
|
|
48
49
|
customAnchor: box.with(() => customAnchor),
|
|
49
50
|
});
|
|
@@ -106,6 +106,7 @@ export type FloatingLayerContentImplProps = {
|
|
|
106
106
|
* Callback that is called when the floating element is placed.
|
|
107
107
|
*/
|
|
108
108
|
onPlaced?: () => void;
|
|
109
|
+
enabled: boolean;
|
|
109
110
|
} & FloatingLayerContentProps;
|
|
110
111
|
export type FloatingLayerAnchorProps = {
|
|
111
112
|
id: string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { flushSync } from "svelte";
|
|
2
|
+
/**
|
|
3
|
+
* Calls a function the next frame after all animations have finished.
|
|
4
|
+
*/
|
|
5
|
+
export function useAfterAnimations(getNode) {
|
|
6
|
+
let frame = -1;
|
|
7
|
+
function cancelFrame() {
|
|
8
|
+
cancelAnimationFrame(frame);
|
|
9
|
+
}
|
|
10
|
+
$effect(() => cancelFrame);
|
|
11
|
+
return (fn) => {
|
|
12
|
+
cancelFrame();
|
|
13
|
+
const node = getNode();
|
|
14
|
+
if (!node)
|
|
15
|
+
return;
|
|
16
|
+
if (typeof node.getAnimations !== "function" || globalThis.bitsAnimationsDisabled) {
|
|
17
|
+
fn();
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
frame = requestAnimationFrame(() => {
|
|
21
|
+
Promise.allSettled(node.getAnimations().map((anim) => anim.finished)).then(() => {
|
|
22
|
+
flushSync(fn);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bits-ui",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.83",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "github:huntabyte/bits-ui",
|
|
6
6
|
"funding": "https://github.com/sponsors/huntabyte",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@internationalized/date": "^3.5.6",
|
|
44
44
|
"esm-env": "^1.1.2",
|
|
45
45
|
"runed": "^0.23.2",
|
|
46
|
-
"svelte-toolbelt": "^0.7.
|
|
46
|
+
"svelte-toolbelt": "^0.7.1"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"svelte": "^5.11.0"
|