@octanejs/select 0.1.1

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.
@@ -0,0 +1,875 @@
1
+ import {
2
+ createContext,
3
+ createPortal,
4
+ useCallback,
5
+ useLayoutEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'octane';
10
+ import type { OctaneNode } from 'octane';
11
+ import type { Octane } from 'octane/jsx-runtime';
12
+ import { autoUpdate } from '@floating-ui/dom';
13
+
14
+ import type { CSSObjectWithLabel, GroupBase, Theme } from './types';
15
+ import { createKeyframes } from './style-adapter';
16
+ import { useStyleResolver } from './style-context.tsrx';
17
+
18
+ export type Ref<T> = ((value: T | null) => void) | { current: T | null } | null;
19
+ export type ClassNamesState = Record<string, boolean>;
20
+ export type CX = (state: ClassNamesState, ...classNames: (string | undefined)[]) => string;
21
+ type DivProps =
22
+ Omit<Octane.JSX.IntrinsicElements['div'], 'ref'> & {
23
+ ref?: Octane.Ref<HTMLDivElement>;
24
+ };
25
+ type InputPropsBase = Omit<Octane.JSX.IntrinsicElements['input'], 'ref'>;
26
+ type SpanProps = Octane.JSX.IntrinsicElements['span'];
27
+ type SvgProps = Octane.JSX.IntrinsicElements['svg'];
28
+
29
+ export interface CommonComponentProps<
30
+ Option = unknown,
31
+ IsMulti extends boolean = boolean,
32
+ Group extends GroupBase<Option> = GroupBase<Option>,
33
+ > {
34
+ [key: string]: unknown;
35
+ className?: string;
36
+ cx: CX;
37
+ getClassNames: (name: string, props: unknown) => string | undefined;
38
+ getStyles: (name: string, props: unknown) => CSSObjectWithLabel;
39
+ hasValue: boolean;
40
+ isMulti: boolean;
41
+ isRtl: boolean;
42
+ selectProps: { controlShouldRenderValue: boolean } & Record<string, unknown>;
43
+ theme: Theme;
44
+ }
45
+
46
+ function useStyleProps(
47
+ props: CommonComponentProps,
48
+ name: string,
49
+ state: ClassNamesState = {},
50
+ ): { className: string; ref?: undefined } {
51
+ const resolveStyle = useStyleResolver();
52
+ const prefixed = props.cx(state, props.getClassNames(name, props), props.className);
53
+ const css = props.getStyles(name, props);
54
+ const implicitLabels: Record<string, string> = {
55
+ indicatorsContainer: 'IndicatorsContainer',
56
+ valueContainer: 'ValueContainer',
57
+ };
58
+ const labeledCSS =
59
+ css.label === undefined && implicitLabels[name] !== undefined
60
+ ? { ...css, label: implicitLabels[name] }
61
+ : css;
62
+ return { className: resolveStyle(labeledCSS, prefixed).className };
63
+ }
64
+
65
+ const svgCSS: CSSObjectWithLabel = {
66
+ label: 'Svg',
67
+ display: 'inline-block',
68
+ fill: 'currentColor',
69
+ lineHeight: 1,
70
+ stroke: 'currentColor',
71
+ strokeWidth: 0,
72
+ };
73
+
74
+ export type CrossIconProps = SvgProps & { size?: number };
75
+ export function CrossIcon(props: CrossIconProps) @{
76
+ const resolveStyle = useStyleResolver();
77
+ const { size = 20, className, children: _children, ...rest } = props;
78
+ const resolved = resolveStyle(svgCSS, className);
79
+ <svg
80
+ {...rest}
81
+ height={size}
82
+ width={size}
83
+ viewBox="0 0 20 20"
84
+ aria-hidden="true"
85
+ focusable="false"
86
+ className={resolved.className}
87
+ >
88
+ <path
89
+ d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"
90
+ />
91
+ </svg>
92
+ }
93
+
94
+ export type DownChevronProps = SvgProps & { size?: number };
95
+ export function DownChevron(props: DownChevronProps) @{
96
+ const resolveStyle = useStyleResolver();
97
+ const { size = 20, className, children: _children, ...rest } = props;
98
+ const resolved = resolveStyle(svgCSS, className);
99
+ <svg
100
+ {...rest}
101
+ height={size}
102
+ width={size}
103
+ viewBox="0 0 20 20"
104
+ aria-hidden="true"
105
+ focusable="false"
106
+ className={resolved.className}
107
+ >
108
+ <path
109
+ d="M4.516 7.548c0.436-0.446 1.043-0.481 1.576 0l3.908 3.747 3.908-3.747c0.533-0.481 1.141-0.446 1.574 0 0.436 0.445 0.408 1.197 0 1.615-0.406 0.418-4.695 4.502-4.695 4.502-0.217 0.223-0.502 0.335-0.787 0.335s-0.57-0.112-0.789-0.335c0 0-4.287-4.084-4.695-4.502s-0.436-1.17 0-1.615z"
110
+ />
111
+ </svg>
112
+ }
113
+
114
+ export interface ContainerProps<
115
+ Option = unknown,
116
+ IsMulti extends boolean = boolean,
117
+ Group extends GroupBase<Option> = GroupBase<Option>,
118
+ > extends CommonComponentProps<Option, IsMulti, Group> {
119
+ children?: OctaneNode;
120
+ innerProps: DivProps;
121
+ isDisabled: boolean;
122
+ isFocused: boolean;
123
+ }
124
+
125
+ export function SelectContainer(props: ContainerProps) @{
126
+ const { children, innerProps, isDisabled, isRtl } = props;
127
+ <div
128
+ {...useStyleProps(props, 'container', {
129
+ '--is-disabled': isDisabled,
130
+ '--is-rtl': isRtl,
131
+ })}
132
+ {...innerProps}
133
+ >{children}</div>
134
+ }
135
+
136
+ export interface ControlProps<
137
+ Option = unknown,
138
+ IsMulti extends boolean = boolean,
139
+ Group extends GroupBase<Option> = GroupBase<Option>,
140
+ > extends CommonComponentProps<Option, IsMulti, Group> {
141
+ children?: OctaneNode;
142
+ innerRef: Ref<HTMLDivElement>;
143
+ innerProps: DivProps;
144
+ isDisabled: boolean;
145
+ isFocused: boolean;
146
+ menuIsOpen: boolean;
147
+ }
148
+
149
+ export function Control(props: ControlProps) @{
150
+ const { children, isDisabled, isFocused, innerRef, innerProps, menuIsOpen } = props;
151
+ <div
152
+ ref={innerRef}
153
+ {...useStyleProps(props, 'control', {
154
+ control: true,
155
+ 'control--is-disabled': isDisabled,
156
+ 'control--is-focused': isFocused,
157
+ 'control--menu-is-open': menuIsOpen,
158
+ })}
159
+ {...innerProps}
160
+ aria-disabled={isDisabled || undefined}
161
+ >{children}</div>
162
+ }
163
+
164
+ export interface ValueContainerProps<
165
+ Option = unknown,
166
+ IsMulti extends boolean = boolean,
167
+ Group extends GroupBase<Option> = GroupBase<Option>,
168
+ > extends CommonComponentProps<Option, IsMulti, Group> {
169
+ children?: OctaneNode;
170
+ innerProps?: DivProps;
171
+ isDisabled: boolean;
172
+ }
173
+
174
+ export function ValueContainer(props: ValueContainerProps) @{
175
+ const { children, innerProps, isMulti, hasValue } = props;
176
+ <div
177
+ {...useStyleProps(props, 'valueContainer', {
178
+ 'value-container': true,
179
+ 'value-container--is-multi': isMulti,
180
+ 'value-container--has-value': hasValue,
181
+ })}
182
+ {...innerProps}
183
+ >{children}</div>
184
+ }
185
+
186
+ export interface IndicatorsContainerProps<
187
+ Option = unknown,
188
+ IsMulti extends boolean = boolean,
189
+ Group extends GroupBase<Option> = GroupBase<Option>,
190
+ > extends CommonComponentProps<Option, IsMulti, Group> {
191
+ children?: OctaneNode;
192
+ innerProps?: DivProps;
193
+ isDisabled: boolean;
194
+ }
195
+
196
+ export function IndicatorsContainer(props: IndicatorsContainerProps) @{
197
+ <div
198
+ {...useStyleProps(props, 'indicatorsContainer', { indicators: true })}
199
+ {...props.innerProps}
200
+ >{props.children}</div>
201
+ }
202
+
203
+ export interface PlaceholderProps<
204
+ Option = unknown,
205
+ IsMulti extends boolean = boolean,
206
+ Group extends GroupBase<Option> = GroupBase<Option>,
207
+ > extends CommonComponentProps<Option, IsMulti, Group> {
208
+ children?: OctaneNode;
209
+ innerProps?: DivProps;
210
+ isDisabled: boolean;
211
+ isFocused: boolean;
212
+ }
213
+
214
+ export function Placeholder(props: PlaceholderProps) @{
215
+ <div
216
+ {...useStyleProps(props, 'placeholder', { placeholder: true })}
217
+ {...props.innerProps}
218
+ >{props.children}</div>
219
+ }
220
+
221
+ export interface SingleValueProps<
222
+ Option = unknown,
223
+ IsMulti extends boolean = boolean,
224
+ Group extends GroupBase<Option> = GroupBase<Option>,
225
+ > extends CommonComponentProps<Option, IsMulti, Group> {
226
+ children?: OctaneNode;
227
+ data: unknown;
228
+ innerProps?: DivProps;
229
+ isDisabled: boolean;
230
+ }
231
+
232
+ export function SingleValue(props: SingleValueProps) @{
233
+ <div
234
+ {...useStyleProps(props, 'singleValue', {
235
+ 'single-value': true,
236
+ 'single-value--is-disabled': props.isDisabled,
237
+ })}
238
+ {...props.innerProps}
239
+ >{props.children}</div>
240
+ }
241
+
242
+ export interface OptionProps<
243
+ Option = unknown,
244
+ IsMulti extends boolean = boolean,
245
+ Group extends GroupBase<Option> = GroupBase<Option>,
246
+ > extends CommonComponentProps<Option, IsMulti, Group> {
247
+ children?: OctaneNode;
248
+ data: unknown;
249
+ innerRef: Ref<HTMLDivElement>;
250
+ innerProps: DivProps;
251
+ label: string;
252
+ type: 'option';
253
+ isDisabled: boolean;
254
+ isFocused: boolean;
255
+ isSelected: boolean;
256
+ }
257
+
258
+ export function Option(props: OptionProps) @{
259
+ <div
260
+ {...useStyleProps(props, 'option', {
261
+ option: true,
262
+ 'option--is-disabled': props.isDisabled,
263
+ 'option--is-focused': props.isFocused,
264
+ 'option--is-selected': props.isSelected,
265
+ })}
266
+ ref={props.innerRef}
267
+ aria-disabled={props.isDisabled}
268
+ {...props.innerProps}
269
+ >{props.children}</div>
270
+ }
271
+
272
+ export interface InputProps<
273
+ Option = unknown,
274
+ IsMulti extends boolean = boolean,
275
+ Group extends GroupBase<Option> = GroupBase<Option>,
276
+ > extends CommonComponentProps<Option, IsMulti, Group>, Omit<InputPropsBase, 'className'> {
277
+ innerRef?: Ref<HTMLInputElement>;
278
+ isHidden: boolean;
279
+ isDisabled?: boolean;
280
+ inputClassName?: string;
281
+ }
282
+
283
+ const inputSpacingStyle = {
284
+ gridArea: '1 / 2',
285
+ font: 'inherit',
286
+ minWidth: '2px',
287
+ border: 0,
288
+ margin: 0,
289
+ outline: 0,
290
+ padding: 0,
291
+ } as const;
292
+
293
+ function inputStyle(isHidden: boolean) {
294
+ return {
295
+ label: 'input',
296
+ color: 'inherit',
297
+ background: 0,
298
+ opacity: isHidden ? 0 : 1,
299
+ width: '100%',
300
+ ...inputSpacingStyle,
301
+ };
302
+ }
303
+
304
+ export function Input(props: InputProps) @{
305
+ const {
306
+ className: _className,
307
+ clearValue: _clearValue,
308
+ cx,
309
+ getStyles: _getStyles,
310
+ getClassNames: _getClassNames,
311
+ getValue: _getValue,
312
+ hasValue: _hasValue,
313
+ isMulti: _isMulti,
314
+ isRtl: _isRtl,
315
+ options: _options,
316
+ selectOption: _selectOption,
317
+ selectProps: _selectProps,
318
+ setValue: _setValue,
319
+ theme: _theme,
320
+ innerRef,
321
+ isDisabled,
322
+ isHidden,
323
+ inputClassName,
324
+ value,
325
+ ...innerProps
326
+ } = props;
327
+ <div {...useStyleProps(props, 'input', { 'input-container': true })} data-value={value || ''}>
328
+ <input
329
+ className={cx({ input: true }, inputClassName)}
330
+ ref={innerRef ?? null}
331
+ style={inputStyle(isHidden)}
332
+ disabled={isDisabled}
333
+ value={value}
334
+ {...innerProps}
335
+ />
336
+ </div>
337
+ }
338
+
339
+ export interface IndicatorProps<
340
+ Option = unknown,
341
+ IsMulti extends boolean = boolean,
342
+ Group extends GroupBase<Option> = GroupBase<Option>,
343
+ > extends CommonComponentProps<Option, IsMulti, Group> {
344
+ children?: OctaneNode;
345
+ innerProps: DivProps;
346
+ isFocused: boolean;
347
+ isDisabled: boolean;
348
+ }
349
+
350
+ export function DropdownIndicator(props: IndicatorProps) @{
351
+ <div
352
+ {...useStyleProps(props, 'dropdownIndicator', {
353
+ indicator: true,
354
+ 'dropdown-indicator': true,
355
+ })}
356
+ {...props.innerProps}
357
+ >
358
+ {props.children || <DownChevron />}
359
+ </div>
360
+ }
361
+
362
+ export function ClearIndicator(props: IndicatorProps) @{
363
+ <div
364
+ {...useStyleProps(props, 'clearIndicator', {
365
+ indicator: true,
366
+ 'clear-indicator': true,
367
+ })}
368
+ {...props.innerProps}
369
+ >
370
+ {props.children || <CrossIcon />}
371
+ </div>
372
+ }
373
+
374
+ export interface IndicatorSeparatorProps<
375
+ Option = unknown,
376
+ IsMulti extends boolean = boolean,
377
+ Group extends GroupBase<Option> = GroupBase<Option>,
378
+ > extends CommonComponentProps<Option, IsMulti, Group> {
379
+ isDisabled: boolean;
380
+ isFocused: boolean;
381
+ innerProps?: SpanProps;
382
+ }
383
+
384
+ export function IndicatorSeparator(props: IndicatorSeparatorProps) @{
385
+ <span
386
+ {...props.innerProps}
387
+ {...useStyleProps(props, 'indicatorSeparator', { 'indicator-separator': true })}
388
+ />
389
+ }
390
+
391
+ export interface MenuProps<
392
+ Option = unknown,
393
+ IsMulti extends boolean = boolean,
394
+ Group extends GroupBase<Option> = GroupBase<Option>,
395
+ > extends CommonComponentProps<Option, IsMulti, Group> {
396
+ children?: OctaneNode;
397
+ innerRef: Ref<HTMLDivElement>;
398
+ innerProps: DivProps;
399
+ isLoading: boolean;
400
+ placement: 'bottom' | 'top';
401
+ minMenuHeight: number;
402
+ maxMenuHeight: number;
403
+ menuPlacement: 'auto' | 'bottom' | 'top';
404
+ menuPosition: 'absolute' | 'fixed';
405
+ menuShouldScrollIntoView: boolean;
406
+ }
407
+
408
+ export function Menu(props: MenuProps) @{
409
+ <div
410
+ {...useStyleProps(props, 'menu', { menu: true })}
411
+ ref={props.innerRef}
412
+ {...props.innerProps}
413
+ >{props.children}</div>
414
+ }
415
+
416
+ export interface MenuListProps<
417
+ Option = unknown,
418
+ IsMulti extends boolean = boolean,
419
+ Group extends GroupBase<Option> = GroupBase<Option>,
420
+ > extends CommonComponentProps<Option, IsMulti, Group> {
421
+ children?: OctaneNode;
422
+ innerRef: Ref<HTMLDivElement>;
423
+ innerProps: DivProps;
424
+ maxHeight: number;
425
+ focusedOption: unknown;
426
+ }
427
+
428
+ export function MenuList(props: MenuListProps) @{
429
+ <div
430
+ {...useStyleProps(props, 'menuList', {
431
+ 'menu-list': true,
432
+ 'menu-list--is-multi': props.isMulti,
433
+ })}
434
+ ref={props.innerRef}
435
+ {...props.innerProps}
436
+ >{props.children}</div>
437
+ }
438
+
439
+ export interface NoticeProps<
440
+ Option = unknown,
441
+ IsMulti extends boolean = boolean,
442
+ Group extends GroupBase<Option> = GroupBase<Option>,
443
+ > extends CommonComponentProps<Option, IsMulti, Group> {
444
+ children?: OctaneNode;
445
+ innerProps?: DivProps;
446
+ }
447
+
448
+ export function NoOptionsMessage(props: NoticeProps) @{
449
+ const children = props.children ?? 'No options';
450
+ <div
451
+ {...useStyleProps(props, 'noOptionsMessage', {
452
+ 'menu-notice': true,
453
+ 'menu-notice--no-options': true,
454
+ })}
455
+ {...props.innerProps}
456
+ >{children}</div>
457
+ }
458
+
459
+ export function LoadingMessage(props: NoticeProps) @{
460
+ const children = props.children ?? 'Loading...';
461
+ <div
462
+ {...useStyleProps(props, 'loadingMessage', {
463
+ 'menu-notice': true,
464
+ 'menu-notice--loading': true,
465
+ })}
466
+ {...props.innerProps}
467
+ >{children}</div>
468
+ }
469
+
470
+ type ComponentType<Props> = (props: Props) => Octane.JSX.Element;
471
+
472
+ function cleanCommonProps<Props extends CommonComponentProps>(props: Props) {
473
+ const {
474
+ className: _className,
475
+ cx: _cx,
476
+ getStyles: _getStyles,
477
+ getClassNames: _getClassNames,
478
+ hasValue: _hasValue,
479
+ isMulti: _isMulti,
480
+ isRtl: _isRtl,
481
+ selectProps: _selectProps,
482
+ theme: _theme,
483
+ ...innerProps
484
+ } = props;
485
+ return innerProps;
486
+ }
487
+
488
+ export type GroupHeadingProps<
489
+ Option = unknown,
490
+ IsMulti extends boolean = boolean,
491
+ Group extends GroupBase<Option> = GroupBase<Option>,
492
+ > =
493
+ CommonComponentProps<Option, IsMulti, Group> & Omit<DivProps, 'className'> & {
494
+ children?: OctaneNode;
495
+ id: string;
496
+ data: unknown;
497
+ };
498
+
499
+ export function GroupHeading(props: GroupHeadingProps) @{
500
+ const { data: _data, ...innerProps } = cleanCommonProps(props);
501
+ <div {...useStyleProps(props, 'groupHeading', { 'group-heading': true })} {...innerProps} />
502
+ }
503
+
504
+ export interface GroupProps<
505
+ Option = unknown,
506
+ IsMulti extends boolean = boolean,
507
+ GroupType extends GroupBase<Option> = GroupBase<Option>,
508
+ > extends CommonComponentProps<Option, IsMulti, GroupType> {
509
+ children?: OctaneNode;
510
+ Heading: ComponentType<GroupHeadingProps>;
511
+ headingProps: { id: string; data: unknown };
512
+ innerProps?: DivProps;
513
+ label?: OctaneNode;
514
+ data: unknown;
515
+ options: readonly unknown[];
516
+ }
517
+
518
+ export function Group(props: GroupProps) @{
519
+ const Heading = props.Heading;
520
+ <div {...useStyleProps(props, 'group', { group: true })} {...props.innerProps}>
521
+ <Heading
522
+ {...props.headingProps}
523
+ selectProps={props.selectProps}
524
+ theme={props.theme}
525
+ getStyles={props.getStyles}
526
+ getClassNames={props.getClassNames}
527
+ cx={props.cx}
528
+ hasValue={props.hasValue}
529
+ isMulti={props.isMulti}
530
+ isRtl={props.isRtl}
531
+ >{props.label}</Heading>
532
+ <div>{props.children}</div>
533
+ </div>
534
+ }
535
+
536
+ export interface MultiValueGenericProps<
537
+ Option = unknown,
538
+ IsMulti extends boolean = boolean,
539
+ Group extends GroupBase<Option> = GroupBase<Option>,
540
+ > {
541
+ children?: OctaneNode;
542
+ data: unknown;
543
+ innerProps: DivProps;
544
+ selectProps: Record<string, unknown>;
545
+ }
546
+
547
+ export function MultiValueGeneric(props: MultiValueGenericProps) @{
548
+ <div {...props.innerProps}>{props.children}</div>
549
+ }
550
+
551
+ export const MultiValueContainer = MultiValueGeneric;
552
+ export const MultiValueLabel = MultiValueGeneric;
553
+
554
+ export interface MultiValueRemoveProps<
555
+ Option = unknown,
556
+ IsMulti extends boolean = boolean,
557
+ Group extends GroupBase<Option> = GroupBase<Option>,
558
+ > {
559
+ children?: OctaneNode;
560
+ data: unknown;
561
+ innerProps: DivProps;
562
+ selectProps: Record<string, unknown>;
563
+ }
564
+
565
+ export function MultiValueRemove(props: MultiValueRemoveProps) @{
566
+ <div role="button" {...props.innerProps}>{props.children || <CrossIcon size={14} />}</div>
567
+ }
568
+
569
+ export interface MultiValueProps<
570
+ Option = unknown,
571
+ IsMulti extends boolean = boolean,
572
+ Group extends GroupBase<Option> = GroupBase<Option>,
573
+ > extends CommonComponentProps<Option, IsMulti, Group> {
574
+ children?: OctaneNode;
575
+ components: {
576
+ Container: ComponentType<MultiValueGenericProps>;
577
+ Label: ComponentType<MultiValueGenericProps>;
578
+ Remove: ComponentType<MultiValueRemoveProps>;
579
+ };
580
+ cropWithEllipsis?: boolean;
581
+ data: unknown;
582
+ innerProps?: DivProps;
583
+ isFocused: boolean;
584
+ isDisabled: boolean;
585
+ removeProps: DivProps;
586
+ index: number;
587
+ }
588
+
589
+ export function MultiValue(props: MultiValueProps) @{
590
+ const Container = props.components.Container;
591
+ const Label = props.components.Label;
592
+ const Remove = props.components.Remove;
593
+ <Container
594
+ data={props.data}
595
+ innerProps={{
596
+ ...useStyleProps(props, 'multiValue', {
597
+ 'multi-value': true,
598
+ 'multi-value--is-disabled': props.isDisabled,
599
+ }),
600
+ ...props.innerProps,
601
+ }}
602
+ selectProps={props.selectProps}
603
+ >
604
+ <Label
605
+ data={props.data}
606
+ innerProps={{
607
+ ...useStyleProps(props, 'multiValueLabel', { 'multi-value__label': true }),
608
+ }}
609
+ selectProps={props.selectProps}
610
+ >{props.children}</Label>
611
+ <Remove
612
+ data={props.data}
613
+ innerProps={{
614
+ ...useStyleProps(props, 'multiValueRemove', { 'multi-value__remove': true }),
615
+ 'aria-label': `Remove ${props.children || 'option'}`,
616
+ ...props.removeProps,
617
+ }}
618
+ selectProps={props.selectProps}
619
+ />
620
+ </Container>
621
+ }
622
+
623
+ const loadingDotAnimations = createKeyframes({
624
+ '0%, 80%, 100%': { opacity: 0 },
625
+ '40%': { opacity: 1 },
626
+ });
627
+
628
+ function LoadingDot(props: { delay: number; offset: boolean }) @{
629
+ const resolveStyle = useStyleResolver();
630
+ const resolved = resolveStyle({
631
+ animation: `${loadingDotAnimations} 1s ease-in-out ${props.delay}ms infinite;`,
632
+ backgroundColor: 'currentColor',
633
+ borderRadius: '1em',
634
+ display: 'inline-block',
635
+ marginLeft: props.offset ? '1em' : undefined,
636
+ height: '1em',
637
+ verticalAlign: 'top',
638
+ width: '1em',
639
+ label: 'LoadingDot',
640
+ });
641
+ <span className={resolved.className} />
642
+ }
643
+
644
+ export interface LoadingIndicatorProps<
645
+ Option = unknown,
646
+ IsMulti extends boolean = boolean,
647
+ Group extends GroupBase<Option> = GroupBase<Option>,
648
+ > extends CommonComponentProps<Option, IsMulti, Group> {
649
+ innerProps: DivProps;
650
+ isFocused: boolean;
651
+ isDisabled: boolean;
652
+ size?: number;
653
+ }
654
+
655
+ export function LoadingIndicator(props: LoadingIndicatorProps) @{
656
+ const size = props.size ?? 4;
657
+ const styleInput = { ...props, size };
658
+ <div
659
+ {...useStyleProps(styleInput, 'loadingIndicator', {
660
+ indicator: true,
661
+ 'loading-indicator': true,
662
+ })}
663
+ {...props.innerProps}
664
+ >
665
+ <LoadingDot delay={0} offset={props.isRtl} />
666
+ <LoadingDot delay={160} offset={true} />
667
+ <LoadingDot delay={320} offset={!props.isRtl} />
668
+ </div>
669
+ }
670
+
671
+ type MenuPlacement = 'auto' | 'bottom' | 'top';
672
+ type MenuPosition = 'absolute' | 'fixed';
673
+ const PortalPlacementContext = createContext<
674
+ | {
675
+ setPortalPlacement: (placement: 'bottom' | 'top') => void;
676
+ }
677
+ | null
678
+ >(null);
679
+
680
+ function coercePlacement(placement: MenuPlacement): 'bottom' | 'top' {
681
+ return placement === 'auto' ? 'bottom' : placement;
682
+ }
683
+
684
+ function boundingClientObject(element: HTMLElement) {
685
+ const rect = element.getBoundingClientRect();
686
+ return {
687
+ bottom: rect.bottom,
688
+ height: rect.height,
689
+ left: rect.left,
690
+ right: rect.right,
691
+ top: rect.top,
692
+ width: rect.width,
693
+ };
694
+ }
695
+
696
+ export interface MenuPortalProps<
697
+ Option = unknown,
698
+ IsMulti extends boolean = boolean,
699
+ Group extends GroupBase<Option> = GroupBase<Option>,
700
+ > extends CommonComponentProps<Option, IsMulti, Group> {
701
+ appendTo: HTMLElement | undefined;
702
+ children?: OctaneNode;
703
+ controlElement: HTMLDivElement | null;
704
+ innerProps: DivProps;
705
+ menuPlacement: MenuPlacement;
706
+ menuPosition: MenuPosition;
707
+ }
708
+
709
+ export function MenuPortal(props: MenuPortalProps) @{
710
+ const menuPortalRef = useRef<HTMLDivElement | null>(null);
711
+ const cleanupRef = useRef<(() => void) | null>(null);
712
+ const [placement, setPortalPlacement] = useState<'bottom' | 'top'>(
713
+ coercePlacement(props.menuPlacement),
714
+ );
715
+ const portalPlacementContext = useMemo(() => ({ setPortalPlacement }), []);
716
+ useLayoutEffect(() => {
717
+ setPortalPlacement(coercePlacement(props.menuPlacement));
718
+ }, [props.menuPlacement]);
719
+ const [computedPosition, setComputedPosition] = useState<
720
+ | {
721
+ offset: number;
722
+ rect: { left: number; width: number };
723
+ }
724
+ | null
725
+ >(null);
726
+
727
+ const updateComputedPosition = useCallback(() => {
728
+ if (!props.controlElement) return;
729
+ const rect = boundingClientObject(props.controlElement);
730
+ const scrollDistance =
731
+ props.menuPosition === 'fixed' ? 0 : window.pageYOffset;
732
+ const offset = rect[placement] + scrollDistance;
733
+ if (
734
+ offset !== computedPosition?.offset || rect.left !== computedPosition?.rect.left ||
735
+ rect.width !== computedPosition?.rect.width
736
+ ) {
737
+ setComputedPosition({ offset, rect });
738
+ }
739
+ }, [
740
+ props.controlElement,
741
+ props.menuPosition,
742
+ placement,
743
+ computedPosition?.offset,
744
+ computedPosition?.rect.left,
745
+ computedPosition?.rect.width,
746
+ ]);
747
+
748
+ useLayoutEffect(() => {
749
+ updateComputedPosition();
750
+ }, [updateComputedPosition]);
751
+
752
+ const runAutoUpdate = useCallback(() => {
753
+ cleanupRef.current?.();
754
+ cleanupRef.current = null;
755
+ if (props.controlElement && menuPortalRef.current) {
756
+ cleanupRef.current = autoUpdate(
757
+ props.controlElement,
758
+ menuPortalRef.current,
759
+ updateComputedPosition,
760
+ { elementResize: 'ResizeObserver' in window },
761
+ );
762
+ }
763
+ }, [props.controlElement, updateComputedPosition]);
764
+
765
+ useLayoutEffect(() => {
766
+ runAutoUpdate();
767
+ return () => cleanupRef.current?.();
768
+ }, [runAutoUpdate]);
769
+
770
+ const setMenuPortalElement = useCallback((element: HTMLDivElement | null) => {
771
+ menuPortalRef.current = element;
772
+ runAutoUpdate();
773
+ }, [runAutoUpdate]);
774
+
775
+ if (!props.appendTo && props.menuPosition !== 'fixed' || !computedPosition) return null;
776
+
777
+ const menuWrapperProps: DivProps = {
778
+ ...useStyleProps(
779
+ {
780
+ ...props,
781
+ offset: computedPosition.offset,
782
+ position: props.menuPosition,
783
+ rect: computedPosition.rect,
784
+ },
785
+ 'menuPortal',
786
+ { 'menu-portal': true },
787
+ ),
788
+ ...props.innerProps,
789
+ ref: [setMenuPortalElement, props.innerProps.ref],
790
+ };
791
+ const menuWrapper = <div {...menuWrapperProps}>{props.children}</div>;
792
+
793
+ <PortalPlacementContext.Provider value={portalPlacementContext}>
794
+ {props.appendTo ? createPortal(menuWrapper, props.appendTo) : menuWrapper}
795
+ </PortalPlacementContext.Provider>
796
+ }
797
+
798
+ export interface SelectComponents<
799
+ Option = unknown,
800
+ IsMulti extends boolean = boolean,
801
+ Group extends GroupBase<Option> = GroupBase<Option>,
802
+ > {
803
+ ClearIndicator: ComponentType<IndicatorProps<Option, IsMulti, Group>>;
804
+ Control: ComponentType<ControlProps<Option, IsMulti, Group>>;
805
+ DropdownIndicator: ComponentType<IndicatorProps<Option, IsMulti, Group>> | null;
806
+ DownChevron: ComponentType<DownChevronProps>;
807
+ CrossIcon: ComponentType<CrossIconProps>;
808
+ Group: ComponentType<GroupProps<Option, IsMulti, Group>>;
809
+ GroupHeading: ComponentType<GroupHeadingProps<Option, IsMulti, Group>>;
810
+ IndicatorsContainer: ComponentType<IndicatorsContainerProps<Option, IsMulti, Group>>;
811
+ IndicatorSeparator: ComponentType<IndicatorSeparatorProps<Option, IsMulti, Group>> | null;
812
+ Input: ComponentType<InputProps<Option, IsMulti, Group>>;
813
+ LoadingIndicator: ComponentType<LoadingIndicatorProps<Option, IsMulti, Group>>;
814
+ Menu: ComponentType<MenuProps<Option, IsMulti, Group>>;
815
+ MenuList: ComponentType<MenuListProps<Option, IsMulti, Group>>;
816
+ MenuPortal: (props: MenuPortalProps<Option, IsMulti, Group>) => Octane.JSX.Element | null;
817
+ LoadingMessage: ComponentType<NoticeProps<Option, IsMulti, Group>>;
818
+ NoOptionsMessage: ComponentType<NoticeProps<Option, IsMulti, Group>>;
819
+ MultiValue: ComponentType<MultiValueProps<Option, IsMulti, Group>>;
820
+ MultiValueContainer: ComponentType<MultiValueGenericProps<Option, IsMulti, Group>>;
821
+ MultiValueLabel: ComponentType<MultiValueGenericProps<Option, IsMulti, Group>>;
822
+ MultiValueRemove: ComponentType<MultiValueRemoveProps<Option, IsMulti, Group>>;
823
+ Option: ComponentType<OptionProps<Option, IsMulti, Group>>;
824
+ Placeholder: ComponentType<PlaceholderProps<Option, IsMulti, Group>>;
825
+ SelectContainer: ComponentType<ContainerProps<Option, IsMulti, Group>>;
826
+ SingleValue: ComponentType<SingleValueProps<Option, IsMulti, Group>>;
827
+ ValueContainer: ComponentType<ValueContainerProps<Option, IsMulti, Group>>;
828
+ }
829
+
830
+ export type SelectComponentsConfig<
831
+ Option = unknown,
832
+ IsMulti extends boolean = boolean,
833
+ Group extends GroupBase<Option> = GroupBase<Option>,
834
+ > =
835
+ Partial<SelectComponents<Option, IsMulti, Group>>;
836
+
837
+ export const components: SelectComponents = {
838
+ ClearIndicator,
839
+ Control,
840
+ DropdownIndicator,
841
+ DownChevron,
842
+ CrossIcon,
843
+ Group,
844
+ GroupHeading,
845
+ IndicatorsContainer,
846
+ IndicatorSeparator,
847
+ Input,
848
+ LoadingIndicator,
849
+ Menu,
850
+ MenuList,
851
+ MenuPortal,
852
+ LoadingMessage,
853
+ NoOptionsMessage,
854
+ MultiValue,
855
+ MultiValueContainer,
856
+ MultiValueLabel,
857
+ MultiValueRemove,
858
+ Option,
859
+ Placeholder,
860
+ SelectContainer,
861
+ SingleValue,
862
+ ValueContainer,
863
+ };
864
+
865
+ export type SelectComponentsGeneric = typeof components;
866
+
867
+ export function defaultComponents<
868
+ Option = unknown,
869
+ IsMulti extends boolean = boolean,
870
+ Group extends GroupBase<Option> = GroupBase<Option>,
871
+ >(props: {
872
+ components: SelectComponentsConfig<Option, IsMulti, Group>;
873
+ }): SelectComponents<Option, IsMulti, Group> {
874
+ return { ...components, ...props.components } as SelectComponents<Option, IsMulti, Group>;
875
+ }