@huin-core/react-select 1.0.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.mjs ADDED
@@ -0,0 +1,1353 @@
1
+ "use client";
2
+
3
+ // packages/react/select/src/Select.tsx
4
+ import * as React from "react";
5
+ import { createCollection } from "@huin-core/react-collection";
6
+ import { useComposedRefs } from "@huin-core/react-compose-refs";
7
+ import { createContextScope } from "@huin-core/react-context";
8
+ import { useDirection } from "@huin-core/react-direction";
9
+ import { useId } from "@huin-core/react-id";
10
+ import * as PopperPrimitive from "@huin-core/react-popper";
11
+ import { createPopperScope } from "@huin-core/react-popper";
12
+ import { useControllableState } from "@huin-core/react-use-controllable-state";
13
+ import { usePrevious } from "@huin-core/react-use-previous";
14
+ import { VisuallyHidden } from "@huin-core/react-visually-hidden";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ var SELECT_NAME = "Select";
17
+ var [Collection, useCollection, createCollectionScope] = createCollection(SELECT_NAME);
18
+ var [createSelectContext, createSelectScope] = createContextScope(
19
+ SELECT_NAME,
20
+ [createCollectionScope, createPopperScope]
21
+ );
22
+ var usePopperScope = createPopperScope();
23
+ var [SelectProvider, useSelectContext] = createSelectContext(SELECT_NAME);
24
+ var [SelectNativeOptionsProvider, useSelectNativeOptionsContext] = createSelectContext(SELECT_NAME);
25
+ var Select = (props) => {
26
+ const {
27
+ __scopeSelect,
28
+ children,
29
+ open: openProp,
30
+ defaultOpen,
31
+ onOpenChange,
32
+ value: valueProp,
33
+ defaultValue,
34
+ onValueChange,
35
+ dir,
36
+ name,
37
+ autoComplete,
38
+ disabled,
39
+ required
40
+ } = props;
41
+ const popperScope = usePopperScope(__scopeSelect);
42
+ const [trigger, setTrigger] = React.useState(
43
+ null
44
+ );
45
+ const [valueNode, setValueNode] = React.useState(
46
+ null
47
+ );
48
+ const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false);
49
+ const direction = useDirection(dir);
50
+ const [open = false, setOpen] = useControllableState({
51
+ prop: openProp,
52
+ defaultProp: defaultOpen,
53
+ onChange: onOpenChange
54
+ });
55
+ const [value, setValue] = useControllableState({
56
+ prop: valueProp,
57
+ defaultProp: defaultValue,
58
+ onChange: onValueChange
59
+ });
60
+ const triggerPointerDownPosRef = React.useRef(null);
61
+ const isFormControl = trigger ? Boolean(trigger.closest("form")) : true;
62
+ const [nativeOptionsSet, setNativeOptionsSet] = React.useState(
63
+ /* @__PURE__ */ new Set()
64
+ );
65
+ const nativeSelectKey = Array.from(nativeOptionsSet).map((option) => option.props.value).join(";");
66
+ return /* @__PURE__ */ jsx(PopperPrimitive.Root, { ...popperScope, children: /* @__PURE__ */ jsxs(
67
+ SelectProvider,
68
+ {
69
+ required,
70
+ scope: __scopeSelect,
71
+ trigger,
72
+ onTriggerChange: setTrigger,
73
+ valueNode,
74
+ onValueNodeChange: setValueNode,
75
+ valueNodeHasChildren,
76
+ onValueNodeHasChildrenChange: setValueNodeHasChildren,
77
+ contentId: useId(),
78
+ value,
79
+ onValueChange: setValue,
80
+ open,
81
+ onOpenChange: setOpen,
82
+ dir: direction,
83
+ triggerPointerDownPosRef,
84
+ disabled,
85
+ children: [
86
+ /* @__PURE__ */ jsx(Collection.Provider, { scope: __scopeSelect, children: /* @__PURE__ */ jsx(
87
+ SelectNativeOptionsProvider,
88
+ {
89
+ scope: props.__scopeSelect,
90
+ onNativeOptionAdd: React.useCallback((option) => {
91
+ setNativeOptionsSet((prev) => new Set(prev).add(option));
92
+ }, []),
93
+ onNativeOptionRemove: React.useCallback((option) => {
94
+ setNativeOptionsSet((prev) => {
95
+ const optionsSet = new Set(prev);
96
+ optionsSet.delete(option);
97
+ return optionsSet;
98
+ });
99
+ }, []),
100
+ children
101
+ }
102
+ ) }),
103
+ isFormControl ? /* @__PURE__ */ jsxs(
104
+ BubbleSelect,
105
+ {
106
+ "aria-hidden": true,
107
+ required,
108
+ tabIndex: -1,
109
+ name,
110
+ autoComplete,
111
+ value,
112
+ onChange: (event) => setValue(event.target.value),
113
+ disabled,
114
+ children: [
115
+ value === void 0 ? /* @__PURE__ */ jsx("option", { value: "" }) : null,
116
+ Array.from(nativeOptionsSet)
117
+ ]
118
+ },
119
+ nativeSelectKey
120
+ ) : null
121
+ ]
122
+ }
123
+ ) });
124
+ };
125
+ Select.displayName = SELECT_NAME;
126
+ var BubbleSelect = React.forwardRef((props, forwardedRef) => {
127
+ const { value, ...selectProps } = props;
128
+ const ref = React.useRef(null);
129
+ const composedRefs = useComposedRefs(forwardedRef, ref);
130
+ const prevValue = usePrevious(value);
131
+ React.useEffect(() => {
132
+ const select = ref.current;
133
+ const selectProto = window.HTMLSelectElement.prototype;
134
+ const descriptor = Object.getOwnPropertyDescriptor(
135
+ selectProto,
136
+ "value"
137
+ );
138
+ const setValue = descriptor.set;
139
+ if (prevValue !== value && setValue) {
140
+ const event = new Event("change", { bubbles: true });
141
+ setValue.call(select, value);
142
+ select.dispatchEvent(event);
143
+ }
144
+ }, [prevValue, value]);
145
+ return /* @__PURE__ */ jsx(VisuallyHidden, { asChild: true, children: /* @__PURE__ */ jsx("select", { ...selectProps, ref: composedRefs, defaultValue: value }) });
146
+ });
147
+ BubbleSelect.displayName = "BubbleSelect";
148
+ var Root2 = Select;
149
+
150
+ // packages/react/select/src/SelectTrigger.tsx
151
+ import React5 from "react";
152
+ import { Primitive as Primitive4 } from "@huin-core/react-primitive";
153
+ import { useComposedRefs as useComposedRefs5 } from "@huin-core/react-compose-refs";
154
+
155
+ // packages/react/select/src/SelectContent.tsx
156
+ import React3 from "react";
157
+ import { useLayoutEffect } from "@huin-core/react-use-layout-effect";
158
+ import ReactDOM from "react-dom";
159
+ import { Primitive as Primitive2 } from "@huin-core/react-primitive";
160
+
161
+ // packages/react/select/src/SelectViewport.tsx
162
+ import React2 from "react";
163
+ import { Primitive } from "@huin-core/react-primitive";
164
+ import { useComposedRefs as useComposedRefs2 } from "@huin-core/react-compose-refs";
165
+ import { composeEventHandlers } from "@huin-core/primitive";
166
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
167
+ var CONTENT_NAME = "SelectContent";
168
+ var [SelectViewportProvider, useSelectViewportContext] = createSelectContext(CONTENT_NAME, {});
169
+ var VIEWPORT_NAME = "SelectViewport";
170
+ var SelectViewport = React2.forwardRef((props, forwardedRef) => {
171
+ const { __scopeSelect, nonce, ...viewportProps } = props;
172
+ const contentContext = useSelectContentContext(VIEWPORT_NAME, __scopeSelect);
173
+ const viewportContext = useSelectViewportContext(
174
+ VIEWPORT_NAME,
175
+ __scopeSelect
176
+ );
177
+ const composedRefs = useComposedRefs2(
178
+ forwardedRef,
179
+ contentContext.onViewportChange
180
+ );
181
+ const prevScrollTopRef = React2.useRef(0);
182
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
183
+ /* @__PURE__ */ jsx2(
184
+ "style",
185
+ {
186
+ dangerouslySetInnerHTML: {
187
+ __html: `[data-huin-core-select-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-huin-core-select-viewport]::-webkit-scrollbar{display:none}`
188
+ },
189
+ nonce
190
+ }
191
+ ),
192
+ /* @__PURE__ */ jsx2(Collection.Slot, { scope: __scopeSelect, children: /* @__PURE__ */ jsx2(
193
+ Primitive.div,
194
+ {
195
+ "data-huin-core-select-viewport": "",
196
+ role: "presentation",
197
+ ...viewportProps,
198
+ ref: composedRefs,
199
+ style: {
200
+ // we use position: 'relative' here on the `viewport` so that when we call
201
+ // `selectedItem.offsetTop` in calculations, the offset is relative to the viewport
202
+ // (independent of the scrollUpButton).
203
+ position: "relative",
204
+ flex: 1,
205
+ overflow: "auto",
206
+ ...viewportProps.style
207
+ },
208
+ onScroll: composeEventHandlers(viewportProps.onScroll, (event) => {
209
+ const viewport = event.currentTarget;
210
+ const { contentWrapper, shouldExpandOnScrollRef } = viewportContext;
211
+ if (shouldExpandOnScrollRef?.current && contentWrapper) {
212
+ const scrolledBy = Math.abs(
213
+ prevScrollTopRef.current - viewport.scrollTop
214
+ );
215
+ if (scrolledBy > 0) {
216
+ const availableHeight = window.innerHeight - CONTENT_MARGIN * 2;
217
+ const cssMinHeight = parseFloat(contentWrapper.style.minHeight);
218
+ const cssHeight = parseFloat(contentWrapper.style.height);
219
+ const prevHeight = Math.max(cssMinHeight, cssHeight);
220
+ if (prevHeight < availableHeight) {
221
+ const nextHeight = prevHeight + scrolledBy;
222
+ const clampedNextHeight = Math.min(
223
+ availableHeight,
224
+ nextHeight
225
+ );
226
+ const heightDiff = nextHeight - clampedNextHeight;
227
+ contentWrapper.style.height = clampedNextHeight + "px";
228
+ if (contentWrapper.style.bottom === "0px") {
229
+ viewport.scrollTop = heightDiff > 0 ? heightDiff : 0;
230
+ contentWrapper.style.justifyContent = "flex-end";
231
+ }
232
+ }
233
+ }
234
+ }
235
+ prevScrollTopRef.current = viewport.scrollTop;
236
+ })
237
+ }
238
+ ) })
239
+ ] });
240
+ });
241
+ SelectViewport.displayName = VIEWPORT_NAME;
242
+
243
+ // packages/react/select/src/SelectContent.tsx
244
+ import { DismissableLayer } from "@huin-core/react-dismissable-layer";
245
+ import { FocusScope } from "@huin-core/react-focus-scope";
246
+ import { useComposedRefs as useComposedRefs3 } from "@huin-core/react-compose-refs";
247
+ import { hideOthers } from "aria-hidden";
248
+ import { useFocusGuards } from "@huin-core/react-focus-guards";
249
+ import { RemoveScroll } from "react-remove-scroll";
250
+ import { Slot } from "@huin-core/react-slot";
251
+ import { composeEventHandlers as composeEventHandlers2 } from "@huin-core/primitive";
252
+ import { clamp } from "@huin-core/number";
253
+ import * as PopperPrimitive2 from "@huin-core/react-popper";
254
+ import { useCallbackRef } from "@huin-core/react-use-callback-ref";
255
+ import { jsx as jsx3 } from "react/jsx-runtime";
256
+ var CONTENT_NAME2 = "SelectContent";
257
+ var SelectContent = React3.forwardRef(
258
+ (props, forwardedRef) => {
259
+ const context = useSelectContext(CONTENT_NAME2, props.__scopeSelect);
260
+ const [fragment, setFragment] = React3.useState();
261
+ useLayoutEffect(() => {
262
+ setFragment(new DocumentFragment());
263
+ }, []);
264
+ if (!context.open) {
265
+ const frag = fragment;
266
+ return frag ? ReactDOM.createPortal(
267
+ /* @__PURE__ */ jsx3(SelectContentProvider, { scope: props.__scopeSelect, children: /* @__PURE__ */ jsx3(Collection.Slot, { scope: props.__scopeSelect, children: /* @__PURE__ */ jsx3("div", { children: props.children }) }) }),
268
+ frag
269
+ ) : null;
270
+ }
271
+ return /* @__PURE__ */ jsx3(SelectContentImpl, { ...props, ref: forwardedRef });
272
+ }
273
+ );
274
+ SelectContent.displayName = CONTENT_NAME2;
275
+ var CONTENT_MARGIN = 10;
276
+ var [SelectContentProvider, useSelectContentContext] = createSelectContext(CONTENT_NAME2);
277
+ var CONTENT_IMPL_NAME = "SelectContentImpl";
278
+ var SelectContentImpl = React3.forwardRef((props, forwardedRef) => {
279
+ const {
280
+ __scopeSelect,
281
+ position = "item-aligned",
282
+ onCloseAutoFocus,
283
+ onEscapeKeyDown,
284
+ onPointerDownOutside,
285
+ //
286
+ // PopperContent props
287
+ side,
288
+ sideOffset,
289
+ align,
290
+ alignOffset,
291
+ arrowPadding,
292
+ collisionBoundary,
293
+ collisionPadding,
294
+ sticky,
295
+ hideWhenDetached,
296
+ avoidCollisions,
297
+ //
298
+ ...contentProps
299
+ } = props;
300
+ const context = useSelectContext(CONTENT_NAME2, __scopeSelect);
301
+ const [content, setContent] = React3.useState(
302
+ null
303
+ );
304
+ const [viewport, setViewport] = React3.useState(
305
+ null
306
+ );
307
+ const composedRefs = useComposedRefs3(
308
+ forwardedRef,
309
+ (node) => setContent(node)
310
+ );
311
+ const [selectedItem, setSelectedItem] = React3.useState(null);
312
+ const [selectedItemText, setSelectedItemText] = React3.useState(null);
313
+ const getItems = useCollection(__scopeSelect);
314
+ const [isPositioned, setIsPositioned] = React3.useState(false);
315
+ const firstValidItemFoundRef = React3.useRef(false);
316
+ React3.useEffect(() => {
317
+ if (content) return hideOthers(content);
318
+ }, [content]);
319
+ useFocusGuards();
320
+ const focusFirst = React3.useCallback(
321
+ (candidates) => {
322
+ const [firstItem, ...restItems] = getItems().map(
323
+ (item) => item.ref.current
324
+ );
325
+ const [lastItem] = restItems.slice(-1);
326
+ const PREVIOUSLY_FOCUSED_ELEMENT = document.activeElement;
327
+ for (const candidate of candidates) {
328
+ if (candidate === PREVIOUSLY_FOCUSED_ELEMENT) return;
329
+ candidate?.scrollIntoView({ block: "nearest" });
330
+ if (candidate === firstItem && viewport) viewport.scrollTop = 0;
331
+ if (candidate === lastItem && viewport)
332
+ viewport.scrollTop = viewport.scrollHeight;
333
+ candidate?.focus();
334
+ if (document.activeElement !== PREVIOUSLY_FOCUSED_ELEMENT) return;
335
+ }
336
+ },
337
+ [getItems, viewport]
338
+ );
339
+ const focusSelectedItem = React3.useCallback(
340
+ () => focusFirst([selectedItem, content]),
341
+ [focusFirst, selectedItem, content]
342
+ );
343
+ React3.useEffect(() => {
344
+ if (isPositioned) {
345
+ focusSelectedItem();
346
+ }
347
+ }, [isPositioned, focusSelectedItem]);
348
+ const { onOpenChange, triggerPointerDownPosRef } = context;
349
+ React3.useEffect(() => {
350
+ if (content) {
351
+ let pointerMoveDelta = { x: 0, y: 0 };
352
+ const handlePointerMove = (event) => {
353
+ pointerMoveDelta = {
354
+ x: Math.abs(
355
+ Math.round(event.pageX) - (triggerPointerDownPosRef.current?.x ?? 0)
356
+ ),
357
+ y: Math.abs(
358
+ Math.round(event.pageY) - (triggerPointerDownPosRef.current?.y ?? 0)
359
+ )
360
+ };
361
+ };
362
+ const handlePointerUp = (event) => {
363
+ if (pointerMoveDelta.x <= 10 && pointerMoveDelta.y <= 10) {
364
+ event.preventDefault();
365
+ } else {
366
+ if (!content.contains(event.target)) {
367
+ onOpenChange(false);
368
+ }
369
+ }
370
+ document.removeEventListener("pointermove", handlePointerMove);
371
+ triggerPointerDownPosRef.current = null;
372
+ };
373
+ if (triggerPointerDownPosRef.current !== null) {
374
+ document.addEventListener("pointermove", handlePointerMove);
375
+ document.addEventListener("pointerup", handlePointerUp, {
376
+ capture: true,
377
+ once: true
378
+ });
379
+ }
380
+ return () => {
381
+ document.removeEventListener("pointermove", handlePointerMove);
382
+ document.removeEventListener("pointerup", handlePointerUp, {
383
+ capture: true
384
+ });
385
+ };
386
+ }
387
+ }, [content, onOpenChange, triggerPointerDownPosRef]);
388
+ React3.useEffect(() => {
389
+ const close = () => onOpenChange(false);
390
+ window.addEventListener("blur", close);
391
+ window.addEventListener("resize", close);
392
+ return () => {
393
+ window.removeEventListener("blur", close);
394
+ window.removeEventListener("resize", close);
395
+ };
396
+ }, [onOpenChange]);
397
+ const [searchRef, handleTypeaheadSearch] = useTypeaheadSearch((search) => {
398
+ const enabledItems = getItems().filter((item) => !item.disabled);
399
+ const currentItem = enabledItems.find(
400
+ (item) => item.ref.current === document.activeElement
401
+ );
402
+ const nextItem = findNextItem(enabledItems, search, currentItem);
403
+ if (nextItem) {
404
+ setTimeout(() => nextItem.ref.current.focus());
405
+ }
406
+ });
407
+ const itemRefCallback = React3.useCallback(
408
+ (node, value, disabled) => {
409
+ const isFirstValidItem = !firstValidItemFoundRef.current && !disabled;
410
+ const isSelectedItem = context.value !== void 0 && context.value === value;
411
+ if (isSelectedItem || isFirstValidItem) {
412
+ setSelectedItem(node);
413
+ if (isFirstValidItem) firstValidItemFoundRef.current = true;
414
+ }
415
+ },
416
+ [context.value]
417
+ );
418
+ const handleItemLeave = React3.useCallback(() => content?.focus(), [content]);
419
+ const itemTextRefCallback = React3.useCallback(
420
+ (node, value, disabled) => {
421
+ const isFirstValidItem = !firstValidItemFoundRef.current && !disabled;
422
+ const isSelectedItem = context.value !== void 0 && context.value === value;
423
+ if (isSelectedItem || isFirstValidItem) {
424
+ setSelectedItemText(node);
425
+ }
426
+ },
427
+ [context.value]
428
+ );
429
+ const SelectPosition = position === "popper" ? SelectPopperPosition : SelectItemAlignedPosition;
430
+ const popperContentProps = SelectPosition === SelectPopperPosition ? {
431
+ side,
432
+ sideOffset,
433
+ align,
434
+ alignOffset,
435
+ arrowPadding,
436
+ collisionBoundary,
437
+ collisionPadding,
438
+ sticky,
439
+ hideWhenDetached,
440
+ avoidCollisions
441
+ } : {};
442
+ return /* @__PURE__ */ jsx3(
443
+ SelectContentProvider,
444
+ {
445
+ scope: __scopeSelect,
446
+ content,
447
+ viewport,
448
+ onViewportChange: setViewport,
449
+ itemRefCallback,
450
+ selectedItem,
451
+ onItemLeave: handleItemLeave,
452
+ itemTextRefCallback,
453
+ focusSelectedItem,
454
+ selectedItemText,
455
+ position,
456
+ isPositioned,
457
+ searchRef,
458
+ children: /* @__PURE__ */ jsx3(RemoveScroll, { as: Slot, allowPinchZoom: true, children: /* @__PURE__ */ jsx3(
459
+ FocusScope,
460
+ {
461
+ asChild: true,
462
+ trapped: context.open,
463
+ onMountAutoFocus: (event) => {
464
+ event.preventDefault();
465
+ },
466
+ onUnmountAutoFocus: composeEventHandlers2(
467
+ onCloseAutoFocus,
468
+ (event) => {
469
+ context.trigger?.focus({ preventScroll: true });
470
+ event.preventDefault();
471
+ }
472
+ ),
473
+ children: /* @__PURE__ */ jsx3(
474
+ DismissableLayer,
475
+ {
476
+ asChild: true,
477
+ disableOutsidePointerEvents: true,
478
+ onEscapeKeyDown,
479
+ onPointerDownOutside,
480
+ onFocusOutside: (event) => event.preventDefault(),
481
+ onDismiss: () => context.onOpenChange(false),
482
+ children: /* @__PURE__ */ jsx3(
483
+ SelectPosition,
484
+ {
485
+ role: "listbox",
486
+ id: context.contentId,
487
+ "data-state": context.open ? "open" : "closed",
488
+ dir: context.dir,
489
+ onContextMenu: (event) => event.preventDefault(),
490
+ ...contentProps,
491
+ ...popperContentProps,
492
+ onPlaced: () => setIsPositioned(true),
493
+ ref: composedRefs,
494
+ style: {
495
+ // flex layout so we can place the scroll buttons properly
496
+ display: "flex",
497
+ flexDirection: "column",
498
+ // reset the outline by default as the content MAY get focused
499
+ outline: "none",
500
+ ...contentProps.style
501
+ },
502
+ onKeyDown: composeEventHandlers2(
503
+ contentProps.onKeyDown,
504
+ (event) => {
505
+ const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
506
+ if (event.key === "Tab") event.preventDefault();
507
+ if (!isModifierKey && event.key.length === 1)
508
+ handleTypeaheadSearch(event.key);
509
+ if (["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) {
510
+ const items = getItems().filter((item) => !item.disabled);
511
+ let candidateNodes = items.map((item) => item.ref.current);
512
+ if (["ArrowUp", "End"].includes(event.key)) {
513
+ candidateNodes = candidateNodes.slice().reverse();
514
+ }
515
+ if (["ArrowUp", "ArrowDown"].includes(event.key)) {
516
+ const currentElement = event.target;
517
+ const currentIndex = candidateNodes.indexOf(currentElement);
518
+ candidateNodes = candidateNodes.slice(currentIndex + 1);
519
+ }
520
+ setTimeout(() => focusFirst(candidateNodes));
521
+ event.preventDefault();
522
+ }
523
+ }
524
+ )
525
+ }
526
+ )
527
+ }
528
+ )
529
+ }
530
+ ) })
531
+ }
532
+ );
533
+ });
534
+ SelectContentImpl.displayName = CONTENT_IMPL_NAME;
535
+ var ITEM_ALIGNED_POSITION_NAME = "SelectItemAlignedPosition";
536
+ var SelectItemAlignedPosition = React3.forwardRef((props, forwardedRef) => {
537
+ const { __scopeSelect, onPlaced, ...popperProps } = props;
538
+ const context = useSelectContext(CONTENT_NAME2, __scopeSelect);
539
+ const contentContext = useSelectContentContext(CONTENT_NAME2, __scopeSelect);
540
+ const [contentWrapper, setContentWrapper] = React3.useState(null);
541
+ const [content, setContent] = React3.useState(null);
542
+ const composedRefs = useComposedRefs3(
543
+ forwardedRef,
544
+ (node) => setContent(node)
545
+ );
546
+ const getItems = useCollection(__scopeSelect);
547
+ const shouldExpandOnScrollRef = React3.useRef(false);
548
+ const shouldRepositionRef = React3.useRef(true);
549
+ const { viewport, selectedItem, selectedItemText, focusSelectedItem } = contentContext;
550
+ const position = React3.useCallback(() => {
551
+ if (context.trigger && context.valueNode && contentWrapper && content && viewport && selectedItem && selectedItemText) {
552
+ const triggerRect = context.trigger.getBoundingClientRect();
553
+ const contentRect = content.getBoundingClientRect();
554
+ const valueNodeRect = context.valueNode.getBoundingClientRect();
555
+ const itemTextRect = selectedItemText.getBoundingClientRect();
556
+ if (context.dir !== "rtl") {
557
+ const itemTextOffset = itemTextRect.left - contentRect.left;
558
+ const left = valueNodeRect.left - itemTextOffset;
559
+ const leftDelta = triggerRect.left - left;
560
+ const minContentWidth = triggerRect.width + leftDelta;
561
+ const contentWidth = Math.max(minContentWidth, contentRect.width);
562
+ const rightEdge = window.innerWidth - CONTENT_MARGIN;
563
+ const clampedLeft = clamp(left, [
564
+ CONTENT_MARGIN,
565
+ rightEdge - contentWidth
566
+ ]);
567
+ contentWrapper.style.minWidth = minContentWidth + "px";
568
+ contentWrapper.style.left = clampedLeft + "px";
569
+ } else {
570
+ const itemTextOffset = contentRect.right - itemTextRect.right;
571
+ const right = window.innerWidth - valueNodeRect.right - itemTextOffset;
572
+ const rightDelta = window.innerWidth - triggerRect.right - right;
573
+ const minContentWidth = triggerRect.width + rightDelta;
574
+ const contentWidth = Math.max(minContentWidth, contentRect.width);
575
+ const leftEdge = window.innerWidth - CONTENT_MARGIN;
576
+ const clampedRight = clamp(right, [
577
+ CONTENT_MARGIN,
578
+ leftEdge - contentWidth
579
+ ]);
580
+ contentWrapper.style.minWidth = minContentWidth + "px";
581
+ contentWrapper.style.right = clampedRight + "px";
582
+ }
583
+ const items = getItems();
584
+ const availableHeight = window.innerHeight - CONTENT_MARGIN * 2;
585
+ const itemsHeight = viewport.scrollHeight;
586
+ const contentStyles = window.getComputedStyle(content);
587
+ const contentBorderTopWidth = parseInt(contentStyles.borderTopWidth, 10);
588
+ const contentPaddingTop = parseInt(contentStyles.paddingTop, 10);
589
+ const contentBorderBottomWidth = parseInt(
590
+ contentStyles.borderBottomWidth,
591
+ 10
592
+ );
593
+ const contentPaddingBottom = parseInt(contentStyles.paddingBottom, 10);
594
+ const fullContentHeight = contentBorderTopWidth + contentPaddingTop + itemsHeight + contentPaddingBottom + contentBorderBottomWidth;
595
+ const minContentHeight = Math.min(
596
+ selectedItem.offsetHeight * 5,
597
+ fullContentHeight
598
+ );
599
+ const viewportStyles = window.getComputedStyle(viewport);
600
+ const viewportPaddingTop = parseInt(viewportStyles.paddingTop, 10);
601
+ const viewportPaddingBottom = parseInt(viewportStyles.paddingBottom, 10);
602
+ const topEdgeToTriggerMiddle = triggerRect.top + triggerRect.height / 2 - CONTENT_MARGIN;
603
+ const triggerMiddleToBottomEdge = availableHeight - topEdgeToTriggerMiddle;
604
+ const selectedItemHalfHeight = selectedItem.offsetHeight / 2;
605
+ const itemOffsetMiddle = selectedItem.offsetTop + selectedItemHalfHeight;
606
+ const contentTopToItemMiddle = contentBorderTopWidth + contentPaddingTop + itemOffsetMiddle;
607
+ const itemMiddleToContentBottom = fullContentHeight - contentTopToItemMiddle;
608
+ const willAlignWithoutTopOverflow = contentTopToItemMiddle <= topEdgeToTriggerMiddle;
609
+ if (willAlignWithoutTopOverflow) {
610
+ const isLastItem = selectedItem === items[items.length - 1].ref.current;
611
+ contentWrapper.style.bottom = "0px";
612
+ const viewportOffsetBottom = content.clientHeight - viewport.offsetTop - viewport.offsetHeight;
613
+ const clampedTriggerMiddleToBottomEdge = Math.max(
614
+ triggerMiddleToBottomEdge,
615
+ selectedItemHalfHeight + // viewport might have padding bottom, include it to avoid a scrollable viewport
616
+ (isLastItem ? viewportPaddingBottom : 0) + viewportOffsetBottom + contentBorderBottomWidth
617
+ );
618
+ const height = contentTopToItemMiddle + clampedTriggerMiddleToBottomEdge;
619
+ contentWrapper.style.height = height + "px";
620
+ } else {
621
+ const isFirstItem = selectedItem === items[0].ref.current;
622
+ contentWrapper.style.top = "0px";
623
+ const clampedTopEdgeToTriggerMiddle = Math.max(
624
+ topEdgeToTriggerMiddle,
625
+ contentBorderTopWidth + viewport.offsetTop + // viewport might have padding top, include it to avoid a scrollable viewport
626
+ (isFirstItem ? viewportPaddingTop : 0) + selectedItemHalfHeight
627
+ );
628
+ const height = clampedTopEdgeToTriggerMiddle + itemMiddleToContentBottom;
629
+ contentWrapper.style.height = height + "px";
630
+ viewport.scrollTop = contentTopToItemMiddle - topEdgeToTriggerMiddle + viewport.offsetTop;
631
+ }
632
+ contentWrapper.style.margin = `${CONTENT_MARGIN}px 0`;
633
+ contentWrapper.style.minHeight = minContentHeight + "px";
634
+ contentWrapper.style.maxHeight = availableHeight + "px";
635
+ onPlaced?.();
636
+ requestAnimationFrame(() => shouldExpandOnScrollRef.current = true);
637
+ }
638
+ }, [
639
+ getItems,
640
+ context.trigger,
641
+ context.valueNode,
642
+ contentWrapper,
643
+ content,
644
+ viewport,
645
+ selectedItem,
646
+ selectedItemText,
647
+ context.dir,
648
+ onPlaced
649
+ ]);
650
+ useLayoutEffect(() => position(), [position]);
651
+ const [contentZIndex, setContentZIndex] = React3.useState();
652
+ useLayoutEffect(() => {
653
+ if (content) setContentZIndex(window.getComputedStyle(content).zIndex);
654
+ }, [content]);
655
+ const handleScrollButtonChange = React3.useCallback(
656
+ (node) => {
657
+ if (node && shouldRepositionRef.current === true) {
658
+ position();
659
+ focusSelectedItem?.();
660
+ shouldRepositionRef.current = false;
661
+ }
662
+ },
663
+ [position, focusSelectedItem]
664
+ );
665
+ return /* @__PURE__ */ jsx3(
666
+ SelectViewportProvider,
667
+ {
668
+ scope: __scopeSelect,
669
+ contentWrapper,
670
+ shouldExpandOnScrollRef,
671
+ onScrollButtonChange: handleScrollButtonChange,
672
+ children: /* @__PURE__ */ jsx3(
673
+ "div",
674
+ {
675
+ ref: setContentWrapper,
676
+ style: {
677
+ display: "flex",
678
+ flexDirection: "column",
679
+ position: "fixed",
680
+ zIndex: contentZIndex
681
+ },
682
+ children: /* @__PURE__ */ jsx3(
683
+ Primitive2.div,
684
+ {
685
+ ...popperProps,
686
+ ref: composedRefs,
687
+ style: {
688
+ // When we get the height of the content, it includes borders. If we were to set
689
+ // the height without having `boxSizing: 'border-box'` it would be too big.
690
+ boxSizing: "border-box",
691
+ // We need to ensure the content doesn't get taller than the wrapper
692
+ maxHeight: "100%",
693
+ ...popperProps.style
694
+ }
695
+ }
696
+ )
697
+ }
698
+ )
699
+ }
700
+ );
701
+ });
702
+ SelectItemAlignedPosition.displayName = ITEM_ALIGNED_POSITION_NAME;
703
+ var POPPER_POSITION_NAME = "SelectPopperPosition";
704
+ var SelectPopperPosition = React3.forwardRef((props, forwardedRef) => {
705
+ const {
706
+ __scopeSelect,
707
+ align = "start",
708
+ collisionPadding = CONTENT_MARGIN,
709
+ ...popperProps
710
+ } = props;
711
+ const popperScope = usePopperScope(__scopeSelect);
712
+ return /* @__PURE__ */ jsx3(
713
+ PopperPrimitive2.Content,
714
+ {
715
+ ...popperScope,
716
+ ...popperProps,
717
+ ref: forwardedRef,
718
+ align,
719
+ collisionPadding,
720
+ style: {
721
+ // Ensure border-box for floating-ui calculations
722
+ boxSizing: "border-box",
723
+ ...popperProps.style,
724
+ // re-namespace exposed content custom properties
725
+ ...{
726
+ "--huin-core-select-content-transform-origin": "var(--huin-core-popper-transform-origin)",
727
+ "--huin-core-select-content-available-width": "var(--huin-core-popper-available-width)",
728
+ "--huin-core-select-content-available-height": "var(--huin-core-popper-available-height)",
729
+ "--huin-core-select-trigger-width": "var(--huin-core-popper-anchor-width)",
730
+ "--huin-core-select-trigger-height": "var(--huin-core-popper-anchor-height)"
731
+ }
732
+ }
733
+ }
734
+ );
735
+ });
736
+ SelectPopperPosition.displayName = POPPER_POSITION_NAME;
737
+ function useTypeaheadSearch(onSearchChange) {
738
+ const handleSearchChange = useCallbackRef(onSearchChange);
739
+ const searchRef = React3.useRef("");
740
+ const timerRef = React3.useRef(0);
741
+ const handleTypeaheadSearch = React3.useCallback(
742
+ (key) => {
743
+ const search = searchRef.current + key;
744
+ handleSearchChange(search);
745
+ (function updateSearch(value) {
746
+ searchRef.current = value;
747
+ window.clearTimeout(timerRef.current);
748
+ if (value !== "")
749
+ timerRef.current = window.setTimeout(() => updateSearch(""), 1e3);
750
+ })(search);
751
+ },
752
+ [handleSearchChange]
753
+ );
754
+ const resetTypeahead = React3.useCallback(() => {
755
+ searchRef.current = "";
756
+ window.clearTimeout(timerRef.current);
757
+ }, []);
758
+ React3.useEffect(() => {
759
+ return () => window.clearTimeout(timerRef.current);
760
+ }, []);
761
+ return [searchRef, handleTypeaheadSearch, resetTypeahead];
762
+ }
763
+ function findNextItem(items, search, currentItem) {
764
+ const isRepeated = search.length > 1 && Array.from(search).every((char) => char === search[0]);
765
+ const normalizedSearch = isRepeated ? search[0] : search;
766
+ const currentItemIndex = currentItem ? items.indexOf(currentItem) : -1;
767
+ let wrappedItems = wrapArray(items, Math.max(currentItemIndex, 0));
768
+ const excludeCurrentItem = normalizedSearch.length === 1;
769
+ if (excludeCurrentItem)
770
+ wrappedItems = wrappedItems.filter((v) => v !== currentItem);
771
+ const nextItem = wrappedItems.find(
772
+ (item) => item.textValue.toLowerCase().startsWith(normalizedSearch.toLowerCase())
773
+ );
774
+ return nextItem !== currentItem ? nextItem : void 0;
775
+ }
776
+ function wrapArray(array, startIndex) {
777
+ return array.map((_, index) => array[(startIndex + index) % array.length]);
778
+ }
779
+
780
+ // packages/react/select/src/SelectTrigger.tsx
781
+ import * as PopperPrimitive3 from "@huin-core/react-popper";
782
+
783
+ // packages/react/select/src/SelectValue.tsx
784
+ import React4 from "react";
785
+ import { Primitive as Primitive3 } from "@huin-core/react-primitive";
786
+ import { useComposedRefs as useComposedRefs4 } from "@huin-core/react-compose-refs";
787
+ import { useLayoutEffect as useLayoutEffect2 } from "@huin-core/react-use-layout-effect";
788
+ import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
789
+ var VALUE_NAME = "SelectValue";
790
+ var SelectValue = React4.forwardRef(
791
+ (props, forwardedRef) => {
792
+ const { __scopeSelect, className, style, children, placeholder = "", ...valueProps } = props;
793
+ const context = useSelectContext(VALUE_NAME, __scopeSelect);
794
+ const { onValueNodeHasChildrenChange } = context;
795
+ const hasChildren = children !== void 0;
796
+ const composedRefs = useComposedRefs4(
797
+ forwardedRef,
798
+ context.onValueNodeChange
799
+ );
800
+ useLayoutEffect2(() => {
801
+ onValueNodeHasChildrenChange(hasChildren);
802
+ }, [onValueNodeHasChildrenChange, hasChildren]);
803
+ return /* @__PURE__ */ jsx4(
804
+ Primitive3.span,
805
+ {
806
+ ...valueProps,
807
+ ref: composedRefs,
808
+ style: { pointerEvents: "none" },
809
+ children: shouldShowPlaceholder(context.value) ? /* @__PURE__ */ jsx4(Fragment2, { children: placeholder }) : children
810
+ }
811
+ );
812
+ }
813
+ );
814
+ SelectValue.displayName = VALUE_NAME;
815
+ function shouldShowPlaceholder(value) {
816
+ return value === "" || value === void 0;
817
+ }
818
+
819
+ // packages/react/select/src/SelectTrigger.tsx
820
+ import { composeEventHandlers as composeEventHandlers3 } from "@huin-core/primitive";
821
+ import { jsx as jsx5 } from "react/jsx-runtime";
822
+ var OPEN_KEYS = [" ", "Enter", "ArrowUp", "ArrowDown"];
823
+ var TRIGGER_NAME = "SelectTrigger";
824
+ var SelectTrigger = React5.forwardRef((props, forwardedRef) => {
825
+ const { __scopeSelect, disabled = false, ...triggerProps } = props;
826
+ const popperScope = usePopperScope(__scopeSelect);
827
+ const context = useSelectContext(TRIGGER_NAME, __scopeSelect);
828
+ const isDisabled = context.disabled || disabled;
829
+ const composedRefs = useComposedRefs5(forwardedRef, context.onTriggerChange);
830
+ const getItems = useCollection(__scopeSelect);
831
+ const pointerTypeRef = React5.useRef("touch");
832
+ const [searchRef, handleTypeaheadSearch, resetTypeahead] = useTypeaheadSearch(
833
+ (search) => {
834
+ const enabledItems = getItems().filter((item) => !item.disabled);
835
+ const currentItem = enabledItems.find(
836
+ (item) => item.value === context.value
837
+ );
838
+ const nextItem = findNextItem(enabledItems, search, currentItem);
839
+ if (nextItem !== void 0) {
840
+ context.onValueChange(nextItem.value);
841
+ }
842
+ }
843
+ );
844
+ const handleOpen = (pointerEvent) => {
845
+ if (!isDisabled) {
846
+ context.onOpenChange(true);
847
+ resetTypeahead();
848
+ }
849
+ if (pointerEvent) {
850
+ context.triggerPointerDownPosRef.current = {
851
+ x: Math.round(pointerEvent.pageX),
852
+ y: Math.round(pointerEvent.pageY)
853
+ };
854
+ }
855
+ };
856
+ return /* @__PURE__ */ jsx5(PopperPrimitive3.Anchor, { asChild: true, ...popperScope, children: /* @__PURE__ */ jsx5(
857
+ Primitive4.button,
858
+ {
859
+ type: "button",
860
+ role: "combobox",
861
+ "aria-controls": context.contentId,
862
+ "aria-expanded": context.open,
863
+ "aria-required": context.required,
864
+ "aria-autocomplete": "none",
865
+ dir: context.dir,
866
+ "data-state": context.open ? "open" : "closed",
867
+ disabled: isDisabled,
868
+ "data-disabled": isDisabled ? "" : void 0,
869
+ "data-placeholder": shouldShowPlaceholder(context.value) ? "" : void 0,
870
+ ...triggerProps,
871
+ ref: composedRefs,
872
+ onClick: composeEventHandlers3(triggerProps.onClick, (event) => {
873
+ event.currentTarget.focus();
874
+ if (pointerTypeRef.current !== "mouse") {
875
+ handleOpen(event);
876
+ }
877
+ }),
878
+ onPointerDown: composeEventHandlers3(triggerProps.onPointerDown, (event) => {
879
+ pointerTypeRef.current = event.pointerType;
880
+ const target = event.target;
881
+ if (target.hasPointerCapture(event.pointerId)) {
882
+ target.releasePointerCapture(event.pointerId);
883
+ }
884
+ if (event.button === 0 && event.ctrlKey === false && event.pointerType === "mouse") {
885
+ handleOpen(event);
886
+ event.preventDefault();
887
+ }
888
+ }),
889
+ onKeyDown: composeEventHandlers3(triggerProps.onKeyDown, (event) => {
890
+ const isTypingAhead = searchRef.current !== "";
891
+ const isModifierKey = event.ctrlKey || event.altKey || event.metaKey;
892
+ if (!isModifierKey && event.key.length === 1) handleTypeaheadSearch(event.key);
893
+ if (isTypingAhead && event.key === " ") return;
894
+ if (OPEN_KEYS.includes(event.key)) {
895
+ handleOpen();
896
+ event.preventDefault();
897
+ }
898
+ })
899
+ }
900
+ ) });
901
+ });
902
+ SelectTrigger.displayName = TRIGGER_NAME;
903
+
904
+ // packages/react/select/src/SelectIcon.tsx
905
+ import React6 from "react";
906
+ import { Primitive as Primitive5 } from "@huin-core/react-primitive";
907
+ import { jsx as jsx6 } from "react/jsx-runtime";
908
+ var ICON_NAME = "SelectIcon";
909
+ var SelectIcon = React6.forwardRef(
910
+ (props, forwardedRef) => {
911
+ const { __scopeSelect, children, ...iconProps } = props;
912
+ return /* @__PURE__ */ jsx6(Primitive5.span, { "aria-hidden": true, ...iconProps, ref: forwardedRef, children: children || "\u25BC" });
913
+ }
914
+ );
915
+ SelectIcon.displayName = ICON_NAME;
916
+
917
+ // packages/react/select/src/SelectPortal.tsx
918
+ import { Portal as PortalPrimitive } from "@huin-core/react-portal";
919
+ import { jsx as jsx7 } from "react/jsx-runtime";
920
+ var PORTAL_NAME = "SelectPortal";
921
+ var SelectPortal = (props) => {
922
+ return /* @__PURE__ */ jsx7(PortalPrimitive, { asChild: true, ...props });
923
+ };
924
+ SelectPortal.displayName = PORTAL_NAME;
925
+
926
+ // packages/react/select/src/SelectGroup.tsx
927
+ import React7 from "react";
928
+ import { Primitive as Primitive6 } from "@huin-core/react-primitive";
929
+ import { useId as useId2 } from "@huin-core/react-id";
930
+ import { jsx as jsx8 } from "react/jsx-runtime";
931
+ var GROUP_NAME = "SelectGroup";
932
+ var [SelectGroupContextProvider, useSelectGroupContext] = createSelectContext(GROUP_NAME);
933
+ var SelectGroup = React7.forwardRef(
934
+ (props, forwardedRef) => {
935
+ const { __scopeSelect, ...groupProps } = props;
936
+ const groupId = useId2();
937
+ return /* @__PURE__ */ jsx8(SelectGroupContextProvider, { scope: __scopeSelect, id: groupId, children: /* @__PURE__ */ jsx8(
938
+ Primitive6.div,
939
+ {
940
+ role: "group",
941
+ "aria-labelledby": groupId,
942
+ ...groupProps,
943
+ ref: forwardedRef
944
+ }
945
+ ) });
946
+ }
947
+ );
948
+ SelectGroup.displayName = GROUP_NAME;
949
+
950
+ // packages/react/select/src/SelectLabel.tsx
951
+ import React8 from "react";
952
+ import { Primitive as Primitive7 } from "@huin-core/react-primitive";
953
+ import { jsx as jsx9 } from "react/jsx-runtime";
954
+ var LABEL_NAME = "SelectLabel";
955
+ var SelectLabel = React8.forwardRef(
956
+ (props, forwardedRef) => {
957
+ const { __scopeSelect, ...labelProps } = props;
958
+ const groupContext = useSelectGroupContext(LABEL_NAME, __scopeSelect);
959
+ return /* @__PURE__ */ jsx9(Primitive7.div, { id: groupContext.id, ...labelProps, ref: forwardedRef });
960
+ }
961
+ );
962
+ SelectLabel.displayName = LABEL_NAME;
963
+
964
+ // packages/react/select/src/SelectItem.tsx
965
+ import React9 from "react";
966
+ import { Primitive as Primitive8 } from "@huin-core/react-primitive";
967
+ import { useComposedRefs as useComposedRefs6 } from "@huin-core/react-compose-refs";
968
+ import { useId as useId3 } from "@huin-core/react-id";
969
+ import { composeEventHandlers as composeEventHandlers4 } from "@huin-core/primitive";
970
+ import { jsx as jsx10 } from "react/jsx-runtime";
971
+ var SELECTION_KEYS = [" ", "Enter"];
972
+ var ITEM_NAME = "SelectItem";
973
+ var [SelectItemContextProvider, useSelectItemContext] = createSelectContext(ITEM_NAME);
974
+ var SelectItem = React9.forwardRef(
975
+ (props, forwardedRef) => {
976
+ const {
977
+ __scopeSelect,
978
+ value,
979
+ disabled = false,
980
+ textValue: textValueProp,
981
+ ...itemProps
982
+ } = props;
983
+ const context = useSelectContext(ITEM_NAME, __scopeSelect);
984
+ const contentContext = useSelectContentContext(ITEM_NAME, __scopeSelect);
985
+ const isSelected = context.value === value;
986
+ const [textValue, setTextValue] = React9.useState(textValueProp ?? "");
987
+ const [isFocused, setIsFocused] = React9.useState(false);
988
+ const composedRefs = useComposedRefs6(
989
+ forwardedRef,
990
+ (node) => contentContext.itemRefCallback?.(node, value, disabled)
991
+ );
992
+ const textId = useId3();
993
+ const pointerTypeRef = React9.useRef("touch");
994
+ const handleSelect = () => {
995
+ if (!disabled) {
996
+ context.onValueChange(value);
997
+ context.onOpenChange(false);
998
+ }
999
+ };
1000
+ if (value === "") {
1001
+ throw new Error(
1002
+ "A <Select.Item /> must have a value prop that is not an empty string. This is because the Select value can be set to an empty string to clear the selection and show the placeholder."
1003
+ );
1004
+ }
1005
+ return /* @__PURE__ */ jsx10(
1006
+ SelectItemContextProvider,
1007
+ {
1008
+ scope: __scopeSelect,
1009
+ value,
1010
+ disabled,
1011
+ textId,
1012
+ isSelected,
1013
+ onItemTextChange: React9.useCallback((node) => {
1014
+ setTextValue((prevTextValue) => prevTextValue || (node?.textContent ?? "").trim());
1015
+ }, []),
1016
+ children: /* @__PURE__ */ jsx10(
1017
+ Collection.ItemSlot,
1018
+ {
1019
+ scope: __scopeSelect,
1020
+ value,
1021
+ disabled,
1022
+ textValue,
1023
+ children: /* @__PURE__ */ jsx10(
1024
+ Primitive8.div,
1025
+ {
1026
+ role: "option",
1027
+ "aria-labelledby": textId,
1028
+ "data-highlighted": isFocused ? "" : void 0,
1029
+ "aria-selected": isSelected && isFocused,
1030
+ "data-state": isSelected ? "checked" : "unchecked",
1031
+ "aria-disabled": disabled || void 0,
1032
+ "data-disabled": disabled ? "" : void 0,
1033
+ tabIndex: disabled ? void 0 : -1,
1034
+ ...itemProps,
1035
+ ref: composedRefs,
1036
+ onFocus: composeEventHandlers4(itemProps.onFocus, () => setIsFocused(true)),
1037
+ onBlur: composeEventHandlers4(itemProps.onBlur, () => setIsFocused(false)),
1038
+ onClick: composeEventHandlers4(itemProps.onClick, () => {
1039
+ if (pointerTypeRef.current !== "mouse") handleSelect();
1040
+ }),
1041
+ onPointerUp: composeEventHandlers4(itemProps.onPointerUp, () => {
1042
+ if (pointerTypeRef.current === "mouse") handleSelect();
1043
+ }),
1044
+ onPointerDown: composeEventHandlers4(itemProps.onPointerDown, (event) => {
1045
+ pointerTypeRef.current = event.pointerType;
1046
+ }),
1047
+ onPointerMove: composeEventHandlers4(itemProps.onPointerMove, (event) => {
1048
+ pointerTypeRef.current = event.pointerType;
1049
+ if (disabled) {
1050
+ contentContext.onItemLeave?.();
1051
+ } else if (pointerTypeRef.current === "mouse") {
1052
+ event.currentTarget.focus({ preventScroll: true });
1053
+ }
1054
+ }),
1055
+ onPointerLeave: composeEventHandlers4(itemProps.onPointerLeave, (event) => {
1056
+ if (event.currentTarget === document.activeElement) {
1057
+ contentContext.onItemLeave?.();
1058
+ }
1059
+ }),
1060
+ onKeyDown: composeEventHandlers4(itemProps.onKeyDown, (event) => {
1061
+ const isTypingAhead = contentContext.searchRef?.current !== "";
1062
+ if (isTypingAhead && event.key === " ") return;
1063
+ if (SELECTION_KEYS.includes(event.key)) handleSelect();
1064
+ if (event.key === " ") event.preventDefault();
1065
+ })
1066
+ }
1067
+ )
1068
+ }
1069
+ )
1070
+ }
1071
+ );
1072
+ }
1073
+ );
1074
+ SelectItem.displayName = ITEM_NAME;
1075
+
1076
+ // packages/react/select/src/SelectItemText.tsx
1077
+ import React10 from "react";
1078
+ import { Primitive as Primitive9 } from "@huin-core/react-primitive";
1079
+ import { useComposedRefs as useComposedRefs7 } from "@huin-core/react-compose-refs";
1080
+ import { useLayoutEffect as useLayoutEffect3 } from "@huin-core/react-use-layout-effect";
1081
+ import ReactDOM2 from "react-dom";
1082
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
1083
+ var ITEM_TEXT_NAME = "SelectItemText";
1084
+ var SelectItemText = React10.forwardRef((props, forwardedRef) => {
1085
+ const { __scopeSelect, className, style, ...itemTextProps } = props;
1086
+ const context = useSelectContext(ITEM_TEXT_NAME, __scopeSelect);
1087
+ const contentContext = useSelectContentContext(ITEM_TEXT_NAME, __scopeSelect);
1088
+ const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect);
1089
+ const nativeOptionsContext = useSelectNativeOptionsContext(
1090
+ ITEM_TEXT_NAME,
1091
+ __scopeSelect
1092
+ );
1093
+ const [itemTextNode, setItemTextNode] = React10.useState(null);
1094
+ const composedRefs = useComposedRefs7(
1095
+ forwardedRef,
1096
+ (node) => setItemTextNode(node),
1097
+ itemContext.onItemTextChange,
1098
+ (node) => contentContext.itemTextRefCallback?.(
1099
+ node,
1100
+ itemContext.value,
1101
+ itemContext.disabled
1102
+ )
1103
+ );
1104
+ const textContent = itemTextNode?.textContent;
1105
+ const nativeOption = React10.useMemo(
1106
+ () => /* @__PURE__ */ jsx11(
1107
+ "option",
1108
+ {
1109
+ value: itemContext.value,
1110
+ disabled: itemContext.disabled,
1111
+ children: textContent
1112
+ },
1113
+ itemContext.value
1114
+ ),
1115
+ [itemContext.disabled, itemContext.value, textContent]
1116
+ );
1117
+ const { onNativeOptionAdd, onNativeOptionRemove } = nativeOptionsContext;
1118
+ useLayoutEffect3(() => {
1119
+ onNativeOptionAdd(nativeOption);
1120
+ return () => onNativeOptionRemove(nativeOption);
1121
+ }, [onNativeOptionAdd, onNativeOptionRemove, nativeOption]);
1122
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1123
+ /* @__PURE__ */ jsx11(Primitive9.span, { id: itemContext.textId, ...itemTextProps, ref: composedRefs }),
1124
+ itemContext.isSelected && context.valueNode && !context.valueNodeHasChildren ? ReactDOM2.createPortal(itemTextProps.children, context.valueNode) : null
1125
+ ] });
1126
+ });
1127
+ SelectItemText.displayName = ITEM_TEXT_NAME;
1128
+
1129
+ // packages/react/select/src/SelectItemIndicator.tsx
1130
+ import React11 from "react";
1131
+ import { Primitive as Primitive10 } from "@huin-core/react-primitive";
1132
+ import { jsx as jsx12 } from "react/jsx-runtime";
1133
+ var ITEM_INDICATOR_NAME = "SelectItemIndicator";
1134
+ var SelectItemIndicator = React11.forwardRef((props, forwardedRef) => {
1135
+ const { __scopeSelect, ...itemIndicatorProps } = props;
1136
+ const itemContext = useSelectItemContext(ITEM_INDICATOR_NAME, __scopeSelect);
1137
+ return itemContext.isSelected ? /* @__PURE__ */ jsx12(Primitive10.span, { "aria-hidden": true, ...itemIndicatorProps, ref: forwardedRef }) : null;
1138
+ });
1139
+ SelectItemIndicator.displayName = ITEM_INDICATOR_NAME;
1140
+
1141
+ // packages/react/select/src/SelectScrollUpButton.tsx
1142
+ import React13 from "react";
1143
+
1144
+ // packages/react/select/src/SelectScrollDownButton.tsx
1145
+ import React12 from "react";
1146
+ import { useComposedRefs as useComposedRefs8 } from "@huin-core/react-compose-refs";
1147
+ import { useLayoutEffect as useLayoutEffect4 } from "@huin-core/react-use-layout-effect";
1148
+ import { Primitive as Primitive11 } from "@huin-core/react-primitive";
1149
+ import { composeEventHandlers as composeEventHandlers5 } from "@huin-core/primitive";
1150
+ import { jsx as jsx13 } from "react/jsx-runtime";
1151
+ var SCROLL_DOWN_BUTTON_NAME = "SelectScrollDownButton";
1152
+ var SelectScrollDownButton = React12.forwardRef((props, forwardedRef) => {
1153
+ const contentContext = useSelectContentContext(
1154
+ SCROLL_DOWN_BUTTON_NAME,
1155
+ props.__scopeSelect
1156
+ );
1157
+ const viewportContext = useSelectViewportContext(
1158
+ SCROLL_DOWN_BUTTON_NAME,
1159
+ props.__scopeSelect
1160
+ );
1161
+ const [canScrollDown, setCanScrollDown] = React12.useState(false);
1162
+ const composedRefs = useComposedRefs8(
1163
+ forwardedRef,
1164
+ viewportContext.onScrollButtonChange
1165
+ );
1166
+ useLayoutEffect4(() => {
1167
+ if (contentContext.viewport && contentContext.isPositioned) {
1168
+ let handleScroll2 = function() {
1169
+ const maxScroll = viewport.scrollHeight - viewport.clientHeight;
1170
+ const canScrollDown2 = Math.ceil(viewport.scrollTop) < maxScroll;
1171
+ setCanScrollDown(canScrollDown2);
1172
+ };
1173
+ var handleScroll = handleScroll2;
1174
+ const viewport = contentContext.viewport;
1175
+ handleScroll2();
1176
+ viewport.addEventListener("scroll", handleScroll2);
1177
+ return () => viewport.removeEventListener("scroll", handleScroll2);
1178
+ }
1179
+ }, [contentContext.viewport, contentContext.isPositioned]);
1180
+ return canScrollDown ? /* @__PURE__ */ jsx13(
1181
+ SelectScrollButtonImpl,
1182
+ {
1183
+ ...props,
1184
+ ref: composedRefs,
1185
+ onAutoScroll: () => {
1186
+ const { viewport, selectedItem } = contentContext;
1187
+ if (viewport && selectedItem) {
1188
+ viewport.scrollTop = viewport.scrollTop + selectedItem.offsetHeight;
1189
+ }
1190
+ }
1191
+ }
1192
+ ) : null;
1193
+ });
1194
+ SelectScrollDownButton.displayName = SCROLL_DOWN_BUTTON_NAME;
1195
+ var SelectScrollButtonImpl = React12.forwardRef((props, forwardedRef) => {
1196
+ const { __scopeSelect, onAutoScroll, ...scrollIndicatorProps } = props;
1197
+ const contentContext = useSelectContentContext(
1198
+ "SelectScrollButton",
1199
+ __scopeSelect
1200
+ );
1201
+ const autoScrollTimerRef = React12.useRef(null);
1202
+ const getItems = useCollection(__scopeSelect);
1203
+ const clearAutoScrollTimer = React12.useCallback(() => {
1204
+ if (autoScrollTimerRef.current !== null) {
1205
+ window.clearInterval(autoScrollTimerRef.current);
1206
+ autoScrollTimerRef.current = null;
1207
+ }
1208
+ }, []);
1209
+ React12.useEffect(() => {
1210
+ return () => clearAutoScrollTimer();
1211
+ }, [clearAutoScrollTimer]);
1212
+ useLayoutEffect4(() => {
1213
+ const activeItem = getItems().find(
1214
+ (item) => item.ref.current === document.activeElement
1215
+ );
1216
+ activeItem?.ref.current?.scrollIntoView({ block: "nearest" });
1217
+ }, [getItems]);
1218
+ return /* @__PURE__ */ jsx13(
1219
+ Primitive11.div,
1220
+ {
1221
+ "aria-hidden": true,
1222
+ ...scrollIndicatorProps,
1223
+ ref: forwardedRef,
1224
+ style: { flexShrink: 0, ...scrollIndicatorProps.style },
1225
+ onPointerDown: composeEventHandlers5(
1226
+ scrollIndicatorProps.onPointerDown,
1227
+ () => {
1228
+ if (autoScrollTimerRef.current === null) {
1229
+ autoScrollTimerRef.current = window.setInterval(onAutoScroll, 50);
1230
+ }
1231
+ }
1232
+ ),
1233
+ onPointerMove: composeEventHandlers5(
1234
+ scrollIndicatorProps.onPointerMove,
1235
+ () => {
1236
+ contentContext.onItemLeave?.();
1237
+ if (autoScrollTimerRef.current === null) {
1238
+ autoScrollTimerRef.current = window.setInterval(onAutoScroll, 50);
1239
+ }
1240
+ }
1241
+ ),
1242
+ onPointerLeave: composeEventHandlers5(
1243
+ scrollIndicatorProps.onPointerLeave,
1244
+ () => {
1245
+ clearAutoScrollTimer();
1246
+ }
1247
+ )
1248
+ }
1249
+ );
1250
+ });
1251
+
1252
+ // packages/react/select/src/SelectScrollUpButton.tsx
1253
+ import { useComposedRefs as useComposedRefs9 } from "@huin-core/react-compose-refs";
1254
+ import { useLayoutEffect as useLayoutEffect5 } from "@huin-core/react-use-layout-effect";
1255
+ import { jsx as jsx14 } from "react/jsx-runtime";
1256
+ var SCROLL_UP_BUTTON_NAME = "SelectScrollUpButton";
1257
+ var SelectScrollUpButton = React13.forwardRef((props, forwardedRef) => {
1258
+ const contentContext = useSelectContentContext(
1259
+ SCROLL_UP_BUTTON_NAME,
1260
+ props.__scopeSelect
1261
+ );
1262
+ const viewportContext = useSelectViewportContext(
1263
+ SCROLL_UP_BUTTON_NAME,
1264
+ props.__scopeSelect
1265
+ );
1266
+ const [canScrollUp, setCanScrollUp] = React13.useState(false);
1267
+ const composedRefs = useComposedRefs9(
1268
+ forwardedRef,
1269
+ viewportContext.onScrollButtonChange
1270
+ );
1271
+ useLayoutEffect5(() => {
1272
+ if (contentContext.viewport && contentContext.isPositioned) {
1273
+ let handleScroll2 = function() {
1274
+ const canScrollUp2 = viewport.scrollTop > 0;
1275
+ setCanScrollUp(canScrollUp2);
1276
+ };
1277
+ var handleScroll = handleScroll2;
1278
+ const viewport = contentContext.viewport;
1279
+ handleScroll2();
1280
+ viewport.addEventListener("scroll", handleScroll2);
1281
+ return () => viewport.removeEventListener("scroll", handleScroll2);
1282
+ }
1283
+ }, [contentContext.viewport, contentContext.isPositioned]);
1284
+ return canScrollUp ? /* @__PURE__ */ jsx14(
1285
+ SelectScrollButtonImpl,
1286
+ {
1287
+ ...props,
1288
+ ref: composedRefs,
1289
+ onAutoScroll: () => {
1290
+ const { viewport, selectedItem } = contentContext;
1291
+ if (viewport && selectedItem) {
1292
+ viewport.scrollTop = viewport.scrollTop - selectedItem.offsetHeight;
1293
+ }
1294
+ }
1295
+ }
1296
+ ) : null;
1297
+ });
1298
+ SelectScrollUpButton.displayName = SCROLL_UP_BUTTON_NAME;
1299
+
1300
+ // packages/react/select/src/SelectSeparator.tsx
1301
+ import React14 from "react";
1302
+ import { Primitive as Primitive12 } from "@huin-core/react-primitive";
1303
+ import { jsx as jsx15 } from "react/jsx-runtime";
1304
+ var SEPARATOR_NAME = "SelectSeparator";
1305
+ var SelectSeparator = React14.forwardRef((props, forwardedRef) => {
1306
+ const { __scopeSelect, ...separatorProps } = props;
1307
+ return /* @__PURE__ */ jsx15(Primitive12.div, { "aria-hidden": true, ...separatorProps, ref: forwardedRef });
1308
+ });
1309
+ SelectSeparator.displayName = SEPARATOR_NAME;
1310
+
1311
+ // packages/react/select/src/SelectArrow.tsx
1312
+ import * as PopperPrimitive4 from "@huin-core/react-popper";
1313
+ import React15 from "react";
1314
+ import { jsx as jsx16 } from "react/jsx-runtime";
1315
+ var ARROW_NAME = "SelectArrow";
1316
+ var SelectArrow = React15.forwardRef(
1317
+ (props, forwardedRef) => {
1318
+ const { __scopeSelect, ...arrowProps } = props;
1319
+ const popperScope = usePopperScope(__scopeSelect);
1320
+ const context = useSelectContext(ARROW_NAME, __scopeSelect);
1321
+ const contentContext = useSelectContentContext(ARROW_NAME, __scopeSelect);
1322
+ return context.open && contentContext.position === "popper" ? /* @__PURE__ */ jsx16(
1323
+ PopperPrimitive4.Arrow,
1324
+ {
1325
+ ...popperScope,
1326
+ ...arrowProps,
1327
+ ref: forwardedRef
1328
+ }
1329
+ ) : null;
1330
+ }
1331
+ );
1332
+ SelectArrow.displayName = ARROW_NAME;
1333
+ export {
1334
+ Root2 as Root,
1335
+ Select,
1336
+ SelectArrow,
1337
+ SelectContent,
1338
+ SelectGroup,
1339
+ SelectIcon,
1340
+ SelectItem,
1341
+ SelectItemIndicator,
1342
+ SelectItemText,
1343
+ SelectLabel,
1344
+ SelectPortal,
1345
+ SelectScrollDownButton,
1346
+ SelectScrollUpButton,
1347
+ SelectSeparator,
1348
+ SelectTrigger,
1349
+ SelectValue,
1350
+ SelectViewport,
1351
+ createSelectScope
1352
+ };
1353
+ //# sourceMappingURL=index.mjs.map