@basic-ui/core 0.0.61 → 0.0.63

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.
@@ -1,3938 +0,0 @@
1
- 'use strict';
2
-
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var react = require('react');
6
- var jsxRuntime = require('react/jsx-runtime');
7
- var core = require('@popperjs/core');
8
- var reactDom = require('react-dom');
9
-
10
- // AccordionGroup Component
11
-
12
- const accordionContext = /*#__PURE__*/react.createContext(null);
13
- const {
14
- Provider: AccordionProvider
15
- } = accordionContext;
16
- const useAccordionContext = () => react.useContext(accordionContext);
17
-
18
- // Accordion Component
19
-
20
- const accordionItemContext = /*#__PURE__*/react.createContext(null);
21
- const {
22
- Provider: AccordionItemProvider
23
- } = accordionItemContext;
24
- const useAccordionItemContext = () => react.useContext(accordionItemContext);
25
-
26
- const tabblable = ['button:enabled:not([readonly])', 'select:enabled:not([readonly])', 'textarea:enabled:not([readonly])', 'input:enabled:not([readonly])', 'a[href]', 'area[href]', 'iframe', 'object', 'embed', '[tabindex]', '[contenteditable]', '[autofocus]'].join(',');
27
-
28
- /* This is naive and will not consider tabIndex */
29
- const getTabblableNodes = parentNode => {
30
- if (!parentNode) {
31
- return [];
32
- }
33
- return Array.from(parentNode.querySelectorAll(tabblable));
34
- };
35
- function focusOnChildNode(parentNode, itemIndex) {
36
- const elements = getTabblableNodes(parentNode);
37
- if (elements.length > 0) {
38
- elements[itemIndex === -1 ? elements.length - 1 : 0].focus();
39
- } else {
40
- const currentTabIndex = parentNode.tabIndex;
41
- parentNode.tabIndex = 0;
42
- parentNode.focus();
43
- parentNode.tabIndex = currentTabIndex;
44
- }
45
- }
46
-
47
- function useAutoFocus(open, elementRef) {
48
- react.useEffect(() => {
49
- if (open) {
50
- // We will only autoFocus on the first child if the currently active element isn't already trapped inside the modal
51
- if (elementRef.current && !elementRef.current.contains(document.activeElement)) {
52
- focusOnChildNode(elementRef.current, 0);
53
- }
54
- }
55
- // eslint-disable-next-line react-hooks/exhaustive-deps
56
- }, [open]);
57
- }
58
-
59
- function assignRef(ref, value) {
60
- if (ref == null) return;
61
- if (typeof ref === 'function') {
62
- ref(value);
63
- } else {
64
- try {
65
- ref.current = value;
66
- } catch (error) {
67
- throw new Error(`Cannot assign value "${value}" to ref "${ref}"`);
68
- }
69
- }
70
- }
71
- function assignMultipleRefs(...refs) {
72
- return node => {
73
- refs.forEach(ref => {
74
- assignRef(ref, node);
75
- });
76
- };
77
- }
78
-
79
- function canUseDOM() {
80
- return !!(typeof window !== 'undefined' && window.document && window.document.createElement);
81
- }
82
-
83
- function createContext(rootName, defaultContext) {
84
- const Ctx = /*#__PURE__*/react.createContext(defaultContext);
85
- function Provider(props) {
86
- const {
87
- children,
88
- ...context
89
- } = props;
90
- const value = react.useMemo(() => context,
91
- // eslint-disable-next-line react-hooks/exhaustive-deps
92
- Object.values(context));
93
- return /*#__PURE__*/jsxRuntime.jsx(Ctx.Provider, {
94
- value: value,
95
- children: children
96
- });
97
- }
98
- function useContext(childName) {
99
- const context = react.useContext(Ctx);
100
- if (context) {
101
- return context;
102
- }
103
- if (defaultContext) {
104
- return defaultContext;
105
- }
106
- throw Error(`${childName} must be rendered inside of a ${rootName} component.`);
107
- }
108
- Ctx.displayName = `${rootName}Context`;
109
- Provider.displayName = `${rootName}Provider`;
110
- return [Provider, useContext];
111
- }
112
-
113
- function getCircularIndex(index, maxLength) {
114
- if (maxLength < 0) {
115
- return null;
116
- }
117
- return (index % maxLength + maxLength) % maxLength;
118
- }
119
-
120
- /**
121
- * Detects right clicks
122
- *
123
- * @param nativeEvent
124
- */
125
- function isRightClick(nativeEvent) {
126
- return 'which' in nativeEvent ? nativeEvent.which === 3 : 'button' in nativeEvent ? nativeEvent.button === 2 : false;
127
- }
128
-
129
- /**
130
- * Get an element's owner document. Useful when components are used in iframes
131
- * or other environments like dev tools.
132
- *
133
- * @param element
134
- */
135
- function getOwnerDocument(element) {
136
- return canUseDOM() ? element ? element.ownerDocument : document : null;
137
- }
138
-
139
- // https://twitter.com/chpwn/status/285540192096497664
140
- // iOS constant = 0.55
141
- function rubberBand(distance, dimension, constant = 0.15) {
142
- return distance * dimension * constant / (dimension + constant * distance);
143
- }
144
- function rubberBandClamp(min, max, delta, constant = 0.15) {
145
- if (delta < min) {
146
- return -rubberBand(min - delta, max - min, constant) + min;
147
- }
148
- if (delta > max) {
149
- return rubberBand(delta - max, max - min, constant) + max;
150
- }
151
- return delta;
152
- }
153
-
154
- /* eslint-disable react-hooks/rules-of-hooks */
155
- const useEnhancedEffect$2 = typeof window !== 'undefined' ? react.useLayoutEffect : react.useEffect;
156
-
157
- /**
158
- * Converts a callback to a ref to avoid triggering re-renders when passed as a
159
- * prop and exposed as a stable function to avoid executing effects when
160
- * passed as a dependency.
161
- */
162
- function createStableCallbackHook(useEffectHook, callback) {
163
- const callbackRef = react.useRef(callback);
164
- useEffectHook(() => {
165
- callbackRef.current = callback;
166
- });
167
- return react.useCallback((...args) => {
168
- callbackRef.current && callbackRef.current(...args);
169
- }, []);
170
- }
171
-
172
- /**
173
- * Converts a callback to a ref to avoid triggering re-renders when passed as a
174
- * prop and exposed as a stable function to avoid executing effects when passed
175
- * as a dependency.
176
- */
177
- function useStableCallback(callback) {
178
- return createStableCallbackHook(react.useEffect, callback);
179
- }
180
-
181
- /**
182
- * Converts a callback to a ref to avoid triggering re-renders when passed as a
183
- * prop and exposed as a stable function to avoid executing effects when passed
184
- * as a dependency.
185
- *
186
- * Use this over `useStableCallback` when you want the callback to be cached in
187
- * `useLayoutEffect` instead of `useEffect` to deal with timing issues only when
188
- * needed.
189
- */
190
- function useStableLayoutCallback(callback) {
191
- return createStableCallbackHook(useEnhancedEffect$2, callback);
192
- }
193
-
194
- function wrapEvent(theirHandler, ourHandler) {
195
- return (event, ...otherArgs) => {
196
- const ret = theirHandler && theirHandler(event, ...otherArgs);
197
- if (!event || !event.defaultPrevented) {
198
- return ourHandler(event, ...otherArgs);
199
- }
200
- return ret;
201
- };
202
- }
203
-
204
- function useControlledState(valueProp, onChangeProp, defaultValue, defaultOnChange) {
205
- const isControlled = valueProp !== undefined;
206
- const wasControlled = react.useRef(isControlled);
207
- const hasWarned = react.useRef(false);
208
- const [valueState, setValueState] = react.useState(defaultValue);
209
- if (isControlled) {
210
- if (!wasControlled.current && !hasWarned.current && process.env.NODE_ENV !== 'production') {
211
- console.warn('Trying to change from controlled to uncontrolled.');
212
- hasWarned.current = true;
213
- }
214
- return [valueProp, wrapEvent(onChangeProp, defaultOnChange(setValueState))];
215
- }
216
- return [valueState, wrapEvent(onChangeProp, defaultOnChange(setValueState))];
217
- }
218
-
219
- function useChildrenCounterParent(itemsRef) {
220
- // Reset the options ref every render so that they are always
221
- // accurate and ready for keyboard navigation handlers. Using layout
222
- // effect to schedule this effect before the ComboboxOptions push into
223
- // the array
224
- itemsRef.current = [];
225
- itemsRef.current.isNewRender = true;
226
- react.useEffect(() => {
227
- // Rendering is finished. Meaning any children can now rerender,
228
- // and they should not push any new items to our array, because
229
- // it is not a new render
230
- itemsRef.current.isNewRender = false;
231
- });
232
- react.useEffect(() => {
233
- // When we are unmounting, it means there are no children anymore.
234
- // Clear out our items array
235
- return () => {
236
- itemsRef.current = [];
237
- };
238
- }, [itemsRef]);
239
- }
240
- function useChildrenCounterChild(itemsRef, itemIndexRef, obj, disabled = false) {
241
- if (itemsRef && itemsRef.current.isNewRender) {
242
- if (disabled) {
243
- itemIndexRef.current = -1;
244
- return;
245
- }
246
-
247
- // push this option to the optionsRef array
248
- itemIndexRef.current = itemsRef.current.length;
249
- if (obj instanceof Function) {
250
- itemsRef.current.push(obj(itemIndexRef.current));
251
- } else {
252
- itemsRef.current.push(obj);
253
- }
254
- }
255
- }
256
-
257
- function useFocusReturn(open, rootEl) {
258
- const previousFocusRef = react.useRef((() => {
259
- if (open && typeof document !== 'undefined' && document.activeElement instanceof HTMLElement) {
260
- return document.activeElement;
261
- }
262
- return null;
263
- })());
264
- react.useEffect(() => {
265
- if (open) {
266
- // once opened, keep track of the element that triggered
267
- // the Modal opening
268
- if (!previousFocusRef.current && document.activeElement instanceof HTMLElement && !rootEl.current?.contains(document.activeElement)) {
269
- previousFocusRef.current = document.activeElement;
270
- }
271
- const rootElement = rootEl.current;
272
- const previousElement = previousFocusRef.current;
273
- return () => {
274
- // on unmount, return focus to that element
275
- if (previousElement && !rootElement?.contains(document.activeElement)) {
276
- previousElement.focus({
277
- preventScroll: true
278
- });
279
- }
280
- };
281
- }
282
- }, [open, rootEl]);
283
- }
284
-
285
- function useFocusState(props = {}) {
286
- const {
287
- onFocus,
288
- onBlur
289
- } = props;
290
- const [hasFocus, setHasFocus] = react.useState(false);
291
- const handleFocus = () => {
292
- setHasFocus(true);
293
- };
294
- const handleBlur = () => {
295
- setHasFocus(false);
296
- };
297
- return {
298
- bind: {
299
- onFocus: wrapEvent(onFocus, handleFocus),
300
- onBlur: wrapEvent(onBlur, handleBlur)
301
- },
302
- hasFocus
303
- };
304
- }
305
-
306
- function useOnClickOutside(ref, handler, active = true) {
307
- const listener = react.useCallback(event => {
308
- // Do nothing if clicking ref's element or descendent elements
309
- if (!ref.current || ref.current.contains(event.target)) {
310
- return;
311
- }
312
- handler && handler(event);
313
- }, [ref, handler]);
314
- react.useEffect(() => {
315
- if (active) {
316
- document.addEventListener('pointerup', listener);
317
- return () => {
318
- document.removeEventListener('pointerup', listener);
319
- };
320
- }
321
- return;
322
- }, [listener, active]);
323
- }
324
-
325
- function useOnKeyDown(ownerWindow, handler, active = true) {
326
- react.useEffect(() => {
327
- if (active) {
328
- ownerWindow.addEventListener('keydown', handler);
329
- return () => {
330
- ownerWindow.removeEventListener('keydown', handler);
331
- };
332
- }
333
- return;
334
- }, [ownerWindow, active, handler]);
335
- }
336
-
337
- // This manages transitions between states with a built in reducer to manage
338
- // the data that goes with those transitions.
339
- function useReducerMachine(chart, reducer, initialData) {
340
- const [reducerState, dispatch] = react.useReducer(reducer, initialData);
341
- const {
342
- state,
343
- ...data
344
- } = reducerState;
345
- const transition = (action, payload = {}) => {
346
- const currentState = chart.states[state];
347
- if (!currentState) {
348
- throw new Error(`Unknown currentState "${state}"`);
349
- }
350
- const nextState = currentState.on[action];
351
- if (!nextState) {
352
- throw new Error(`Unknown action "${action}" for state "${state}"`);
353
- }
354
- dispatch({
355
- type: action,
356
- nextState,
357
- ...payload
358
- });
359
- };
360
- return [state, data, transition];
361
- }
362
-
363
- let scrollBodyCount = 0;
364
- function useRemoveBodyScroll(open, elementRef) {
365
- react.useEffect(() => {
366
- if (open && elementRef.current) {
367
- const ownerDocument = getOwnerDocument(elementRef.current);
368
- const ownerWindow = ownerDocument.defaultView || window;
369
- scrollBodyCount += 1;
370
-
371
- // calculate scrollbar width if mounting the first scroll lock
372
- let scrollBarWidth = 0;
373
- if (scrollBodyCount === 1) {
374
- scrollBarWidth = ownerWindow.innerWidth - ownerDocument.body.clientWidth;
375
- }
376
- ownerDocument.body.style.overflow = 'hidden';
377
- if (scrollBarWidth > 0) {
378
- ownerDocument.body.style.marginRight = `${scrollBarWidth}px`;
379
- }
380
- return () => {
381
- scrollBodyCount -= 1;
382
- if (scrollBodyCount === 0) {
383
- ownerDocument.body.style.overflow = '';
384
- ownerDocument.body.style.marginRight = '';
385
- }
386
- };
387
- }
388
- }, [elementRef, open]);
389
- }
390
-
391
- function useThrottle(value, limit) {
392
- const [throttledValue, setThrottledValue] = react.useState(value);
393
- const lastRan = react.useRef(Date.now());
394
- react.useEffect(() => {
395
- const handler = setTimeout(() => {
396
- setThrottledValue(value);
397
- lastRan.current = Date.now();
398
- }, limit - (Date.now() - lastRan.current));
399
- return () => {
400
- clearTimeout(handler);
401
- };
402
- }, [value, limit]);
403
- return throttledValue;
404
- }
405
-
406
- function useMeasure(ref) {
407
- const ro = react.useRef(null);
408
- const [bounds, setBounds] = react.useState({
409
- left: 0,
410
- top: 0,
411
- width: 0,
412
- height: 0
413
- });
414
- react.useEffect(() => {
415
- if (ro.current === null) {
416
- ro.current = new ResizeObserver(entries => {
417
- const entry = entries[0];
418
- setBounds(entry.contentRect);
419
- });
420
- }
421
- if (ref.current) {
422
- ro.current.observe(ref.current);
423
- }
424
- return () => {
425
- if (ro.current) {
426
- ro.current.disconnect();
427
- }
428
- };
429
- }, [ref]);
430
- return bounds;
431
- }
432
-
433
- const initialGestureHandlersState = {
434
- target: null,
435
- x: 0,
436
- xDelta: 0,
437
- xDeltaPercent: 0,
438
- xInitial: 0,
439
- xPrev: 0,
440
- xVelocity: 0,
441
- y: 0,
442
- yDelta: 0,
443
- yDeltaPercent: 0,
444
- yInitial: 0,
445
- yPrev: 0,
446
- yVelocity: 0,
447
- startTime: 0,
448
- down: false,
449
- scrollLocked: false
450
- };
451
- const FRAMERATE_CONST = 1000 / 60; // 60 fps
452
- const VELOCITY_DEPR_FACTOR = FRAMERATE_CONST * 2;
453
- function gestureHandlers(set, containerRef, options = {}) {
454
- const {
455
- ensureTargetIsContainer = false,
456
- minTouchDelta = 0
457
- } = options;
458
-
459
- // Common handlers
460
- const handleUp = () => {
461
- set(state => {
462
- const deltaTime = Date.now() - state.startTime;
463
- const xDelta = state.x - state.xInitial;
464
- const yDelta = state.y - state.yInitial;
465
- const xVelocity = calcVelocity(xDelta, deltaTime, state.xVelocity);
466
- const yVelocity = calcVelocity(yDelta, deltaTime, state.yVelocity);
467
- const newState = {
468
- ...state,
469
- xVelocity,
470
- yVelocity,
471
- target: null,
472
- down: false
473
- };
474
- return newState;
475
- });
476
- };
477
- const handleDown = e => {
478
- const {
479
- target,
480
- pageX,
481
- pageY
482
- } = e;
483
- set(state => {
484
- const newState = {
485
- ...state,
486
- target,
487
- x: pageX,
488
- xDelta: 0,
489
- xDeltaPercent: 0,
490
- xVelocity: 0,
491
- xInitial: pageX,
492
- xPrev: pageX,
493
- y: pageY,
494
- yDelta: 0,
495
- yDeltaPercent: 0,
496
- yVelocity: 0,
497
- yInitial: pageY,
498
- yPrev: pageY,
499
- startTime: Date.now(),
500
- down: true,
501
- scrollLocked: false
502
- };
503
- return newState;
504
- });
505
- };
506
- function calcVelocity(deltaSpace, deltaTime, prevVelocity) {
507
- if (deltaTime < 1) {
508
- deltaTime = 1;
509
- }
510
- const speed = deltaSpace / deltaTime;
511
- const depr = 0.5 + Math.min(deltaTime / VELOCITY_DEPR_FACTOR, 0.5);
512
- return speed * depr + prevVelocity * (1 - depr);
513
- }
514
- function handleMove(e) {
515
- const {
516
- pageX,
517
- pageY
518
- } = e;
519
- if (e.cancelable) {
520
- // prevent drag & drop behaviour from browser
521
- e.preventDefault && e.preventDefault();
522
- }
523
- set(state => {
524
- function getDeltaSum(currentPos, initialPos, prevPos) {
525
- if (state.scrollLocked || Math.abs(currentPos - initialPos) >= minTouchDelta) {
526
- state.scrollLocked = true;
527
- return currentPos - prevPos;
528
- }
529
- return 0;
530
- }
531
- const target = containerRef && containerRef.current || e.target;
532
- const deltaTime = Date.now() - state.startTime;
533
- const width = target ? target.offsetWidth : NaN;
534
- const xDelta = state.xDelta + getDeltaSum(pageX, state.xInitial, state.x);
535
- const xDeltaPercent = xDelta * 100 / width;
536
- const xVelocity = calcVelocity(xDelta, deltaTime, state.xVelocity);
537
- const height = target ? target.offsetHeight : NaN;
538
- const yDelta = state.yDelta + getDeltaSum(pageY, state.yInitial, state.y);
539
- const yDeltaPercent = yDelta * 100 / height;
540
- const yVelocity = calcVelocity(yDelta, deltaTime, state.yVelocity);
541
- const newState = {
542
- ...state,
543
- xDelta,
544
- xDeltaPercent,
545
- x: pageX,
546
- xPrev: state.x,
547
- xVelocity,
548
- yDelta,
549
- yDeltaPercent,
550
- y: pageY,
551
- yPrev: state.y,
552
- yVelocity
553
- };
554
- return newState;
555
- });
556
- }
557
-
558
- // Touch handlers
559
-
560
- function handleTouchMove(e) {
561
- if (e.cancelable) {
562
- // prevent drag & drop behaviour from browser
563
- e.preventDefault();
564
- }
565
- handleMove(e.touches.item(0));
566
- }
567
- function handleTouchStart(e) {
568
- // making sure we're not dragging the element when more than one finger press the screen
569
- const {
570
- touches
571
- } = e;
572
- if (touches.length > 1) {
573
- return;
574
- }
575
- if (ensureTargetIsContainer && containerRef && e.target !== containerRef.current) {
576
- return;
577
- }
578
- const ownerDocument = getOwnerDocument(e.currentTarget);
579
- const ownerWindow = ownerDocument.defaultView || window;
580
- ownerWindow.addEventListener('touchmove', handleTouchMove, {
581
- passive: false
582
- });
583
- ownerWindow.addEventListener('touchend', handleTouchEnd);
584
- ownerWindow.addEventListener('touchcancel', handleTouchEnd);
585
- handleDown(e.touches.item(0));
586
- }
587
- function handleTouchEnd() {
588
- this.removeEventListener('touchmove', handleTouchMove);
589
- this.removeEventListener('touchend', handleTouchEnd);
590
- this.removeEventListener('touchcancel', handleTouchEnd);
591
- handleUp();
592
- }
593
-
594
- // Mouse handlers
595
- function handleMouseDown(e) {
596
- if (ensureTargetIsContainer && containerRef && e.target !== containerRef.current) {
597
- return;
598
- }
599
- const ownerDocument = getOwnerDocument(e.currentTarget);
600
- const ownerWindow = ownerDocument.defaultView || window;
601
- if (e.button === 0) {
602
- ownerWindow.addEventListener('mousemove', handleMove);
603
- ownerWindow.addEventListener('mouseup', handleMouseUp);
604
- handleDown(e);
605
- }
606
- }
607
- function handleMouseUp() {
608
- this.removeEventListener('mousemove', handleMove);
609
- this.removeEventListener('mouseup', handleMouseUp);
610
- handleUp();
611
- }
612
- return {
613
- onMouseDown: handleMouseDown,
614
- onTouchStart: handleTouchStart
615
- };
616
- }
617
- const useGestureHandlers = (containerRef, onGesture, options = {}) => {
618
- const state = react.useRef({
619
- ...initialGestureHandlersState
620
- });
621
- const set = cb => {
622
- state.current = cb(state.current);
623
- onGesture && onGesture(state.current);
624
- return state.current;
625
- };
626
- const handlers = gestureHandlers(set, containerRef, options);
627
- return {
628
- state: state.current,
629
- handlers
630
- };
631
- };
632
-
633
- function getScope(rootRef) {
634
- const queryAllNodes = matcherFn => {
635
- if (!rootRef.current) {
636
- return [];
637
- }
638
- const allNodes = rootRef.current.querySelectorAll('*');
639
- const filtered = [];
640
- allNodes.forEach(node => {
641
- const props = {};
642
- const {
643
- attributes
644
- } = node;
645
- for (let i = 0; i < attributes.length; i++) {
646
- const attr = attributes[i];
647
- props[attr.name] = attr.value;
648
- }
649
- if (matcherFn(node.tagName.toLowerCase(), props, node)) {
650
- filtered.push(node);
651
- }
652
- });
653
- return filtered;
654
- };
655
- return {
656
- queryAllNodes
657
- };
658
- }
659
- function useScope(rootRef) {
660
- const scope = react.useRef(getScope(rootRef));
661
- return scope;
662
- }
663
-
664
- const Accordion = /*#__PURE__*/react.forwardRef(function Accordion(props, forwardedRef) {
665
- const {
666
- as: Comp = 'div',
667
- expandedIndex = -1,
668
- onChange,
669
- ...otherProps
670
- } = props;
671
- const [childrenHeaderHasFocus, setChildrenHeaderHasFocus] = react.useState(false);
672
- const ref = react.useRef(null);
673
- const scope = useScope(ref);
674
- const contextValue = {
675
- childrenHeaderHasFocus,
676
- setChildrenHeaderHasFocus,
677
- scope,
678
- expandedIndex,
679
- onChange
680
- };
681
- return /*#__PURE__*/jsxRuntime.jsx(AccordionProvider, {
682
- value: contextValue,
683
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
684
- ref: assignMultipleRefs(forwardedRef, ref),
685
- ...otherProps,
686
- "data-accordion-root": "",
687
- "data-children-has-focus": childrenHeaderHasFocus
688
- })
689
- });
690
- });
691
-
692
- const AccordionItem = /*#__PURE__*/react.forwardRef(function AccordionItem(props, forwardedRef) {
693
- const {
694
- as: Comp = react.Fragment,
695
- expanded = false,
696
- onChange,
697
- ...otherProps
698
- } = props;
699
- const id = react.useId();
700
- const headerId = id ? `accordion-header-${id}` : undefined;
701
- const bodyId = id ? `accordion-body-${id}` : undefined;
702
- const contextValue = {
703
- headerId,
704
- bodyId,
705
- expanded,
706
- onChange
707
- };
708
- return /*#__PURE__*/jsxRuntime.jsx(AccordionItemProvider, {
709
- value: contextValue,
710
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
711
- ref: forwardedRef,
712
- ...otherProps
713
- })
714
- });
715
- });
716
-
717
- function headerScopeQuery(type, props) {
718
- return props['data-accordion-header'] === '';
719
- }
720
- function bodyScopeQuery(type, props) {
721
- return props['data-accordion-body'] === '';
722
- }
723
-
724
- const AccordionHeader = /*#__PURE__*/react.forwardRef(function AccordionHeader(props, forwardedRef) {
725
- const {
726
- as: Comp = 'div',
727
- onKeyDown,
728
- onClick: onClickProp,
729
- onFocus,
730
- onBlur,
731
- ...otherProps
732
- } = props;
733
- const accordionContext = useAccordionContext();
734
- const accordionItemContext = useAccordionItemContext();
735
- const ref = react.useRef(null);
736
- const [index, setIndex] = react.useState();
737
- if (!accordionItemContext) {
738
- throw new Error('Missing parent <Accordion /> component');
739
- }
740
- react.useEffect(() => {
741
- if (accordionContext) {
742
- const allHeaders = accordionContext.scope.current.queryAllNodes(headerScopeQuery) || [];
743
- const index = allHeaders.findIndex(e => e === ref.current);
744
- setIndex(index);
745
- }
746
- }, [accordionContext]);
747
- const onClick = wrapEvent(onClickProp, e => {
748
- let index = 0;
749
- if (accordionItemContext.expanded) {
750
- index = -1;
751
- } else if (accordionContext) {
752
- const allHeaders = accordionContext.scope.current.queryAllNodes(headerScopeQuery) || [];
753
- index = allHeaders.findIndex(e => e === ref.current);
754
- if (index === accordionContext.expandedIndex) {
755
- index = -1;
756
- }
757
- accordionContext.onChange && accordionContext.onChange(e, index);
758
- }
759
- accordionItemContext.onChange && accordionItemContext.onChange(e, index >= 0);
760
- });
761
- const handleKeyDown = e => {
762
- switch (e.key) {
763
- case 'Enter':
764
- case ' ':
765
- {
766
- onClick(e);
767
- e.preventDefault();
768
- break;
769
- }
770
- case 'ArrowUp':
771
- case 'ArrowDown':
772
- case 'Home':
773
- case 'End':
774
- {
775
- if (!accordionContext) {
776
- return;
777
- }
778
- const allHeaders = accordionContext.scope.current.queryAllNodes(headerScopeQuery);
779
- e.preventDefault();
780
- if (allHeaders.length === 0) {
781
- return;
782
- }
783
- let nextIndex = allHeaders.findIndex(e => e === ref.current);
784
- switch (e.key) {
785
- case 'ArrowUp':
786
- nextIndex += -1;
787
- break;
788
- case 'ArrowDown':
789
- nextIndex += +1;
790
- break;
791
- case 'Home':
792
- nextIndex = 0;
793
- break;
794
- case 'End':
795
- nextIndex = -1;
796
- break;
797
- }
798
-
799
- // We're sure it will not be null, because we already checked for allHeaders.length > 0 above
800
- nextIndex = getCircularIndex(nextIndex, allHeaders.length);
801
- allHeaders[nextIndex] && allHeaders[nextIndex].focus();
802
- break;
803
- }
804
- default:
805
- return;
806
- }
807
- };
808
- const handleFocus = () => {
809
- if (accordionContext) {
810
- if (!accordionContext.childrenHeaderHasFocus) {
811
- // this is needed to avoid rerendering the parent and
812
- // messing up with the internal count for children/parent count
813
- accordionContext.setChildrenHeaderHasFocus(true);
814
- }
815
- }
816
- };
817
- const handleBlur = e => {
818
- if (accordionContext) {
819
- const allHeaders = accordionContext.scope.current.queryAllNodes(headerScopeQuery);
820
- const newFocusIsHeader = allHeaders.findIndex(header => header === e.relatedTarget) >= 0;
821
-
822
- // only remove focus flag if the focus went to some element
823
- // that is not an accordion header
824
- if (!newFocusIsHeader) {
825
- accordionContext.setChildrenHeaderHasFocus(false);
826
- }
827
- }
828
- };
829
- const expanded = Boolean(accordionItemContext.expanded || accordionContext && accordionContext.expandedIndex === index);
830
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
831
- ref: assignMultipleRefs(ref, forwardedRef),
832
- ...otherProps,
833
- id: accordionItemContext.headerId,
834
- "aria-controls": accordionItemContext.bodyId,
835
- role: "button",
836
- "data-accordion-header": "",
837
- tabIndex: "0",
838
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
839
- onFocus: wrapEvent(onFocus, handleFocus),
840
- onBlur: wrapEvent(onBlur, handleBlur),
841
- onClick: onClick,
842
- "aria-expanded": String(expanded)
843
- });
844
- });
845
-
846
- const AccordionBody = /*#__PURE__*/react.forwardRef(function AccordionBody(props, forwardedRef) {
847
- const {
848
- as: Comp = 'div',
849
- ...otherProps
850
- } = props;
851
- const accordionItemContext = useAccordionItemContext();
852
- const accordionContext = useAccordionContext();
853
- const ref = react.useRef(null);
854
- const [index, setIndex] = react.useState();
855
- if (!accordionItemContext) {
856
- throw new Error('Missing parent <Accordion /> component');
857
- }
858
- react.useEffect(() => {
859
- if (accordionContext) {
860
- const allHeaders = accordionContext.scope.current.queryAllNodes(bodyScopeQuery);
861
- const index = allHeaders.findIndex(e => e === ref.current);
862
- setIndex(index);
863
- }
864
- }, [accordionContext]);
865
- const expanded = Boolean(accordionItemContext.expanded || accordionContext && accordionContext.expandedIndex === index);
866
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
867
- ref: assignMultipleRefs(forwardedRef, ref),
868
- ...otherProps,
869
- "aria-labelledby": accordionItemContext.headerId,
870
- id: accordionItemContext.bodyId,
871
- role: "region",
872
- "data-accordion-body": "",
873
- hidden: expanded ? undefined : 'hidden'
874
- });
875
- });
876
-
877
- const CheckBox = /*#__PURE__*/react.forwardRef(function CheckBox(props, forwardedRef) {
878
- const {
879
- as: Comp = 'input',
880
- checked: checkedProp,
881
- defaultChecked = false,
882
- onChange: onChangeProp,
883
- ...otherProps
884
- } = props;
885
- const [checked, onChange] = useControlledState(checkedProp, onChangeProp, defaultChecked, setValue => e => {
886
- setValue(e.target.checked);
887
- });
888
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
889
- ref: forwardedRef,
890
- type: "checkbox",
891
- checked: checked,
892
- "aria-checked": checked,
893
- onChange: onChange,
894
- ...otherProps
895
- });
896
- });
897
-
898
- const comboboxContext = /*#__PURE__*/react.createContext(null);
899
- const {
900
- Provider: ComboBoxProvider
901
- } = comboboxContext;
902
- const useComboBoxContext = () => react.useContext(comboboxContext);
903
-
904
- function scopeQuery$1(nodeType, props) {
905
- return props['data-reach-combobox-option'] === '';
906
- }
907
-
908
- ////////////////////////////////////////////////////////////////////////////////
909
- // States
910
-
911
- // Nothing going on, waiting for the user to type or use the arrow keys
912
- const IDLE = 'IDLE';
913
-
914
- // The component is suggesting options as the user types
915
- const SUGGESTING = 'SUGGESTING';
916
-
917
- // The user is using the keyboard to navigate the list, not typing
918
- const NAVIGATING = 'NAVIGATING';
919
- ////////////////////////////////////////////////////////////////////////////////
920
- // Actions:
921
-
922
- // Used to sync the state with controlled state, right after mounting
923
- const INIT = 'INIT';
924
-
925
- // User cleared the value w/ backspace, but input still has focus
926
- const CLEAR = 'CLEAR';
927
-
928
- // User cleared the value w/ backspace, but input still has focus
929
- const CLEAR_SELECTION = 'CLEAR_SELECTION';
930
-
931
- // User is typing
932
- const CHANGE = 'CHANGE';
933
-
934
- // User is navigating w/ the keyboard
935
- const NAVIGATE = 'NAVIGATE';
936
-
937
- // User can be navigating with keyboard and then click instead, we want the
938
- // value from the click, not the current nav item
939
- const SELECT_WITH_KEYBOARD = 'SELECT_WITH_KEYBOARD';
940
- const SELECT_WITH_CLICK = 'SELECT_WITH_CLICK';
941
-
942
- // Pretty self-explanatory, user can hit escape or blur to close the popover
943
- const ESCAPE = 'ESCAPE';
944
- const BLUR = 'BLUR';
945
- const FOCUS = 'FOCUS';
946
- const OPEN_WITH_BUTTON = 'OPEN_WITH_BUTTON';
947
- const CLOSE_WITH_BUTTON = 'CLOSE_WITH_BUTTON';
948
- ////////////////////////////////////////////////////////////////////////////////
949
- const stateChart = {
950
- initial: IDLE,
951
- states: {
952
- [IDLE]: {
953
- on: {
954
- [BLUR]: IDLE,
955
- [CLEAR]: IDLE,
956
- [INIT]: IDLE,
957
- [CLEAR_SELECTION]: IDLE,
958
- [CHANGE]: SUGGESTING,
959
- [FOCUS]: SUGGESTING,
960
- [NAVIGATE]: NAVIGATING,
961
- [OPEN_WITH_BUTTON]: SUGGESTING
962
- }
963
- },
964
- [SUGGESTING]: {
965
- on: {
966
- [CHANGE]: SUGGESTING,
967
- [FOCUS]: SUGGESTING,
968
- [INIT]: SUGGESTING,
969
- [NAVIGATE]: NAVIGATING,
970
- [CLEAR]: IDLE,
971
- [CLEAR_SELECTION]: SUGGESTING,
972
- [ESCAPE]: IDLE,
973
- [BLUR]: IDLE,
974
- [SELECT_WITH_CLICK]: IDLE,
975
- [CLOSE_WITH_BUTTON]: IDLE
976
- }
977
- },
978
- [NAVIGATING]: {
979
- on: {
980
- [CHANGE]: SUGGESTING,
981
- [FOCUS]: SUGGESTING,
982
- [INIT]: NAVIGATING,
983
- [CLEAR]: IDLE,
984
- [CLEAR_SELECTION]: NAVIGATING,
985
- [BLUR]: IDLE,
986
- [ESCAPE]: IDLE,
987
- [NAVIGATE]: NAVIGATING,
988
- [SELECT_WITH_KEYBOARD]: IDLE,
989
- [SELECT_WITH_CLICK]: IDLE,
990
- [CLOSE_WITH_BUTTON]: IDLE
991
- }
992
- }
993
- }
994
- };
995
- function comboboxReducer(data, action) {
996
- const nextState = {
997
- ...data,
998
- state: action.nextState,
999
- lastActionType: action.type
1000
- };
1001
- switch (action.type) {
1002
- case INIT:
1003
- case CHANGE:
1004
- return {
1005
- ...nextState,
1006
- text: action.text,
1007
- navigationItem: '',
1008
- item: ''
1009
- };
1010
- case NAVIGATE:
1011
- case OPEN_WITH_BUTTON:
1012
- if (action.persistSelection) {
1013
- return {
1014
- ...nextState,
1015
- navigationItem: data.item
1016
- };
1017
- }
1018
- return {
1019
- ...nextState,
1020
- navigationItem: action.item
1021
- };
1022
- case CLEAR_SELECTION:
1023
- return {
1024
- ...nextState,
1025
- navigationItem: ''
1026
- };
1027
- case CLEAR:
1028
- return {
1029
- ...nextState,
1030
- text: '',
1031
- navigationItem: '',
1032
- item: ''
1033
- };
1034
- case BLUR:
1035
- return {
1036
- ...nextState,
1037
- text: action.text,
1038
- navigationItem: '',
1039
- item: action.item
1040
- };
1041
- case CLOSE_WITH_BUTTON:
1042
- case ESCAPE:
1043
- return {
1044
- ...nextState,
1045
- navigationItem: '',
1046
- item: ''
1047
- };
1048
- case SELECT_WITH_CLICK:
1049
- case SELECT_WITH_KEYBOARD:
1050
- return {
1051
- ...nextState,
1052
- text: action.text,
1053
- item: action.item,
1054
- navigationItem: ''
1055
- };
1056
- case FOCUS:
1057
- return {
1058
- ...nextState,
1059
- navigationItem: action.item
1060
- };
1061
- default:
1062
- throw new Error(`Unknown action ${action.type}`);
1063
- }
1064
- }
1065
- const visibleStates = [SUGGESTING, NAVIGATING];
1066
- const isVisible = state => visibleStates.indexOf(state) >= 0;
1067
-
1068
- ////////////////////////////////////////////////////////////////////////////////
1069
- // The rest is all implementation details
1070
-
1071
- // Move focus back to the input if we start navigating w/ the
1072
- // keyboard after focus has moved to any focusable content in
1073
- // the popup.
1074
- function useFocusManagement(lastActionType, inputRef) {
1075
- // useEffect so that the cursor goes to the end of the input instead
1076
- // of awkwardly at the beginning, unclear to me why ...
1077
- react.useEffect(() => {
1078
- if (lastActionType === NAVIGATE || lastActionType === ESCAPE || lastActionType === SELECT_WITH_CLICK || lastActionType === OPEN_WITH_BUTTON) {
1079
- inputRef.current && inputRef.current.focus();
1080
- }
1081
- });
1082
- }
1083
- function getNextItem(currentItem, key, optionsItems, autocomplete) {
1084
- const jumpToStartOrEnd = key === 'Home' || key === 'End';
1085
- let incr = 1;
1086
- switch (key) {
1087
- case 'PageUp':
1088
- incr = -10;
1089
- break;
1090
- case 'PageDown':
1091
- incr = 10;
1092
- break;
1093
- case 'End':
1094
- case 'ArrowUp':
1095
- incr = -1;
1096
- break;
1097
- case 'Home':
1098
- case 'ArrowDown':
1099
- incr = 1;
1100
- break;
1101
- }
1102
- const index = currentItem === '' ? -1 : optionsItems.findIndex(n => String(n.id) === currentItem);
1103
- const optionsLen = optionsItems.length;
1104
-
1105
- // Nothing selected, either go to start, or end
1106
- if (index < 0 || jumpToStartOrEnd) {
1107
- if (incr > 0) {
1108
- // Go to start
1109
- return optionsItems[0];
1110
- } else {
1111
- // Go to end
1112
- return optionsItems[optionsLen - 1];
1113
- }
1114
- } else if (autocomplete) {
1115
- const nextIndex = index + incr;
1116
- if (nextIndex < 0 || nextIndex >= optionsLen) {
1117
- // Next is outside the bounds of list, return nothing selected
1118
- return null;
1119
- }
1120
- }
1121
-
1122
- // I'm sure it won't be null, we already check optionsLen above
1123
- return optionsItems[getCircularIndex(index + incr, optionsLen)];
1124
- }
1125
-
1126
- // We want the same events when the input or the popup have focus (HOW COOL ARE
1127
- // HOOKS BTW?) This is probably the hairiest piece but it's not bad.
1128
- function useKeyDown() {
1129
- const {
1130
- data: {
1131
- text,
1132
- navigationItem
1133
- },
1134
- onSelect,
1135
- optionsRef,
1136
- inputRef,
1137
- state,
1138
- transition,
1139
- autocompletePropRef,
1140
- clearOnEscapeRef,
1141
- persistSelectionRef,
1142
- listScope
1143
- } = useComboBoxContext();
1144
- return function handleKeyDown(event) {
1145
- const optionNodes = listScope.current.queryAllNodes(scopeQuery$1);
1146
- switch (event.key) {
1147
- case 'Home':
1148
- case 'End':
1149
- case 'PageUp':
1150
- case 'PageDown':
1151
- case 'ArrowUp':
1152
- case 'ArrowDown':
1153
- {
1154
- // Don't scroll the page
1155
- event.preventDefault();
1156
- const optionsLen = optionNodes.length;
1157
-
1158
- // If the developer didn't render any options, there's no point in
1159
- // trying to navigate--but seriously what the heck? Give us some
1160
- // options fam.
1161
- if (optionsLen === 0) {
1162
- return;
1163
- }
1164
- if (state === IDLE) {
1165
- // Opening a closed list
1166
- transition(NAVIGATE, {
1167
- persistSelection: persistSelectionRef.current
1168
- });
1169
- } else {
1170
- // When autocompletting, we'll not cycle through the list directly
1171
- const autocomplete = autocompletePropRef.current;
1172
-
1173
- // Get next selected item
1174
- const nextItem = getNextItem(navigationItem, event.key, optionNodes, autocomplete);
1175
- if (nextItem !== null) {
1176
- nextItem.scrollIntoView({
1177
- behavior: 'auto',
1178
- block: 'nearest'
1179
- });
1180
- transition(NAVIGATE, {
1181
- value: optionsRef.current[nextItem.id].text,
1182
- item: nextItem.id
1183
- });
1184
- } else {
1185
- transition(NAVIGATE, {
1186
- value: null,
1187
- item: ''
1188
- });
1189
- }
1190
- }
1191
- break;
1192
- }
1193
- case 'Escape':
1194
- {
1195
- if (state !== IDLE) {
1196
- transition(ESCAPE);
1197
- } else if (state === IDLE && text !== '') {
1198
- if (!inputRef.current || !clearOnEscapeRef.current) {
1199
- break;
1200
- }
1201
-
1202
- // emulate a inputRef change event, might not work in future versions of React
1203
- const lastValue = inputRef.current.value;
1204
- inputRef.current.value = '';
1205
- const tracker = inputRef.current._valueTracker;
1206
- if (tracker) {
1207
- tracker.setValue(lastValue);
1208
- }
1209
- const event = new Event('change', {
1210
- bubbles: true
1211
- });
1212
- inputRef.current.dispatchEvent(event);
1213
- }
1214
- break;
1215
- }
1216
- case 'Enter':
1217
- {
1218
- if (state === NAVIGATING && navigationItem !== '') {
1219
- const {
1220
- value: navigationValue,
1221
- text: navigationText
1222
- } = optionsRef.current[navigationItem];
1223
-
1224
- // don't want to submit forms
1225
- event.preventDefault();
1226
- onSelect && onSelect(navigationText, navigationItem, navigationValue);
1227
- transition(SELECT_WITH_KEYBOARD, {
1228
- text: navigationText,
1229
- item: navigationItem
1230
- });
1231
- }
1232
- break;
1233
- }
1234
- }
1235
- };
1236
- }
1237
- function useBlur() {
1238
- const {
1239
- data: {
1240
- navigationItem,
1241
- text: stateText
1242
- },
1243
- transition,
1244
- optionsRef,
1245
- popoverRef,
1246
- inputRef,
1247
- buttonRef,
1248
- onSelect,
1249
- selectOnBlur // not implemented yet
1250
- } = useComboBoxContext();
1251
- return function handleBlur() {
1252
- requestAnimationFrame(() => {
1253
- // we on want to close only if focus rests outside the combobox
1254
- if (document.activeElement !== inputRef.current && document.activeElement !== buttonRef.current && popoverRef.current) {
1255
- if (popoverRef.current.contains(document.activeElement)) ; else {
1256
- // focus landed outside the combobox, close it.
1257
- if (!selectOnBlur || navigationItem === '') {
1258
- // we don't wanna select on blur, or navigationIndex is invalid
1259
- transition(BLUR, {
1260
- text: stateText,
1261
- item: ''
1262
- });
1263
- } else {
1264
- // select the currently selected item
1265
- const {
1266
- value: navigationValue,
1267
- text: navigationText
1268
- } = optionsRef.current[navigationItem];
1269
- onSelect && onSelect(navigationText, navigationItem, navigationValue);
1270
- transition(BLUR, {
1271
- text: navigationText,
1272
- item: navigationItem
1273
- });
1274
- }
1275
- }
1276
- }
1277
- });
1278
- };
1279
- }
1280
-
1281
- const Combobox = /*#__PURE__*/react.forwardRef(function Combobox({
1282
- // Called whenever the user selects an item from the list
1283
- onSelect,
1284
- // opens the list when the input receives focused (but only if there are
1285
- // items in the list)
1286
- openOnFocus = false,
1287
- // if set to true, it will select an item after blurring
1288
- selectOnBlur = false,
1289
- children,
1290
- as: Comp = 'div',
1291
- innerAs,
1292
- ...rest
1293
- }, ref) {
1294
- // We store the values of all the ComboboxOptions on this ref. This makes it
1295
- // possible to perform the keyboard navigation from the input on the list. We
1296
- // manipulate this array through context so that we don't have to enforce a
1297
- // parent/child relationship between ComboboxList and ComboboxOption.
1298
- const optionsRef = react.useRef({});
1299
- const listScope = react.useRef(getScope({
1300
- current: null
1301
- }));
1302
-
1303
- // Need this to focus it
1304
- const inputRef = react.useRef(null);
1305
- const popoverRef = react.useRef(null);
1306
- const buttonRef = react.useRef(null);
1307
-
1308
- // When <ComboboxInput autocomplete={false} /> we don't want cycle back to
1309
- // the user's value while navigating (because it's always the user's value),
1310
- // but we need to know this in useKeyDown which is far away from the prop
1311
- // here, so we do something sneaky and write it to this ref on context so we
1312
- // can use it anywhere else 😛. Another new trick for me and I'm excited
1313
- // about this one too!
1314
- const autocompletePropRef = react.useRef(false);
1315
- const persistSelectionRef = react.useRef(false);
1316
- const clearOnEscapeRef = react.useRef(false);
1317
- const listboxIdRef = react.useRef(undefined);
1318
- const labelIdRef = react.useRef(undefined);
1319
- const defaultData = {
1320
- // initial state
1321
- state: stateChart.initial,
1322
- // the value the user has typed, we derived this also when the developer is
1323
- // controlling the value of ComboboxInput
1324
- text: '',
1325
- // the index the user has typed, we derived this also when the developer is
1326
- // controlling the value of ComboboxInput
1327
- item: '',
1328
- // the hash of the currently navigated item
1329
- navigationItem: '',
1330
- // the last submitted action
1331
- lastActionType: INIT
1332
- };
1333
- const [state, data, transition] = useReducerMachine(stateChart, comboboxReducer, defaultData);
1334
- listboxIdRef.current = react.useId();
1335
- labelIdRef.current = react.useId();
1336
- const context = react.useMemo(() => ({
1337
- data,
1338
- inputRef,
1339
- popoverRef,
1340
- buttonRef,
1341
- onSelect,
1342
- optionsRef,
1343
- listScope,
1344
- state,
1345
- transition,
1346
- listboxIdRef,
1347
- labelIdRef,
1348
- autocompletePropRef,
1349
- persistSelectionRef,
1350
- clearOnEscapeRef,
1351
- isVisible: isVisible(state),
1352
- openOnFocus,
1353
- selectOnBlur
1354
- }), [data, onSelect, listScope, state, transition, openOnFocus, selectOnBlur]);
1355
- return /*#__PURE__*/jsxRuntime.jsx(ComboBoxProvider, {
1356
- value: context,
1357
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
1358
- ...rest,
1359
- as: innerAs,
1360
- "data-reach-combobox": "",
1361
- ref: ref,
1362
- role: "combobox",
1363
- "aria-haspopup": "listbox",
1364
- "aria-owns": listboxIdRef.current,
1365
- "aria-expanded": context.isVisible,
1366
- children: children
1367
- })
1368
- });
1369
- });
1370
-
1371
- const ComboboxButton = /*#__PURE__*/react.forwardRef(function ComboboxButton({
1372
- as: Comp = 'button',
1373
- innerAs,
1374
- onClick,
1375
- onKeyDown,
1376
- ...props
1377
- }, ref) {
1378
- const {
1379
- transition,
1380
- data,
1381
- state,
1382
- buttonRef,
1383
- listboxIdRef,
1384
- isVisible
1385
- } = useComboBoxContext();
1386
- const handleKeyDown = useKeyDown();
1387
- const handleClick = () => {
1388
- const payload = {
1389
- item: data.navigationItem
1390
- };
1391
- if (state === IDLE) {
1392
- transition(OPEN_WITH_BUTTON, payload);
1393
- } else {
1394
- transition(CLOSE_WITH_BUTTON, payload);
1395
- }
1396
- };
1397
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1398
- as: innerAs,
1399
- "data-reach-combobox-button": "",
1400
- "aria-controls": listboxIdRef.current,
1401
- "aria-haspopup": "listbox",
1402
- "aria-expanded": isVisible,
1403
- ref: assignMultipleRefs(ref, buttonRef),
1404
- onClick: wrapEvent(onClick, handleClick),
1405
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
1406
- ...props
1407
- });
1408
- });
1409
-
1410
- const ComboboxInput = /*#__PURE__*/react.forwardRef(function ComboboxInput({
1411
- as: Comp = 'input',
1412
- innerAs,
1413
- selectOnClick = false,
1414
- autocomplete = true,
1415
- clearOnEscape = false,
1416
- // wrapped events
1417
- onClick,
1418
- onChange,
1419
- onKeyDown,
1420
- onBlur,
1421
- onFocus,
1422
- id: preferredId,
1423
- defaultValue = '',
1424
- // might be controlled
1425
- value: controlledValue,
1426
- ...props
1427
- }, forwardedRef) {
1428
- const {
1429
- data: {
1430
- navigationItem,
1431
- text,
1432
- lastActionType
1433
- },
1434
- inputRef,
1435
- state,
1436
- transition,
1437
- listboxIdRef,
1438
- autocompletePropRef,
1439
- clearOnEscapeRef,
1440
- openOnFocus,
1441
- optionsRef,
1442
- labelIdRef
1443
- } = useComboBoxContext();
1444
-
1445
- // Keep focus on the input component
1446
- useFocusManagement(lastActionType, inputRef);
1447
-
1448
- // Keep using the defaultValue until the user started interacting
1449
- const hasStartedInteracting = react.useRef(false);
1450
-
1451
- // Because we close the List on blur, we need to track if the blur is
1452
- // caused by clicking inside the list, and if so, don't close the List.
1453
- const selectOnClickRef = react.useRef(false);
1454
- const handleBlur = useBlur();
1455
-
1456
- // Update ref props
1457
- autocompletePropRef.current = autocomplete;
1458
- clearOnEscapeRef.current = clearOnEscape;
1459
- listboxIdRef.current = preferredId || listboxIdRef.current;
1460
-
1461
- // [*]... and when controlled, we don't trigger handleValueChange as the user
1462
- // types, instead the developer controls it with the normal input onChange
1463
- // prop
1464
- const handleChange = e => {
1465
- // After the user started typing, lets forget the defaultValue
1466
- hasStartedInteracting.current = true;
1467
- const text = e.target.value;
1468
- if (text.trim() === '') {
1469
- transition(CLEAR);
1470
- } else {
1471
- transition(CHANGE, {
1472
- text
1473
- });
1474
- }
1475
- };
1476
- const handleKeyDown = useKeyDown();
1477
- const handleFocus = () => {
1478
- if (selectOnClick) {
1479
- selectOnClickRef.current = true;
1480
- }
1481
- // If we select an option with click, useFocusManagement will focus the
1482
- // input, in those cases we don't want to cause the menu to open back up,
1483
- // so we guard behind these states
1484
- if (openOnFocus && lastActionType !== SELECT_WITH_CLICK) {
1485
- transition(FOCUS, {
1486
- item: navigationItem
1487
- });
1488
- }
1489
- };
1490
- const handleClick = () => {
1491
- if (selectOnClickRef.current) {
1492
- selectOnClickRef.current = false;
1493
- inputRef.current && inputRef.current.select();
1494
- }
1495
- };
1496
- const navigationText = navigationItem !== '' ? optionsRef.current[navigationItem].text : undefined;
1497
- const fallbackValue = hasStartedInteracting.current ? '' : defaultValue;
1498
- const inputStrings =
1499
- // When idle, we don't have a navigationText on ArrowUp/Down
1500
- autocomplete && state === NAVIGATING ? [navigationText, controlledValue, text, fallbackValue] : [controlledValue, text, fallbackValue];
1501
- const inputValue = inputStrings.find(str => str !== undefined);
1502
-
1503
- // If they are controlling the value we still need to do our transitions, so
1504
- // we have this derived state to emulate onChange of the input as we receive
1505
- // new `value`s ...[*]
1506
- react.useEffect(() => {
1507
- transition(INIT, {
1508
- text: inputValue,
1509
- item: ''
1510
- });
1511
- // eslint-disable-next-line react-hooks/exhaustive-deps
1512
- }, []);
1513
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1514
- ...props,
1515
- as: innerAs,
1516
- "data-reach-combobox-input": "",
1517
- ref: assignMultipleRefs(inputRef, forwardedRef),
1518
- value: inputValue,
1519
- onClick: wrapEvent(onClick, handleClick),
1520
- onBlur: wrapEvent(onBlur, handleBlur),
1521
- onFocus: wrapEvent(onFocus, handleFocus),
1522
- onChange: wrapEvent(onChange, handleChange),
1523
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
1524
- "aria-labelledby": labelIdRef.current,
1525
- id: listboxIdRef.current,
1526
- "aria-autocomplete": "both",
1527
- "aria-activedescendant": navigationItem !== '' ? navigationItem : undefined,
1528
- autoComplete: "off"
1529
- });
1530
- });
1531
-
1532
- const ComboboxLabel = /*#__PURE__*/react.forwardRef(function ComboboxButton({
1533
- as: Comp = 'label',
1534
- innerAs,
1535
- id: preferredId,
1536
- ...props
1537
- }, ref) {
1538
- const {
1539
- listboxIdRef,
1540
- labelIdRef
1541
- } = useComboBoxContext();
1542
- labelIdRef.current = preferredId || labelIdRef.current;
1543
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1544
- as: innerAs,
1545
- "data-reach-combobox-label": "",
1546
- htmlFor: listboxIdRef.current,
1547
- id: labelIdRef.current,
1548
- ref: ref,
1549
- ...props
1550
- });
1551
- });
1552
-
1553
- const ComboboxList = /*#__PURE__*/react.forwardRef(function ComboboxList({
1554
- // when true, and the list opens again, the option with a matching value will be
1555
- // automatically highleted.
1556
- persistSelection = false,
1557
- as: Comp = 'ul',
1558
- innerAs,
1559
- ...props
1560
- }, ref) {
1561
- const {
1562
- persistSelectionRef,
1563
- labelIdRef,
1564
- listScope
1565
- } = useComboBoxContext();
1566
- const listRef = react.useRef(null);
1567
- react.useEffect(() => {
1568
- listScope.current = getScope(listRef);
1569
- }, [listScope]);
1570
- persistSelectionRef.current = persistSelection;
1571
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1572
- ...props,
1573
- as: innerAs,
1574
- ref: assignMultipleRefs(ref, listRef),
1575
- "data-reach-combobox-list": "",
1576
- role: "listbox",
1577
- "aria-labelledby": labelIdRef.current
1578
- });
1579
- });
1580
-
1581
- // We don't want to track the active descendant with indexes because nothing is
1582
- // more annoying in a combobox than having it change values RIGHT AS YOU HIT
1583
- // ENTER. That only happens if you use the index as your data, rather than
1584
- // *your data as your data*. We use this to generate a unique ID based on the
1585
- // value of each item. This function is short, sweet, and good enough™ (I also
1586
- // don't know how it works, tbqh)
1587
- // https://stackoverflow.com/questions/6122571/simple-non-secure-hash-function-for-javascript
1588
- function makeHash(str) {
1589
- let hash = 0;
1590
- if (str.length === 0) {
1591
- return hash;
1592
- }
1593
- for (let i = 0; i < str.length; i++) {
1594
- const char = str.charCodeAt(i);
1595
- hash = (hash << 5) - hash + char;
1596
- hash = hash & hash;
1597
- }
1598
- return hash;
1599
- }
1600
-
1601
- const ComboboxOption = /*#__PURE__*/react.forwardRef(function ComboboxOption({
1602
- children,
1603
- id: idProp,
1604
- value: valueProp,
1605
- text: textProp,
1606
- onClick,
1607
- as: Comp = 'li',
1608
- innerAs,
1609
- ...props
1610
- }, ref) {
1611
- const {
1612
- onSelect,
1613
- data: {
1614
- navigationItem
1615
- },
1616
- transition,
1617
- optionsRef
1618
- } = useComboBoxContext();
1619
- const transitionCleanupRef = react.useRef(transition);
1620
- const isActiveRef = react.useRef(false);
1621
- const value = valueProp;
1622
- let text = textProp ? textProp : '';
1623
- if (text.length === 0) {
1624
- if (typeof valueProp === 'string' && valueProp.length > 0) {
1625
- text = valueProp;
1626
- } else {
1627
- throw new Error('Missing text for <ComboboxOption />');
1628
- }
1629
- }
1630
- const id = String(makeHash(idProp));
1631
- react.useEffect(() => {
1632
- const opts = optionsRef.current;
1633
- opts[id] = {
1634
- value,
1635
- text
1636
- };
1637
- return () => {
1638
- delete opts[id];
1639
- };
1640
- }, [optionsRef, id, text, value]);
1641
-
1642
- // Keep updating this ref with the current
1643
- // function pointer for transition, so it can
1644
- // be used by the unmount effect below.
1645
- transitionCleanupRef.current = transition;
1646
- isActiveRef.current = navigationItem === id;
1647
- react.useEffect(() => {
1648
- return () => {
1649
- if (isActiveRef.current === true) {
1650
- // clean up selections if this option is getting
1651
- // unmounted and it was the currently selected item
1652
- transitionCleanupRef.current(CLEAR_SELECTION);
1653
- }
1654
- };
1655
- }, []);
1656
- const handleClick = () => {
1657
- onSelect && onSelect(text, id, value);
1658
- transition(SELECT_WITH_CLICK, {
1659
- text,
1660
- item: id
1661
- });
1662
- };
1663
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1664
- ...props,
1665
- as: innerAs,
1666
- "data-reach-combobox-option": "",
1667
- ref: ref,
1668
- id: id,
1669
- role: "option",
1670
- "aria-selected": isActiveRef.current,
1671
- "data-highlighted": isActiveRef.current ? '' : undefined
1672
- // without this the menu will close from `onBlur`, but with it the
1673
- // element can be `document.activeElement` and then our focus checks in
1674
- // onBlur will work as intended
1675
- ,
1676
- tabIndex: "-1",
1677
- onClick: wrapEvent(onClick, handleClick),
1678
- children: children || text
1679
- });
1680
- });
1681
-
1682
- const ComboboxPopover = /*#__PURE__*/react.forwardRef(function ComboboxPopover({
1683
- // wrapped events
1684
- onKeyDown,
1685
- onBlur,
1686
- as: Comp = 'div',
1687
- innerAs,
1688
- ...props
1689
- }, forwardedRef) {
1690
- const {
1691
- popoverRef,
1692
- isVisible
1693
- } = useComboBoxContext();
1694
- const handleKeyDown = useKeyDown();
1695
- const handleBlur = useBlur();
1696
-
1697
- // Instead of conditionally rendering the popover we use the `hidden` prop
1698
- // because we don't want to unmount on close (from escape or onSelect). If
1699
- // we unmounted, then we'd lose the optionsRef and the user wouldn't be able
1700
- // to use the arrow keys to pop the list back open. However, the developer
1701
- // can conditionally render the ComboboxPopover if they do want to cause
1702
- // mount/unmount based on the app's own data (like results.length or
1703
- // whatever).
1704
- const hidden = !isVisible;
1705
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1706
- ...props,
1707
- as: innerAs,
1708
- "data-reach-combobox-popover": "",
1709
- ref: assignMultipleRefs(popoverRef, forwardedRef),
1710
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
1711
- onBlur: wrapEvent(onBlur, handleBlur),
1712
- hidden: hidden
1713
- // Allow the user to click empty space inside the popover without causing
1714
- // to close from useBlur
1715
- ,
1716
- tabIndex: "-1"
1717
- });
1718
- });
1719
-
1720
- const focusLockStack = [];
1721
- function useFocusLock(ref, opts) {
1722
- const {
1723
- enabled = true,
1724
- lockStartRef,
1725
- lockEndRef
1726
- } = opts;
1727
- react.useEffect(() => {
1728
- const rootEl = ref.current;
1729
- if (enabled && rootEl) {
1730
- focusLockStack.push(rootEl);
1731
- const listener = event => {
1732
- const isActiveFocusLock = focusLockStack[focusLockStack.length - 1] === rootEl;
1733
- if (!isActiveFocusLock) {
1734
- // Not the currently focused lock. Forget about it.
1735
- return;
1736
- }
1737
- if (event.target === lockEndRef.current) {
1738
- focusOnChildNode(rootEl, 0);
1739
- } else if (event.target === lockStartRef.current) {
1740
- focusOnChildNode(rootEl, -1);
1741
- } else if (document !== event.target && rootEl !== event.target && !rootEl.contains(event.target)) {
1742
- event.preventDefault();
1743
- focusOnChildNode(rootEl, 0);
1744
- }
1745
- };
1746
- document.addEventListener('focusin', listener);
1747
- return () => {
1748
- document.removeEventListener('focusin', listener);
1749
- focusLockStack.pop();
1750
- };
1751
- }
1752
- // eslint-disable-next-line react-hooks/exhaustive-deps
1753
- }, [enabled]);
1754
- }
1755
-
1756
- const FocusLock = function FocusLock(props) {
1757
- const {
1758
- as: Comp = 'div',
1759
- childRef,
1760
- enabled = false,
1761
- style = {},
1762
- children,
1763
- ...otherProps
1764
- } = props;
1765
- const lockStartRef = react.useRef(null);
1766
- const lockEndRef = react.useRef(null);
1767
- useFocusLock(childRef, {
1768
- enabled,
1769
- lockStartRef,
1770
- lockEndRef
1771
- });
1772
- const lockStyle = {
1773
- width: 1,
1774
- height: 0,
1775
- padding: 0,
1776
- overflow: 'hidden',
1777
- position: 'fixed',
1778
- top: 1,
1779
- left: 1,
1780
- ...style
1781
- };
1782
- return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
1783
- children: [/*#__PURE__*/jsxRuntime.jsx(Comp, {
1784
- ref: lockStartRef,
1785
- "data-focus-lock-start": "",
1786
- "aria-hidden": true,
1787
- tabIndex: 0,
1788
- style: lockStyle,
1789
- ...otherProps
1790
- }), children, /*#__PURE__*/jsxRuntime.jsx(Comp, {
1791
- ref: lockEndRef,
1792
- "data-focus-lock-end": "",
1793
- "aria-hidden": true,
1794
- tabIndex: 0,
1795
- style: lockStyle,
1796
- ...otherProps
1797
- })]
1798
- });
1799
- };
1800
-
1801
- // MenuRoot
1802
-
1803
- const menuContext = /*#__PURE__*/react.createContext(null);
1804
- const {
1805
- Provider: MenuProvider
1806
- } = menuContext;
1807
- const useMenuContext = () => react.useContext(menuContext);
1808
-
1809
- // MenuList
1810
-
1811
- const menuListContext = /*#__PURE__*/react.createContext(null);
1812
- const MenuListProvider = menuListContext.Provider;
1813
- const useMenuListContext = () => react.useContext(menuListContext);
1814
-
1815
- const Menu = /*#__PURE__*/react.forwardRef(function Menu(props, forwardedRef) {
1816
- const {
1817
- as: Comp = react.Fragment,
1818
- innerAs,
1819
- open: openProp,
1820
- defaultOpen = false,
1821
- onChange: onChangeProp,
1822
- ...otherProps
1823
- } = props;
1824
- const menuListIdRef = react.useRef(undefined);
1825
- const openWithArrowKeyRef = react.useRef(null);
1826
- const buttonRef = react.useRef(null);
1827
- const [open, onChange] = useControlledState(openProp, onChangeProp, defaultOpen, setState => (e, isOpen) => {
1828
- setState(isOpen);
1829
- });
1830
- const [offsetFn, setOffsetFn] = react.useState(undefined);
1831
- menuListIdRef.current = react.useId();
1832
- const isContextMenu = react.useRef(false);
1833
- if (!open && offsetFn) {
1834
- setOffsetFn(undefined);
1835
- }
1836
- const value = {
1837
- menuListIdRef,
1838
- openWithArrowKeyRef,
1839
- open,
1840
- onChange,
1841
- buttonRef,
1842
- offsetFn,
1843
- setOffsetFn,
1844
- isContextMenu
1845
- };
1846
- return /*#__PURE__*/jsxRuntime.jsx(MenuProvider, {
1847
- value: value,
1848
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
1849
- ...(Comp !== react.Fragment ? {
1850
- as: innerAs,
1851
- ref: forwardedRef
1852
- } : {}),
1853
- ...otherProps
1854
- })
1855
- });
1856
- });
1857
-
1858
- const MenuButton = /*#__PURE__*/react.forwardRef(function MenuButton(props, forwardedRef) {
1859
- const {
1860
- as: Comp = 'button',
1861
- innerAs,
1862
- id: preferredId,
1863
- onClick,
1864
- onKeyDown,
1865
- disabled,
1866
- ...otherProps
1867
- } = props;
1868
- const {
1869
- menuListIdRef,
1870
- openWithArrowKeyRef,
1871
- open,
1872
- buttonRef,
1873
- onChange
1874
- } = useMenuContext();
1875
- const buttonIdGenerated = react.useId();
1876
- const buttonId = preferredId || buttonIdGenerated;
1877
- const handleKeyDown = e => {
1878
- if (disabled) {
1879
- return;
1880
- }
1881
- buttonRef.current = e.currentTarget;
1882
- const isArrowKey = () => ['ArrowUp', 'ArrowDown'].includes(e.key);
1883
- const isEnterKey = () => [' ', 'Enter'].includes(e.key);
1884
- const openedWithArrow = isArrowKey();
1885
- if (openedWithArrow || isEnterKey()) {
1886
- if (openedWithArrow) {
1887
- // Used to make it open at the end or begining of the list
1888
- openWithArrowKeyRef.current = e.key;
1889
- }
1890
- onChange(e, true);
1891
- e.preventDefault();
1892
- }
1893
- };
1894
- const handleClick = e => {
1895
- if (disabled) {
1896
- return;
1897
- }
1898
- buttonRef.current = e.currentTarget;
1899
- onChange(e, !open);
1900
- };
1901
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1902
- ref: forwardedRef,
1903
- as: innerAs,
1904
- id: buttonId,
1905
- role: "button",
1906
- type: "button",
1907
- "aria-haspopup": true,
1908
- "aria-controls": menuListIdRef.current,
1909
- "aria-expanded": open ? true : undefined,
1910
- "data-menu-button": "",
1911
- onClick: wrapEvent(onClick, handleClick),
1912
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
1913
- disabled: disabled,
1914
- ...otherProps
1915
- });
1916
- });
1917
-
1918
- const ContextMenuTrigger = /*#__PURE__*/react.forwardRef(function ContextMenuTrigger(props, forwardedRef) {
1919
- const {
1920
- as: Comp = 'div',
1921
- innerAs,
1922
- id: preferredId,
1923
- onContextMenu,
1924
- disabled,
1925
- ...otherProps
1926
- } = props;
1927
- const {
1928
- menuListIdRef,
1929
- open,
1930
- buttonRef,
1931
- onChange,
1932
- setOffsetFn,
1933
- isContextMenu
1934
- } = useMenuContext();
1935
- const idGenerated = react.useId();
1936
- const id = preferredId || idGenerated;
1937
- const handleContextMenu = e => {
1938
- if (disabled) {
1939
- return;
1940
- }
1941
- const rect = e.currentTarget.getBoundingClientRect();
1942
- isContextMenu.current = true;
1943
- buttonRef.current = e.currentTarget;
1944
- onChange(e, true);
1945
- const mouseX = e.nativeEvent.x;
1946
- const mouseY = e.nativeEvent.y;
1947
- setOffsetFn(() => ({
1948
- reference
1949
- }) => [mouseX - rect.x, mouseY - rect.y - reference.height]);
1950
- e.preventDefault();
1951
- };
1952
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
1953
- ref: forwardedRef,
1954
- as: innerAs,
1955
- id: id,
1956
- "aria-haspopup": true,
1957
- "aria-controls": menuListIdRef.current,
1958
- "aria-expanded": open ? true : undefined,
1959
- "data-menu-trigger": "",
1960
- onContextMenu: wrapEvent(onContextMenu, handleContextMenu),
1961
- disabled: disabled,
1962
- ...otherProps
1963
- });
1964
- });
1965
-
1966
- const MenuItem = /*#__PURE__*/react.forwardRef(function MenuItem(props, forwardedRef) {
1967
- const {
1968
- as: Comp = 'li',
1969
- innerAs,
1970
- disabled,
1971
- onSelect,
1972
- onClick,
1973
- onKeyDown,
1974
- ...otherProps
1975
- } = props;
1976
- const {
1977
- onChange,
1978
- buttonRef
1979
- } = useMenuContext();
1980
- const {
1981
- navigationItem,
1982
- onNavigate
1983
- } = useMenuListContext();
1984
- const ref = react.useRef(null);
1985
- const itemId = react.useId();
1986
- const isActive = ref.current && ref.current === navigationItem;
1987
- const handleSelect = wrapEvent(onSelect, e => {
1988
- onChange(e, false);
1989
- buttonRef.current?.focus();
1990
- e.preventDefault();
1991
- });
1992
- const handleClick = e => {
1993
- if (!disabled) {
1994
- onNavigate && onNavigate(ref.current);
1995
- handleSelect(e);
1996
- }
1997
- };
1998
- const handleKeyDown = e => {
1999
- switch (e.key) {
2000
- case ' ':
2001
- case 'Enter':
2002
- if (!disabled) {
2003
- handleSelect(e);
2004
- }
2005
- break;
2006
- }
2007
- };
2008
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
2009
- ref: assignMultipleRefs(ref, forwardedRef),
2010
- as: innerAs,
2011
- id: disabled ? undefined : itemId,
2012
- "data-menu-item": "",
2013
- "data-highlighted": isActive ? '' : undefined,
2014
- role: "menuitem",
2015
- onClick: wrapEvent(onClick, handleClick),
2016
- tabIndex: disabled ? -1 : 0,
2017
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
2018
- "data-disabled": disabled ? '' : undefined,
2019
- "aria-disabled": disabled ? '' : undefined,
2020
- disabled: disabled,
2021
- ...otherProps
2022
- });
2023
- });
2024
-
2025
- function queryScope(type, props) {
2026
- return props['data-menu-item'] === '' && props['data-disabled'] !== '' && props['data-disabled'] !== true;
2027
- }
2028
-
2029
- const useEnhancedEffect$1 = typeof window !== 'undefined' ? react.useLayoutEffect : react.useEffect;
2030
- const MenuList = /*#__PURE__*/react.forwardRef(function MenuList(props, forwardedRef) {
2031
- const {
2032
- as: Comp = 'ul',
2033
- innerAs,
2034
- onKeyDown,
2035
- id: preferredId,
2036
- defaultActiveItemValue,
2037
- ...otherProps
2038
- } = props;
2039
- const interactedOutside = react.useRef(false);
2040
- const itemSearchStr = react.useRef('');
2041
- const itemSearchClearTimeout = react.useRef(undefined);
2042
- const {
2043
- menuListIdRef,
2044
- buttonRef,
2045
- onChange,
2046
- openWithArrowKeyRef,
2047
- open,
2048
- isContextMenu
2049
- } = useMenuContext();
2050
- const [navigationItem, setNavigationItem] = react.useState();
2051
- const [mounted, setMounted] = react.useState(false);
2052
- const menuListRef = react.useRef(null);
2053
- const scope = useScope(menuListRef);
2054
- const onNavigate = el => {
2055
- el.focus({
2056
- preventScroll: true
2057
- });
2058
- setNavigationItem(el);
2059
- };
2060
- menuListIdRef.current = preferredId || menuListIdRef.current;
2061
- useEnhancedEffect$1(() => {
2062
- if (!mounted) {
2063
- const allItems = scope.current.queryAllNodes(queryScope);
2064
- let index = getCircularIndex(openWithArrowKeyRef.current === 'ArrowUp' ? -1 : 0, allItems.length);
2065
- if (defaultActiveItemValue) {
2066
- if (typeof defaultActiveItemValue === 'string') {
2067
- for (let i = 0; i < allItems.length; i++) {
2068
- if (allItems[i].dataset.value === defaultActiveItemValue) {
2069
- index = i;
2070
- break;
2071
- }
2072
- }
2073
- } else if (Array.isArray(defaultActiveItemValue)) {
2074
- const set = new Set(defaultActiveItemValue);
2075
- for (let i = 0; i < allItems.length; i++) {
2076
- const val = allItems[i].dataset.value;
2077
- if (val && set.has(val)) {
2078
- index = i;
2079
- break;
2080
- }
2081
- }
2082
- }
2083
- }
2084
- if (index !== null) {
2085
- onNavigate && onNavigate(allItems[index]);
2086
- }
2087
- }
2088
- openWithArrowKeyRef.current = null;
2089
- setMounted(true);
2090
- }, [mounted, navigationItem, onNavigate, openWithArrowKeyRef, scope, defaultActiveItemValue]);
2091
- const handleClickOutside = react.useCallback(e => {
2092
- if (isContextMenu.current) {
2093
- if (!interactedOutside.current && e.pointerType === 'touch') {
2094
- // First interaction should be ignored, because
2095
- // this is what triggered the context menu to open
2096
- interactedOutside.current = true;
2097
- return;
2098
- }
2099
- if (e.button === 0) {
2100
- onChange(e, false);
2101
- }
2102
- } else {
2103
- if (e.target instanceof HTMLElement && e.target !== buttonRef.current && !buttonRef.current?.contains(e.target)) {
2104
- onChange(e, false);
2105
- }
2106
- }
2107
- e.preventDefault();
2108
- }, [buttonRef, isContextMenu, onChange]);
2109
- useOnClickOutside(menuListRef, handleClickOutside, open);
2110
- function handleKeyDown(e) {
2111
- switch (e.key) {
2112
- case 'Escape':
2113
- case 'Tab':
2114
- {
2115
- onChange(e, false);
2116
- e.preventDefault(); // prevents focusing on next element, because we will be handling it
2117
- itemSearchStr.current = '';
2118
- buttonRef.current?.focus();
2119
- break;
2120
- }
2121
- case 'Home':
2122
- case 'End':
2123
- case 'ArrowDown':
2124
- case 'ArrowUp':
2125
- e.preventDefault();
2126
- itemSearchStr.current = '';
2127
- const allItems = scope ? scope.current.queryAllNodes(queryScope) : [];
2128
- const currentIndex = allItems.findIndex(e => e === navigationItem);
2129
- if (allItems.length === 0) {
2130
- return;
2131
- }
2132
- let nextIndex = currentIndex;
2133
- switch (e.key) {
2134
- case 'ArrowUp':
2135
- nextIndex += -1;
2136
- break;
2137
- case 'ArrowDown':
2138
- nextIndex += 1;
2139
- break;
2140
- case 'Home':
2141
- nextIndex = 0;
2142
- break;
2143
- case 'End':
2144
- nextIndex = -1;
2145
- break;
2146
- }
2147
- // We already checked if allItems.length = 0 above
2148
- nextIndex = getCircularIndex(nextIndex, allItems.length);
2149
- onNavigate && onNavigate(allItems[nextIndex]);
2150
- break;
2151
- default:
2152
- {
2153
- if (e.key.length === 1 && !e.ctrlKey && !e.altKey) {
2154
- // A-Z
2155
- e.preventDefault();
2156
- if (itemSearchStr.current.length === 0 || itemSearchStr.current.slice(-1) !== e.key) {
2157
- itemSearchStr.current = itemSearchStr.current + e.key;
2158
- }
2159
- clearTimeout(itemSearchClearTimeout.current);
2160
- itemSearchClearTimeout.current = setTimeout(() => {
2161
- itemSearchStr.current = '';
2162
- }, 500);
2163
- const allItems = scope ? scope.current.queryAllNodes(queryScope) : [];
2164
- const currentIndex = allItems.findIndex(e => e === navigationItem);
2165
- const searchStr = itemSearchStr.current;
2166
- let nextIndex = -1;
2167
- for (let i = searchStr.length === 1 ? 1 : 0; i < allItems.length; i++) {
2168
- const idx = getCircularIndex(currentIndex + i, allItems.length);
2169
- const dom = allItems[idx];
2170
- const domText = dom.innerText.toLowerCase();
2171
- if (domText.length > 0 && domText.startsWith(searchStr)) {
2172
- nextIndex = idx;
2173
- break;
2174
- }
2175
- }
2176
- if (nextIndex >= 0 && nextIndex < allItems.length) {
2177
- onNavigate && onNavigate(allItems[nextIndex]);
2178
- }
2179
- }
2180
- }
2181
- }
2182
- }
2183
- if (!open) {
2184
- interactedOutside.current = false;
2185
- return null;
2186
- }
2187
- return /*#__PURE__*/jsxRuntime.jsx(MenuListProvider, {
2188
- value: {
2189
- navigationItem,
2190
- onNavigate
2191
- },
2192
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
2193
- ref: assignMultipleRefs(forwardedRef, menuListRef),
2194
- as: innerAs,
2195
- id: menuListIdRef.current,
2196
- role: "menu",
2197
- "aria-labelledby": buttonRef.current?.id,
2198
- "data-menu-list": "",
2199
- tabIndex: "-1",
2200
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
2201
- ...otherProps
2202
- })
2203
- });
2204
- });
2205
-
2206
- const context = /*#__PURE__*/react.createContext(null);
2207
- const PopperProvider = context.Provider;
2208
- const usePopperContext = () => react.useContext(context);
2209
-
2210
- const PortalSelectorContext = /*#__PURE__*/react.createContext(null);
2211
- PortalSelectorContext.displayName = 'PortalSelectorContext';
2212
- const PortalSelectorProvider = ({
2213
- selector,
2214
- children
2215
- }) => {
2216
- return /*#__PURE__*/jsxRuntime.jsx(PortalSelectorContext.Provider, {
2217
- value: selector,
2218
- children: children
2219
- });
2220
- };
2221
-
2222
- const Portal = ({
2223
- children,
2224
- selector: selectorProp
2225
- }) => {
2226
- const selectorCtx = react.useContext(PortalSelectorContext);
2227
- if (typeof window === 'undefined') {
2228
- return null;
2229
- }
2230
- const selector = selectorProp || selectorCtx || 'body';
2231
- const dom = typeof selector === 'string' ? document.querySelector(selector) : selector();
2232
- if (dom) {
2233
- return /*#__PURE__*/reactDom.createPortal(/*#__PURE__*/jsxRuntime.jsx("div", {
2234
- "data-portal": "",
2235
- children: children
2236
- }), dom);
2237
- }
2238
- return null;
2239
- };
2240
-
2241
- const useEnhancedEffect = typeof window !== 'undefined' ? react.useLayoutEffect : react.useEffect;
2242
- const emptyModifiers = [];
2243
- const Popper = /*#__PURE__*/react.memo(/*#__PURE__*/react.forwardRef(function Popper({
2244
- placement = 'bottom',
2245
- strategy = 'absolute',
2246
- as: Comp = 'div',
2247
- innerAs,
2248
- anchorEl,
2249
- children,
2250
- modifiers = emptyModifiers,
2251
- usePortal = false,
2252
- style = {},
2253
- portalSelector,
2254
- distance = 0,
2255
- skidding = 0,
2256
- arrowPadding = 5,
2257
- offsetFn,
2258
- ...props
2259
- }, forwardedRef) {
2260
- const arrowRef = react.useRef(null);
2261
- const popperRef = react.useRef(null);
2262
- const popperEngineInstance = react.useRef(null);
2263
- const defaultModifiers = react.useMemo(() => {
2264
- const arrowModifier = {
2265
- name: 'arrow',
2266
- options: {
2267
- element: '[data-popper-arrow]',
2268
- padding: arrowPadding
2269
- }
2270
- };
2271
- const offsetModifier = {
2272
- name: 'offset',
2273
- options: {
2274
- offset: offsetFn ?? [skidding, distance]
2275
- }
2276
- };
2277
- return [arrowModifier, offsetModifier];
2278
- }, [arrowPadding, distance, skidding, offsetFn]);
2279
- useEnhancedEffect(() => {
2280
- if (anchorEl && anchorEl.current && popperRef.current) {
2281
- popperEngineInstance.current = core.createPopper(anchorEl.current, popperRef.current, {
2282
- placement,
2283
- strategy,
2284
- modifiers: [...defaultModifiers, ...modifiers]
2285
- });
2286
- }
2287
- return () => {
2288
- popperEngineInstance.current && popperEngineInstance.current.destroy();
2289
- popperEngineInstance.current = null;
2290
- };
2291
- }, [anchorEl, modifiers, placement, strategy, defaultModifiers]);
2292
- useEnhancedEffect(() => {
2293
- popperEngineInstance.current?.forceUpdate();
2294
- }, [props.hidden || props['aria-hidden']]);
2295
- const contextValue = {
2296
- arrowRef
2297
- };
2298
- const ret = /*#__PURE__*/jsxRuntime.jsx(PopperProvider, {
2299
- value: contextValue,
2300
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
2301
- ...props,
2302
- as: innerAs,
2303
- ref: assignMultipleRefs(popperRef, forwardedRef),
2304
- style: {
2305
- position: 'fixed',
2306
- left: -5000,
2307
- top: -5000,
2308
- ...style
2309
- },
2310
- children: children
2311
- })
2312
- });
2313
- if (usePortal) {
2314
- return /*#__PURE__*/jsxRuntime.jsx(Portal, {
2315
- selector: portalSelector,
2316
- children: ret
2317
- });
2318
- }
2319
- return ret;
2320
- }));
2321
-
2322
- const PopperArrow = /*#__PURE__*/react.forwardRef(function PopperArrow({
2323
- as: Comp = 'div',
2324
- ...props
2325
- }, ref) {
2326
- const ctx = usePopperContext();
2327
- if (ctx === null) {
2328
- return null;
2329
- }
2330
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
2331
- ...props,
2332
- ref: node => {
2333
- if (node && ctx.arrowRef.current && ctx.arrowRef.current !== node) {
2334
- throw new Error('You can only render one <PopperArrow /> per <Popper> component');
2335
- }
2336
- assignMultipleRefs(ref, ctx.arrowRef)(node);
2337
- },
2338
- "data-popper-arrow": ""
2339
- });
2340
- });
2341
-
2342
- const MenuPopover = /*#__PURE__*/react.forwardRef(function MenuPopover(props, forwardedRef) {
2343
- const {
2344
- as = 'div',
2345
- innerAs,
2346
- ...otherProps
2347
- } = props;
2348
- const {
2349
- buttonRef,
2350
- open,
2351
- offsetFn,
2352
- isContextMenu
2353
- } = useMenuContext();
2354
- if (!open) {
2355
- return null;
2356
- }
2357
- return /*#__PURE__*/jsxRuntime.jsx(Popper, {
2358
- as: as,
2359
- innerAs: innerAs,
2360
- ref: forwardedRef,
2361
- anchorEl: buttonRef,
2362
- offsetFn: offsetFn,
2363
- placement: isContextMenu.current ? 'bottom-start' : undefined,
2364
- ...otherProps
2365
- });
2366
- });
2367
-
2368
- const Modal = /*#__PURE__*/react.forwardRef(({
2369
- as: Comp = 'div',
2370
- children,
2371
- trapFocus = true,
2372
- style = {},
2373
- ...otherProps
2374
- }, ref) => {
2375
- const modalRef = react.useRef(null);
2376
- useFocusReturn(trapFocus, modalRef);
2377
- useRemoveBodyScroll(trapFocus, modalRef);
2378
- useAutoFocus(trapFocus, modalRef);
2379
- return /*#__PURE__*/jsxRuntime.jsx(FocusLock, {
2380
- childRef: modalRef,
2381
- enabled: trapFocus,
2382
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
2383
- ref: assignMultipleRefs(ref, modalRef),
2384
- "data-modal-container": "",
2385
- role: "dialog",
2386
- "aria-modal": "true",
2387
- style: style,
2388
- tabIndex: -1,
2389
- ...otherProps,
2390
- children: children
2391
- })
2392
- });
2393
- });
2394
-
2395
- const ModalBackdrop = /*#__PURE__*/react.forwardRef(({
2396
- as: Comp = 'div',
2397
- onClose,
2398
- onClick,
2399
- onMouseDown,
2400
- onKeyDown,
2401
- disableCloseOnClick = false,
2402
- disableEscapeKeyDown = false,
2403
- ...otherProps
2404
- }, forwardedRef) => {
2405
- const ref = react.useRef(null);
2406
- const mouseDownTargetRef = react.useRef(null);
2407
- const handleClick = e => {
2408
- // Ignore the events not coming from the "backdrop"
2409
- // We don't want to close the dialog when clicking the dialog content.
2410
- if (e.target !== e.currentTarget) {
2411
- return;
2412
- }
2413
-
2414
- // Make sure the event starts and ends on the same DOM element.
2415
- if (e.target !== mouseDownTargetRef.current) {
2416
- return;
2417
- }
2418
- mouseDownTargetRef.current = null;
2419
- !disableCloseOnClick && onClose?.();
2420
- e.stopPropagation();
2421
- };
2422
- const handleMouseDown = e => {
2423
- mouseDownTargetRef.current = e.target;
2424
- };
2425
- const handleKeyDown = e => {
2426
- if (e.key === 'Escape') {
2427
- !disableEscapeKeyDown && onClose?.();
2428
- e.stopPropagation();
2429
- }
2430
- };
2431
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
2432
- ref: assignMultipleRefs(ref, forwardedRef),
2433
- "data-modal-root": "",
2434
- onClick: wrapEvent(onClick, handleClick),
2435
- onMouseDown: wrapEvent(onMouseDown, handleMouseDown),
2436
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
2437
- ...otherProps
2438
- });
2439
- });
2440
-
2441
- // RadioGroup Component
2442
-
2443
- const RadioGroupContext = /*#__PURE__*/react.createContext(undefined);
2444
- const {
2445
- Provider: RadioGroupProvider
2446
- } = RadioGroupContext;
2447
- const useRadioGroupContext = () => react.useContext(RadioGroupContext);
2448
-
2449
- const RadioButton = /*#__PURE__*/react.forwardRef(function RadioButton(props, forwardedRef) {
2450
- const {
2451
- as: Comp = 'input',
2452
- value: valueProp,
2453
- onChange: onChangeProp,
2454
- checked: checkedProp,
2455
- name: nameProp,
2456
- ...otherProps
2457
- } = props;
2458
- const radioGroupContext = useRadioGroupContext();
2459
- const handleChange = e => {
2460
- radioGroupContext?.onChange?.(e, valueProp);
2461
- };
2462
- const checked = radioGroupContext ? radioGroupContext.value === valueProp : checkedProp;
2463
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
2464
- ref: forwardedRef,
2465
- type: "radio",
2466
- checked: checked,
2467
- "aria-checked": String(checked),
2468
- name: radioGroupContext ? radioGroupContext.name : nameProp,
2469
- onChange: wrapEvent(onChangeProp, handleChange),
2470
- value: valueProp,
2471
- ...otherProps
2472
- });
2473
- });
2474
-
2475
- const RadioGroup = /*#__PURE__*/react.forwardRef(function RadioGroup(props, forwardedRef) {
2476
- const {
2477
- as: Comp = 'div',
2478
- onChange: onChangeProp,
2479
- value: valueProp,
2480
- name: nameProp,
2481
- defaultValue,
2482
- ...otherProps
2483
- } = props;
2484
- const [value, onChange] = useControlledState(valueProp, onChangeProp, defaultValue, setValue => (e, value) => {
2485
- setValue(value);
2486
- });
2487
- const fallbackId = react.useId();
2488
- return /*#__PURE__*/jsxRuntime.jsx(RadioGroupProvider, {
2489
- value: {
2490
- value,
2491
- onChange,
2492
- name: nameProp || fallbackId
2493
- },
2494
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
2495
- ref: forwardedRef,
2496
- role: "radiogroup",
2497
- ...otherProps
2498
- })
2499
- });
2500
- });
2501
-
2502
- /* eslint-disable @typescript-eslint/ban-ts-comment */
2503
- const noop = () => {
2504
- /* noop */
2505
- };
2506
- const [SliderProvider, useSliderContext] = createContext('Slider');
2507
-
2508
- ////////////////////////////////////////////////////////////////////////////////
2509
-
2510
- /**
2511
- * Slider
2512
- *
2513
- * @see Docs https://reach.tech/slider#slider
2514
- */
2515
- const Slider = /*#__PURE__*/react.forwardRef(function Slider({
2516
- children,
2517
- ...props
2518
- }, forwardedRef) {
2519
- return /*#__PURE__*/jsxRuntime.jsx(SliderInput, {
2520
- ...props,
2521
- ref: forwardedRef,
2522
- "data-reach-slider": "",
2523
- children: /*#__PURE__*/jsxRuntime.jsxs(SliderTrack, {
2524
- children: [/*#__PURE__*/jsxRuntime.jsx(SliderRange, {}), /*#__PURE__*/jsxRuntime.jsx(SliderHandle, {}), children]
2525
- })
2526
- });
2527
- });
2528
-
2529
- /**
2530
- * @see Docs https://reach.tech/slider#slider-props
2531
- */
2532
-
2533
- ////////////////////////////////////////////////////////////////////////////////
2534
-
2535
- /**
2536
- * SliderInput
2537
- *
2538
- * The parent component of the slider interface. This is a lower level component
2539
- * if you need more control over styles or rendering the slider's inner
2540
- * components.
2541
- *
2542
- * @see Docs https://reach.tech/slider#sliderinput
2543
- */
2544
- const SliderInput = /*#__PURE__*/react.forwardRef(function SliderInput({
2545
- 'aria-label': ariaLabel,
2546
- 'aria-labelledby': ariaLabelledBy,
2547
- 'aria-valuetext': ariaValueTextProp,
2548
- as: Comp = 'div',
2549
- innerAs,
2550
- defaultValue,
2551
- disabled = false,
2552
- value: controlledValue,
2553
- getAriaLabel,
2554
- getAriaValueText,
2555
- handleAlignment = 'center',
2556
- max = 100,
2557
- min = 0,
2558
- name,
2559
- onChange: onChangeProp,
2560
- onKeyDown,
2561
- onMouseDown,
2562
- onMouseMove,
2563
- onMouseUp,
2564
- onPointerDown,
2565
- onPointerUp,
2566
- onTouchEnd,
2567
- onTouchMove,
2568
- onTouchStart,
2569
- orientation = 'horizontal',
2570
- step = 1,
2571
- children,
2572
- ...rest
2573
- }, forwardedRef) {
2574
- const touchId = react.useRef(undefined);
2575
- const fallbackId = react.useId();
2576
- const id = rest.id || fallbackId;
2577
-
2578
- // Track whether or not the pointer is down without updating the component
2579
- const pointerDownRef = react.useRef(false);
2580
- const trackRef = react.useRef(null);
2581
- const handleRef = react.useRef(null);
2582
- const sliderRef = react.useRef(null);
2583
- const [hasFocus, setHasFocus] = react.useState(false);
2584
- const {
2585
- ref: x,
2586
- ...handleDimensions
2587
- } = useDimensions(handleRef);
2588
- const [_value, onChange] = useControlledState(controlledValue, onChangeProp, defaultValue || min, setValue => (e, v) => setValue(v));
2589
- const value = clamp$1(_value, min, max);
2590
- const trackPercent = valueToPercent(value, min, max);
2591
- const isVertical = orientation === 'vertical';
2592
- const handleSize = isVertical ? handleDimensions.height : handleDimensions.width;
2593
-
2594
- // TODO: Consider removing the `handleAlignment` prop
2595
- // We may want to use accept a `handlePosition` prop instead and let apps
2596
- // define their own positioning logic, similar to how we do for popovers.
2597
- const handlePosition = `calc(${trackPercent}% - ${handleAlignment === 'center' ? `${handleSize}px / 2` : `${handleSize}px * ${trackPercent * 0.01}`})`;
2598
- const handlePositionRef = react.useRef(handlePosition);
2599
- react.useLayoutEffect(() => {
2600
- handlePositionRef.current = handlePosition;
2601
- }, [handlePosition]);
2602
- const getNewValueFromEvent = react.useCallback(event => {
2603
- return getNewValue(getPointerPosition(event, touchId), trackRef.current, {
2604
- step,
2605
- orientation,
2606
- min,
2607
- max
2608
- });
2609
- }, [max, min, orientation, step]);
2610
-
2611
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_kbd_interaction
2612
- const handleKeyDown = useStableLayoutCallback(event => {
2613
- if (disabled) {
2614
- return;
2615
- }
2616
- let newValue;
2617
- const tenSteps = (max - min) / 10;
2618
- const keyStep = step || (max - min) / 100;
2619
- switch (event.key) {
2620
- // Decrease the value of the slider by one step.
2621
- case 'ArrowLeft':
2622
- case 'ArrowDown':
2623
- newValue = value - keyStep;
2624
- break;
2625
- // Increase the value of the slider by one step
2626
- case 'ArrowRight':
2627
- case 'ArrowUp':
2628
- newValue = value + keyStep;
2629
- break;
2630
- // Decrement the slider by an amount larger than the step change made by
2631
- // `ArrowDown`.
2632
- case 'PageDown':
2633
- newValue = value - tenSteps;
2634
- break;
2635
- // Increment the slider by an amount larger than the step change made by
2636
- // `ArrowUp`.
2637
- case 'PageUp':
2638
- newValue = value + tenSteps;
2639
- break;
2640
- // Set the slider to the first allowed value in its range.
2641
- case 'Home':
2642
- newValue = min;
2643
- break;
2644
- // Set the slider to the last allowed value in its range.
2645
- case 'End':
2646
- newValue = max;
2647
- break;
2648
- default:
2649
- return;
2650
- }
2651
- newValue = clamp$1(step ? roundValueToStep(newValue, step, min) : newValue, min, max);
2652
- onChange(event, newValue);
2653
- });
2654
- const ariaValueText = getAriaValueText ? getAriaValueText(value) : ariaValueTextProp;
2655
- const rangeStyle = {
2656
- [isVertical ? 'height' : 'width']: `${trackPercent}%`
2657
- };
2658
-
2659
- // Slide events!
2660
- // We will try to use pointer events if they are supported to leverage
2661
- // setPointerCapture and releasePointerCapture. We'll fall back to separate
2662
- // mouse and touch events.
2663
- // TODO: This could be more concise
2664
- const removeMoveEvents = react.useRef(noop);
2665
- const removeStartEvents = react.useRef(noop);
2666
- const removeEndEvents = react.useRef(noop);
2667
-
2668
- // Store our event handlers in refs so we aren't attaching/detaching events
2669
- // on every render if the user doesn't useCallback
2670
- const appEvents = react.useRef({
2671
- onMouseMove,
2672
- onMouseDown,
2673
- onMouseUp,
2674
- onTouchStart,
2675
- onTouchEnd,
2676
- onTouchMove,
2677
- onPointerDown,
2678
- onPointerUp
2679
- });
2680
- react.useLayoutEffect(() => {
2681
- appEvents.current.onMouseMove = onMouseMove;
2682
- appEvents.current.onMouseDown = onMouseDown;
2683
- appEvents.current.onMouseUp = onMouseUp;
2684
- appEvents.current.onTouchStart = onTouchStart;
2685
- appEvents.current.onTouchEnd = onTouchEnd;
2686
- appEvents.current.onTouchMove = onTouchMove;
2687
- appEvents.current.onPointerDown = onPointerDown;
2688
- appEvents.current.onPointerUp = onPointerUp;
2689
- }, [onMouseMove, onMouseDown, onMouseUp, onTouchStart, onTouchEnd, onTouchMove, onPointerDown, onPointerUp]);
2690
- const handleSlideStart = useStableLayoutCallback(event => {
2691
- if (isRightClick(event)) return;
2692
- if (disabled) {
2693
- pointerDownRef.current = false;
2694
- return;
2695
- }
2696
- const ownerDocument = getOwnerDocument(sliderRef.current);
2697
- const ownerWindow = ownerDocument.defaultView || window;
2698
- pointerDownRef.current = true;
2699
- if (event.changedTouches) {
2700
- // Prevent scrolling for touch events
2701
- event.preventDefault();
2702
- const touch = event.changedTouches?.[0];
2703
- if (touch != null) {
2704
- touchId.current = touch.identifier;
2705
- }
2706
- }
2707
- const newValue = getNewValueFromEvent(event);
2708
- if (newValue == null) {
2709
- return;
2710
- }
2711
- ownerWindow.requestAnimationFrame(() => handleRef.current?.focus());
2712
- onChange(event, newValue);
2713
- removeMoveEvents.current = addMoveListener();
2714
- removeEndEvents.current = addEndListener();
2715
- });
2716
- const setPointerCapture = useStableLayoutCallback(event => {
2717
- if (isRightClick(event)) return;
2718
- if (disabled) {
2719
- pointerDownRef.current = false;
2720
- return;
2721
- }
2722
- pointerDownRef.current = true;
2723
- sliderRef.current?.setPointerCapture(event.pointerId);
2724
- });
2725
- const releasePointerCapture = useStableLayoutCallback(event => {
2726
- if (isRightClick(event)) return;
2727
- sliderRef.current?.releasePointerCapture(event.pointerId);
2728
- pointerDownRef.current = false;
2729
- });
2730
- const handlePointerMove = useStableLayoutCallback(event => {
2731
- if (disabled || !pointerDownRef.current) {
2732
- pointerDownRef.current = false;
2733
- return;
2734
- }
2735
- const newValue = getNewValueFromEvent(event);
2736
- if (newValue == null) {
2737
- return;
2738
- }
2739
- onChange?.(event, newValue);
2740
- });
2741
- const handleSlideStop = useStableLayoutCallback(event => {
2742
- if (isRightClick(event)) return;
2743
- pointerDownRef.current = false;
2744
- const newValue = getNewValueFromEvent(event);
2745
- if (newValue == null) {
2746
- return;
2747
- }
2748
- touchId.current = undefined;
2749
- removeMoveEvents.current();
2750
- removeEndEvents.current();
2751
- });
2752
- const addMoveListener = react.useCallback(() => {
2753
- const ownerDocument = getOwnerDocument(sliderRef.current);
2754
- const touchListener = wrapEvent(appEvents.current.onTouchMove, handlePointerMove);
2755
- const mouseListener = wrapEvent(appEvents.current.onMouseMove, handlePointerMove);
2756
- ownerDocument.addEventListener('touchmove', touchListener);
2757
- ownerDocument.addEventListener('mousemove', mouseListener);
2758
- return () => {
2759
- ownerDocument.removeEventListener('touchmove', touchListener);
2760
- ownerDocument.removeEventListener('mousemove', mouseListener);
2761
- };
2762
- }, [handlePointerMove]);
2763
- const addEndListener = react.useCallback(() => {
2764
- const ownerDocument = getOwnerDocument(sliderRef.current);
2765
- const ownerWindow = ownerDocument.defaultView || window;
2766
- const pointerListener = wrapEvent(appEvents.current.onPointerUp, releasePointerCapture);
2767
- const touchListener = wrapEvent(appEvents.current.onTouchEnd, handleSlideStop);
2768
- const mouseListener = wrapEvent(appEvents.current.onMouseUp, handleSlideStop);
2769
- if ('PointerEvent' in ownerWindow) {
2770
- ownerDocument.addEventListener('pointerup', pointerListener);
2771
- }
2772
- ownerDocument.addEventListener('touchend', touchListener);
2773
- ownerDocument.addEventListener('mouseup', mouseListener);
2774
- return () => {
2775
- if ('PointerEvent' in ownerWindow) {
2776
- ownerDocument.removeEventListener('pointerup', pointerListener);
2777
- }
2778
- ownerDocument.removeEventListener('touchend', touchListener);
2779
- ownerDocument.removeEventListener('mouseup', mouseListener);
2780
- };
2781
- }, [handleSlideStop, releasePointerCapture]);
2782
- const addStartListener = react.useCallback(() => {
2783
- // e.preventDefault is ignored by React's synthetic touchStart event, so
2784
- // we attach the listener directly to the DOM node
2785
- // https://github.com/facebook/react/issues/9809#issuecomment-413978405
2786
-
2787
- const sliderElement = sliderRef.current;
2788
- if (!sliderElement) {
2789
- return noop;
2790
- }
2791
- const ownerDocument = getOwnerDocument(sliderElement);
2792
- const ownerWindow = ownerDocument.defaultView || window;
2793
- const touchListener = wrapEvent(appEvents.current.onTouchStart, handleSlideStart);
2794
- const mouseListener = wrapEvent(appEvents.current.onMouseDown, handleSlideStart);
2795
- const pointerListener = wrapEvent(appEvents.current.onPointerDown, setPointerCapture);
2796
- sliderElement.addEventListener('touchstart', touchListener);
2797
- sliderElement.addEventListener('mousedown', mouseListener);
2798
- if ('PointerEvent' in ownerWindow) {
2799
- sliderElement.addEventListener('pointerdown', pointerListener);
2800
- }
2801
- return () => {
2802
- sliderElement.removeEventListener('touchstart', touchListener);
2803
- sliderElement.removeEventListener('mousedown', mouseListener);
2804
- if ('PointerEvent' in ownerWindow) {
2805
- sliderElement.removeEventListener('pointerdown', pointerListener);
2806
- }
2807
- };
2808
- }, [setPointerCapture, handleSlideStart]);
2809
- react.useEffect(() => {
2810
- removeStartEvents.current = addStartListener();
2811
- return () => {
2812
- removeStartEvents.current();
2813
- removeEndEvents.current();
2814
- removeMoveEvents.current();
2815
- };
2816
- }, [addStartListener]);
2817
- const inputFallbackId = react.useId();
2818
- const inputId = id || inputFallbackId;
2819
- return /*#__PURE__*/jsxRuntime.jsx(SliderProvider, {
2820
- ariaLabel: getAriaLabel ? getAriaLabel(value) : ariaLabel,
2821
- ariaLabelledBy: ariaLabelledBy,
2822
- ariaValueText: ariaValueText,
2823
- handleDimensions: handleDimensions,
2824
- handleKeyDown: handleKeyDown,
2825
- handlePosition: handlePosition,
2826
- handleRef: handleRef,
2827
- hasFocus: hasFocus,
2828
- onKeyDown: onKeyDown,
2829
- setHasFocus: setHasFocus,
2830
- sliderId: id,
2831
- sliderMax: max,
2832
- sliderMin: min,
2833
- value: value,
2834
- disabled: !!disabled,
2835
- isVertical: isVertical,
2836
- orientation: orientation,
2837
- trackPercent: trackPercent,
2838
- trackRef: trackRef,
2839
- rangeStyle: rangeStyle,
2840
- updateValue: onChange,
2841
- children: /*#__PURE__*/jsxRuntime.jsxs(Comp, {
2842
- ...rest,
2843
- // @ts-ignore
2844
- as: innerAs,
2845
- ref: assignMultipleRefs(sliderRef, forwardedRef),
2846
- "data-reach-slider-input": "",
2847
- "data-disabled": disabled ? '' : undefined,
2848
- "data-orientation": orientation,
2849
- tabIndex: -1,
2850
- children: [typeof children === 'function' ? children({
2851
- hasFocus,
2852
- id,
2853
- max,
2854
- min,
2855
- value,
2856
- ariaValueText
2857
- }) : children, name &&
2858
- /*#__PURE__*/
2859
- // If the slider is used in a form we'll need an input field to
2860
- // capture the value. We'll assume this when the component is given a
2861
- // form field name (A `name` prop doesn't really make sense in any
2862
- // other context).
2863
- jsxRuntime.jsx("input", {
2864
- type: "hidden",
2865
- value: value,
2866
- name: name,
2867
- id: inputId
2868
- })]
2869
- })
2870
- });
2871
- });
2872
-
2873
- /**
2874
- * @see Docs https://reach.tech/slider#sliderinput-props
2875
- */
2876
-
2877
- ////////////////////////////////////////////////////////////////////////////////
2878
-
2879
- /**
2880
- * SliderTrack
2881
- *
2882
- * @see Docs https://reach.tech/slider#slidertrack
2883
- */
2884
- const SliderTrackImpl = /*#__PURE__*/react.forwardRef(function SliderTrack({
2885
- as: Comp = 'div',
2886
- innerAs,
2887
- children,
2888
- style = {},
2889
- ...props
2890
- }, forwardedRef) {
2891
- const {
2892
- disabled,
2893
- orientation,
2894
- trackRef
2895
- } = useSliderContext('SliderTrack');
2896
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
2897
- ref: assignMultipleRefs(trackRef, forwardedRef)
2898
- // @ts-ignore
2899
- ,
2900
- as: innerAs,
2901
- style: {
2902
- ...style,
2903
- position: 'relative'
2904
- },
2905
- ...props,
2906
- "data-reach-slider-track": "",
2907
- "data-disabled": disabled ? '' : undefined,
2908
- "data-orientation": orientation,
2909
- children: children
2910
- });
2911
- });
2912
- const SliderTrack = /*#__PURE__*/react.memo(SliderTrackImpl);
2913
-
2914
- /**
2915
- * @see Docs https://reach.tech/slider#slidertrack-props
2916
- */
2917
-
2918
- ////////////////////////////////////////////////////////////////////////////////
2919
-
2920
- /**
2921
- * SliderRange
2922
- *
2923
- * The (typically) highlighted portion of the track that represents the space
2924
- * between the slider's `min` value and its current value.
2925
- *
2926
- * @see Docs https://reach.tech/slider#sliderrange
2927
- */
2928
- const SliderRangeImpl = /*#__PURE__*/react.forwardRef(function SliderRange({
2929
- as: Comp = 'div',
2930
- innerAs,
2931
- children,
2932
- style = {},
2933
- ...props
2934
- }, forwardedRef) {
2935
- const {
2936
- disabled,
2937
- orientation,
2938
- rangeStyle
2939
- } = useSliderContext('SliderRange');
2940
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
2941
- ref: forwardedRef
2942
- // @ts-ignore
2943
- ,
2944
- as: innerAs,
2945
- style: {
2946
- position: 'absolute',
2947
- ...rangeStyle,
2948
- ...style
2949
- },
2950
- ...props,
2951
- "data-reach-slider-range": "",
2952
- "data-disabled": disabled ? '' : undefined,
2953
- "data-orientation": orientation
2954
- });
2955
- });
2956
- const SliderRange = /*#__PURE__*/react.memo(SliderRangeImpl);
2957
-
2958
- /**
2959
- * `SliderRange` accepts any props that a HTML div component accepts.
2960
- * `SliderRange` will not accept or render any children.
2961
- *
2962
- * @see Docs https://reach.tech/slider#sliderrange-props
2963
- */
2964
-
2965
- ////////////////////////////////////////////////////////////////////////////////
2966
-
2967
- /**
2968
- * SliderHandle
2969
- *
2970
- * The handle that the user drags along the track to set the slider value.
2971
- *
2972
- * @see Docs https://reach.tech/slider#sliderhandle
2973
- */
2974
- const SliderHandleImpl = /*#__PURE__*/react.forwardRef(function SliderHandle({
2975
- // min,
2976
- // max,
2977
- as: Comp = 'div',
2978
- innerAs,
2979
- onBlur,
2980
- onFocus,
2981
- style = {},
2982
- onKeyDown,
2983
- ...props
2984
- }, forwardedRef) {
2985
- const {
2986
- ariaLabel,
2987
- ariaLabelledBy,
2988
- ariaValueText,
2989
- disabled,
2990
- handlePosition,
2991
- handleRef,
2992
- isVertical,
2993
- handleKeyDown,
2994
- orientation,
2995
- setHasFocus,
2996
- sliderMin,
2997
- sliderMax,
2998
- value
2999
- } = useSliderContext('SliderHandle');
3000
- return /*#__PURE__*/jsxRuntime.jsx(Comp
3001
- // @ts-ignore
3002
- , {
3003
- as: innerAs,
3004
- "aria-disabled": disabled || undefined
3005
- // If the slider has a visible label, it is referenced by
3006
- // `aria-labelledby` on the slider element. Otherwise, the slider
3007
- // element has a label provided by `aria-label`.
3008
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3009
- ,
3010
- "aria-label": ariaLabel,
3011
- "aria-labelledby": ariaLabel ? undefined : ariaLabelledBy
3012
- // If the slider is vertically oriented, it has `aria-orientation` set
3013
- // to vertical. The default value of `aria-orientation` for a slider is
3014
- // horizontal.
3015
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3016
- ,
3017
- "aria-orientation": orientation
3018
- // The slider element has the `aria-valuemax` property set to a decimal
3019
- // value representing the maximum allowed value of the slider.
3020
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3021
- ,
3022
- "aria-valuemax": sliderMax
3023
- // The slider element has the `aria-valuemin` property set to a decimal
3024
- // value representing the minimum allowed value of the slider.
3025
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3026
- ,
3027
- "aria-valuemin": sliderMin
3028
- // The slider element has the `aria-valuenow` property set to a decimal
3029
- // value representing the current value of the slider.
3030
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3031
- ,
3032
- "aria-valuenow": value
3033
- // If the value of `aria-valuenow` is not user-friendly, e.g., the day
3034
- // of the week is represented by a number, the `aria-valuetext` property
3035
- // is set to a string that makes the slider value understandable, e.g.,
3036
- // "Monday".
3037
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3038
- ,
3039
- "aria-valuetext": ariaValueText
3040
- // The element serving as the focusable slider control has role
3041
- // `slider`.
3042
- // https://www.w3.org/TR/wai-aria-practices-1.2/#slider_roles_states_props
3043
- ,
3044
- role: "slider",
3045
- tabIndex: disabled ? -1 : 0,
3046
- ...props,
3047
- "data-reach-slider-handle": "",
3048
- ref: assignMultipleRefs(handleRef, forwardedRef),
3049
- onBlur: wrapEvent(onBlur, () => {
3050
- setHasFocus(false);
3051
- }),
3052
- onFocus: wrapEvent(onFocus, () => {
3053
- setHasFocus(true);
3054
- }),
3055
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
3056
- style: {
3057
- position: 'absolute',
3058
- ...(isVertical ? {
3059
- bottom: handlePosition
3060
- } : {
3061
- left: handlePosition
3062
- }),
3063
- ...style
3064
- }
3065
- });
3066
- });
3067
- const SliderHandle = /*#__PURE__*/react.memo(SliderHandleImpl);
3068
-
3069
- /**
3070
- * `SliderRange` accepts any props that a HTML div component accepts.
3071
- *
3072
- * @see Docs https://reach.tech/slider#sliderhandle-props
3073
- */
3074
-
3075
- ////////////////////////////////////////////////////////////////////////////////
3076
-
3077
- /**
3078
- * SliderMarker
3079
- *
3080
- * A fixed value marker. These can be used to illustrate a range of steps or
3081
- * highlight important points along the slider track.
3082
- *
3083
- * @see Docs https://reach.tech/slider#slidermarker
3084
- */
3085
- const SliderMarkerImpl = /*#__PURE__*/react.forwardRef(function SliderMarker({
3086
- as: Comp = 'div',
3087
- innerAs,
3088
- children,
3089
- style = {},
3090
- value,
3091
- ...props
3092
- }, forwardedRef) {
3093
- const {
3094
- disabled,
3095
- isVertical,
3096
- orientation,
3097
- sliderMin,
3098
- sliderMax,
3099
- value: sliderValue
3100
- } = useSliderContext('SliderMarker');
3101
- const inRange = value >= sliderMin && value <= sliderMax;
3102
- const absoluteStartPosition = `${valueToPercent(value, sliderMin, sliderMax)}%`;
3103
- const state = value < sliderValue ? 'under-value' : value === sliderValue ? 'at-value' : 'over-value';
3104
- return inRange ? /*#__PURE__*/jsxRuntime.jsx(Comp, {
3105
- ref: forwardedRef
3106
- // @ts-ignore
3107
- ,
3108
- as: innerAs,
3109
- style: {
3110
- position: 'absolute',
3111
- ...(isVertical ? {
3112
- bottom: absoluteStartPosition
3113
- } : {
3114
- left: absoluteStartPosition
3115
- }),
3116
- ...style
3117
- },
3118
- ...props,
3119
- "data-reach-slider-marker": "",
3120
- "data-disabled": disabled ? '' : undefined,
3121
- "data-orientation": orientation,
3122
- "data-state": state,
3123
- "data-value": value,
3124
- children: children
3125
- }) : null;
3126
- });
3127
- const SliderMarker = /*#__PURE__*/react.memo(SliderMarkerImpl);
3128
-
3129
- /**
3130
- * @see Docs https://reach.tech/slider#slidermarker-props
3131
- */
3132
-
3133
- ////////////////////////////////////////////////////////////////////////////////
3134
-
3135
- function clamp$1(val, min, max) {
3136
- return val > max ? max : val < min ? min : val;
3137
- }
3138
-
3139
- /**
3140
- * This handles the case when num is very small (0.00000001), js will turn
3141
- * this into 1e-8. When num is bigger than 1 or less than -1 it won't get
3142
- * converted to this notation so it's fine.
3143
- *
3144
- * @param num
3145
- * @see https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Slider/Slider.js#L69
3146
- */
3147
- function getDecimalPrecision(num) {
3148
- if (Math.abs(num) < 1) {
3149
- const parts = num.toExponential().split('e-');
3150
- const matissaDecimalPart = parts[0].split('.')[1];
3151
- return (matissaDecimalPart ? matissaDecimalPart.length : 0) + parseInt(parts[1], 10);
3152
- }
3153
- const decimalPart = num.toString().split('.')[1];
3154
- return decimalPart ? decimalPart.length : 0;
3155
- }
3156
- function percentToValue(percent, min, max) {
3157
- return (max - min) * percent + min;
3158
- }
3159
- function roundValueToStep(value, step, min) {
3160
- const nearest = Math.round((value - min) / step) * step + min;
3161
- return Number(nearest.toFixed(getDecimalPrecision(step)));
3162
- }
3163
- function getPointerPosition(event, touchId) {
3164
- if (touchId.current !== undefined && event.changedTouches) {
3165
- for (let i = 0; i < event.changedTouches.length; i += 1) {
3166
- const touch = event.changedTouches[i];
3167
- if (touch.identifier === touchId.current) {
3168
- return {
3169
- x: touch.clientX,
3170
- y: touch.clientY
3171
- };
3172
- }
3173
- }
3174
- return false;
3175
- }
3176
- return {
3177
- x: event.clientX,
3178
- y: event.clientY
3179
- };
3180
- }
3181
- function getNewValue(handlePosition, track, props) {
3182
- const {
3183
- orientation,
3184
- min,
3185
- max,
3186
- step
3187
- } = props;
3188
- if (!track || !handlePosition) {
3189
- return null;
3190
- }
3191
- const {
3192
- left,
3193
- width,
3194
- bottom,
3195
- height
3196
- } = track.getBoundingClientRect();
3197
- const isVertical = orientation === 'vertical';
3198
- const diff = isVertical ? bottom - handlePosition.y : handlePosition.x - left;
3199
- const percent = diff / (isVertical ? height : width);
3200
- const newValue = percentToValue(percent, min, max);
3201
- return clamp$1(step ? roundValueToStep(newValue, step, min) : newValue, min, max);
3202
- }
3203
- function useDimensions(ref) {
3204
- const [{
3205
- width,
3206
- height
3207
- }, setDimensions] = react.useState({
3208
- width: 0,
3209
- height: 0
3210
- });
3211
- // Many existing `useDimensions` type hooks will use `getBoundingClientRect`
3212
- // getBoundingClientRect does not work here when borders are applied.
3213
- // getComputedStyle is not as performant so we may want to create a utility to
3214
- // check for any conflicts with box sizing first and only use
3215
- // `getComputedStyle` if neccessary.
3216
- /* const { width, height } = ref.current
3217
- ? ref.current.getBoundingClientRect()
3218
- : 0; */
3219
-
3220
- react.useLayoutEffect(() => {
3221
- const ownerDocument = getOwnerDocument(ref.current);
3222
- const ownerWindow = ownerDocument.defaultView || window;
3223
- if (ref.current) {
3224
- const {
3225
- height: _newHeight,
3226
- width: _newWidth
3227
- } = ownerWindow.getComputedStyle(ref.current);
3228
- const newHeight = parseFloat(_newHeight);
3229
- const newWidth = parseFloat(_newWidth);
3230
- if (newHeight !== height || newWidth !== width) {
3231
- setDimensions({
3232
- height: newHeight,
3233
- width: newWidth
3234
- });
3235
- }
3236
- }
3237
- }, [ref, width, height]);
3238
- return {
3239
- ref,
3240
- width,
3241
- height
3242
- };
3243
- }
3244
- function valueToPercent(value, min, max) {
3245
- return (value - min) * 100 / (max - min);
3246
- }
3247
-
3248
- // Spinner Component
3249
-
3250
- const spinbuttonContext = /*#__PURE__*/react.createContext(null);
3251
- const {
3252
- Provider: SpinnerProvider
3253
- } = spinbuttonContext;
3254
- const useSpinnerContext = () => react.useContext(spinbuttonContext);
3255
-
3256
- function clamp(value, min, max) {
3257
- return Math.min(Math.max(value, min), max);
3258
- }
3259
-
3260
- const Spinner = /*#__PURE__*/react.forwardRef(function Spinner(props, forwardedRef) {
3261
- const {
3262
- as: Comp = 'div',
3263
- minValue = -1000,
3264
- maxValue = 1000,
3265
- stepSize = 10,
3266
- value = 0,
3267
- onChange,
3268
- onKeyDown,
3269
- onBlur,
3270
- onFocus,
3271
- ...otherProps
3272
- } = props;
3273
- const [spinnerHasFocus, setSpinnerHasFocus] = react.useState(false);
3274
- const ref = react.useRef(null);
3275
- const clamp$1 = value => clamp(value, minValue, maxValue);
3276
- const handleKeyDown = e => {
3277
- let nextValue = value;
3278
- switch (e.key) {
3279
- case 'ArrowUp':
3280
- nextValue += 1;
3281
- break;
3282
- case 'ArrowDown':
3283
- nextValue -= 1;
3284
- break;
3285
- case 'Home':
3286
- nextValue = minValue;
3287
- break;
3288
- case 'End':
3289
- nextValue = maxValue;
3290
- break;
3291
- case 'PageUp':
3292
- nextValue += stepSize;
3293
- break;
3294
- case 'PageDown':
3295
- nextValue -= stepSize;
3296
- break;
3297
- default:
3298
- return;
3299
- }
3300
- ref.current && ref.current.focus();
3301
- nextValue = clamp$1(nextValue);
3302
- if (nextValue !== value) {
3303
- e.preventDefault(); // prevent scrolling
3304
- onChange && onChange(e, nextValue);
3305
- }
3306
- };
3307
- const handleFocus = () => {
3308
- setSpinnerHasFocus(true);
3309
- };
3310
- const handleBlur = () => {
3311
- setSpinnerHasFocus(false);
3312
- };
3313
- return /*#__PURE__*/jsxRuntime.jsx(SpinnerProvider, {
3314
- value: {
3315
- minValue,
3316
- value,
3317
- maxValue,
3318
- stepSize,
3319
- onChange,
3320
- clamp: clamp$1,
3321
- spinnerHasFocus
3322
- },
3323
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
3324
- ref: assignMultipleRefs(forwardedRef, ref),
3325
- "data-spinner-root": "",
3326
- role: "spinbutton",
3327
- "aria-valuenow": value,
3328
- "aria-valuemin": minValue,
3329
- "aria-valuemax": maxValue,
3330
- tabIndex: 0,
3331
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
3332
- onFocus: wrapEvent(onFocus, handleFocus),
3333
- onBlur: wrapEvent(onBlur, handleBlur),
3334
- ...otherProps
3335
- })
3336
- });
3337
- });
3338
-
3339
- const SpinnerButton = /*#__PURE__*/react.forwardRef(function SpinnerButton(props, forwardedRef) {
3340
- const {
3341
- as: Comp = 'button',
3342
- type,
3343
- onClick,
3344
- ...otherProps
3345
- } = props;
3346
- const spinnerContext = useSpinnerContext();
3347
- if (!spinnerContext) {
3348
- throw new Error('Missing <Spinner /> in component tree');
3349
- }
3350
- const handleClick = e => {
3351
- const delta = type === 'next' ? 1 : -1;
3352
- spinnerContext.onChange && spinnerContext.onChange(e, spinnerContext.value + delta);
3353
- };
3354
- const disabled = type === 'next' ? spinnerContext.value + 1 > spinnerContext.maxValue : spinnerContext.value - 1 < spinnerContext.minValue;
3355
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
3356
- ...otherProps,
3357
- type: "button",
3358
- ref: forwardedRef,
3359
- tabIndex: -1,
3360
- "data-spinner-button": "",
3361
- "data-spinner-has-focus": spinnerContext.spinnerHasFocus ? '' : undefined,
3362
- disabled: disabled,
3363
- onClick: wrapEvent(onClick, handleClick)
3364
- });
3365
- });
3366
-
3367
- // Tabs Component
3368
-
3369
- const tabsContext = /*#__PURE__*/react.createContext(null);
3370
- const {
3371
- Provider: TabsProvider
3372
- } = tabsContext;
3373
- const useTabsContext = () => react.useContext(tabsContext);
3374
-
3375
- // TabList Component
3376
-
3377
- const tablistContext = /*#__PURE__*/react.createContext(null);
3378
- const {
3379
- Provider: TabListProvider
3380
- } = tablistContext;
3381
- const useTabListContext = () => react.useContext(tablistContext);
3382
-
3383
- const Tabs = /*#__PURE__*/react.forwardRef(function Tabs(props, forwardedRef) {
3384
- const {
3385
- as: Comp = react.Fragment,
3386
- index: indexProp,
3387
- onChange: onChangeProp,
3388
- defaultIndex = 0,
3389
- ...otherProps
3390
- } = props;
3391
- const [index, onChange] = useControlledState(indexProp, onChangeProp, defaultIndex, setState => (e, idx) => setState(idx));
3392
- const [tabListId, setTabListId] = react.useState(null);
3393
- return /*#__PURE__*/jsxRuntime.jsx(TabsProvider, {
3394
- value: {
3395
- currentIndex: index || 0,
3396
- onChange,
3397
- tabListId,
3398
- setTabListId
3399
- },
3400
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
3401
- ref: forwardedRef,
3402
- ...otherProps
3403
- })
3404
- });
3405
- });
3406
-
3407
- function scopeQuery(nodeType, props) {
3408
- return props['data-tab'] === '';
3409
- }
3410
-
3411
- function getNextIndex(desiredIndex, delta, allTabs) {
3412
- for (let i = 0; i < allTabs.length; i++) {
3413
- const nextIndex = getCircularIndex(desiredIndex + delta * i, allTabs.length);
3414
- if (nextIndex !== null && !allTabs[nextIndex].disabled) {
3415
- return nextIndex;
3416
- }
3417
- }
3418
- return null;
3419
- }
3420
- const Tab = /*#__PURE__*/react.forwardRef(function Tab(props, forwardedRef) {
3421
- const {
3422
- as: Comp = 'button',
3423
- onKeyDown,
3424
- onClick,
3425
- index: tabIndex = -1,
3426
- ...otherProps
3427
- } = props;
3428
- const tabContext = useTabsContext();
3429
- const tabListContext = useTabListContext();
3430
- const ref = react.useRef(undefined);
3431
- if (!tabContext || !tabListContext) {
3432
- throw new Error('Missing <Tabs /> or <TabList /> in the component tree');
3433
- }
3434
- const handleKeyDown = e => {
3435
- const right = tabListContext.vertical ? 'ArrowDown' : 'ArrowRight';
3436
- const left = tabListContext.vertical ? 'ArrowUp' : 'ArrowLeft';
3437
- const first = 'Home';
3438
- const last = 'End';
3439
- const navigateIndex = (desiredIndex, isLast) => {
3440
- const delta = e.key === right || e.key === first ? 1 : -1;
3441
- const allTabs = tabListContext.tabsScope.current.queryAllNodes(scopeQuery);
3442
- const currentIndex = ref.current ? allTabs.indexOf(ref.current) : -1;
3443
- const nextIndex = getNextIndex(isLast ? desiredIndex : currentIndex + desiredIndex, delta, allTabs);
3444
- if (nextIndex !== null && nextIndex !== currentIndex && tabContext.onChange) {
3445
- allTabs[nextIndex].focus();
3446
- !tabListContext.manualActivation && tabContext.onChange(e, nextIndex);
3447
- }
3448
- };
3449
- switch (e.key) {
3450
- case right:
3451
- case left:
3452
- {
3453
- navigateIndex(e.key === right ? 1 : -1, false);
3454
- break;
3455
- }
3456
- case first: // Home / End
3457
- case last:
3458
- {
3459
- navigateIndex(e.key === first ? 0 : -1, true);
3460
- break;
3461
- }
3462
- }
3463
- };
3464
- const handleClick = e => {
3465
- const allTabs = tabListContext.tabsScope.current.queryAllNodes(scopeQuery);
3466
- const currentIndex = ref.current ? allTabs.indexOf(ref.current) : -1;
3467
- if (currentIndex >= 0) {
3468
- tabContext.onChange && tabContext.onChange(e, currentIndex);
3469
- }
3470
- };
3471
- const isSelected = tabIndex === tabContext.currentIndex;
3472
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
3473
- ...otherProps,
3474
- ref: assignMultipleRefs(ref, forwardedRef),
3475
- "data-tab": "",
3476
- id: tabContext.tabListId !== null && tabIndex >= 0 ? `tab-${tabContext.tabListId}-${tabIndex}` : undefined,
3477
- "aria-controls": tabContext.tabListId !== null && tabIndex >= 0 ? `tabpanel-${tabContext.tabListId}-${tabIndex}` : undefined,
3478
- role: "tab",
3479
- tabIndex: isSelected ? 0 : -1,
3480
- "aria-selected": isSelected,
3481
- selected: isSelected,
3482
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown),
3483
- onClick: wrapEvent(onClick, handleClick)
3484
- });
3485
- });
3486
-
3487
- const TabList = /*#__PURE__*/react.forwardRef(function TabList(props, forwardedRef) {
3488
- const {
3489
- as: Comp = 'div',
3490
- manualActivation = false,
3491
- vertical = false,
3492
- children: childrenProps,
3493
- ...otherProps
3494
- } = props;
3495
- const ref = react.useRef(null);
3496
- const tabsScope = useScope(ref);
3497
- const tabsContext = useTabsContext();
3498
- const id = react.useId();
3499
- if (!tabsContext) {
3500
- throw new Error('Missing <Tabs /> in the component tree');
3501
- }
3502
- react.useEffect(() => {
3503
- if (id !== undefined) {
3504
- tabsContext.setTabListId(id);
3505
- return () => {
3506
- tabsContext.setTabListId(null);
3507
- };
3508
- }
3509
- return;
3510
- }, [id, tabsContext, tabsContext.setTabListId]);
3511
- const children = react.Children.map(childrenProps, (node, index) => /*#__PURE__*/react.cloneElement(node, {
3512
- index
3513
- }));
3514
- return /*#__PURE__*/jsxRuntime.jsx(TabListProvider, {
3515
- value: {
3516
- tabsScope,
3517
- manualActivation,
3518
- vertical
3519
- },
3520
- children: /*#__PURE__*/jsxRuntime.jsx(Comp, {
3521
- ref: assignMultipleRefs(forwardedRef, ref),
3522
- "data-tab-list": "",
3523
- role: "tablist",
3524
- ...otherProps,
3525
- children: children
3526
- })
3527
- });
3528
- });
3529
-
3530
- const TabPanels = /*#__PURE__*/react.forwardRef(function TabPanels(props, forwardedRef) {
3531
- const {
3532
- as: Comp = react.Fragment,
3533
- children: childrenProps,
3534
- lazy,
3535
- ...otherProps
3536
- } = props;
3537
- const children = react.Children.map(childrenProps, (node, index) => /*#__PURE__*/react.cloneElement(node, {
3538
- index,
3539
- lazy
3540
- }));
3541
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
3542
- ref: forwardedRef,
3543
- ...otherProps,
3544
- children: children
3545
- });
3546
- });
3547
-
3548
- const TabPanel = /*#__PURE__*/react.forwardRef(function TabPanel(props, forwardedRef) {
3549
- const {
3550
- as: Comp = 'div',
3551
- index,
3552
- lazy = false,
3553
- children,
3554
- ...otherProps
3555
- } = props;
3556
- const tabsContext = useTabsContext();
3557
- if (!tabsContext) {
3558
- throw new Error('Missing <Tabs /> in the component tree.');
3559
- }
3560
- const isSelected = tabsContext.currentIndex === index;
3561
- return /*#__PURE__*/jsxRuntime.jsx(Comp, {
3562
- ref: forwardedRef,
3563
- "data-tab-panel": "",
3564
- role: "tabpanel",
3565
- id: tabsContext.tabListId !== null ? `tabpanel-${tabsContext.tabListId}-${index}` : undefined,
3566
- "aria-labelledby": tabsContext.tabListId !== null ? `tab-${tabsContext.tabListId}-${index}` : undefined,
3567
- hidden: isSelected ? undefined : 'hidden',
3568
- tabIndex: 0,
3569
- ...otherProps,
3570
- children: (isSelected || !lazy) && children
3571
- });
3572
- });
3573
-
3574
- function createSubscription() {
3575
- const subscriptions = [];
3576
- function subscribe(fn) {
3577
- subscriptions.push(fn);
3578
- return () => {
3579
- subscriptions.splice(subscriptions.indexOf(fn), 1);
3580
- };
3581
- }
3582
- function notify() {
3583
- subscriptions.forEach(fn => fn());
3584
- }
3585
- return {
3586
- subscribe,
3587
- notify
3588
- };
3589
- }
3590
-
3591
- ////////////////////////////////////////////////////////////////////////////////
3592
- // Timeouts:
3593
-
3594
- // Manages when the user "rests" on an element. Keeps the interface from being
3595
- // flashing tooltips all the time as the user moves the mouse around the screen.
3596
- let restTimeout;
3597
- function startRestTimer() {
3598
- clearTimeout(restTimeout);
3599
- restTimeout = setTimeout(() => {
3600
- send(Rest, undefined);
3601
- }, 200);
3602
- }
3603
- function clearRestTimer() {
3604
- clearTimeout(restTimeout);
3605
- }
3606
-
3607
- // Manages the delay to hide the tooltip after rest leaves.
3608
- let leavingVisibleTimer;
3609
- function startLeavingVisibleTimer() {
3610
- clearTimeout(leavingVisibleTimer);
3611
- leavingVisibleTimer = setTimeout(() => send(TimeComplete, undefined), 100);
3612
- }
3613
- function clearLeavingVisibleTimer() {
3614
- clearTimeout(leavingVisibleTimer);
3615
- }
3616
-
3617
- ////////////////////////////////////////////////////////////////////////////////
3618
- // State machine
3619
-
3620
- // Nothing goin' on
3621
- const Idle = 'IDLE';
3622
- // We're considering showing the tooltip, but we're gonna wait a sec
3623
- const Focused = 'FOCUSED';
3624
- // It's on!
3625
- const Visible = 'VISIBLE';
3626
- // Focus has left, but we want to keep it visible for a sec
3627
- const LeavingVisible = 'LEAVING_VISIBLE';
3628
- // The user clicked the tool, so we want to hide the thing, we can't just use
3629
- // IDLE because we need to ignore mousemove, etc.
3630
- const Dismissed = 'DISMISSED';
3631
- const Blur = 'BLUR';
3632
- const Focus = 'FOCUS';
3633
- const GlobalMouseMove = 'GLOBAL_MOUSE_MOVE';
3634
- const MouseDown = 'MOUSE_DOWN';
3635
- const MouseEnter = 'MOUSE_ENTER';
3636
- const MouseLeave = 'MOUSE_LEAVE';
3637
- const MouseMove = 'MOUSE_MOVE';
3638
- const Rest = 'REST';
3639
- const SelectWithKeyboard = 'SELECT_WITH_KEYBOARD';
3640
- const TimeComplete = 'TIME_COMPLETE';
3641
- const subscription = createSubscription();
3642
- const state = {
3643
- current: {
3644
- state: Idle,
3645
- id: ''
3646
- }
3647
- };
3648
- function clearContextId() {
3649
- state.current.id = '';
3650
- }
3651
- const chart = {
3652
- initial: Idle,
3653
- states: {
3654
- [Idle]: {
3655
- enter: () => {
3656
- clearContextId();
3657
- },
3658
- on: {
3659
- [MouseEnter]: Focused,
3660
- [Focus]: Visible
3661
- }
3662
- },
3663
- [Focused]: {
3664
- enter: startRestTimer,
3665
- leave: clearRestTimer,
3666
- on: {
3667
- [MouseMove]: Focused,
3668
- [MouseLeave]: Idle,
3669
- [MouseDown]: Dismissed,
3670
- [Blur]: Idle,
3671
- [Rest]: Visible
3672
- }
3673
- },
3674
- [Visible]: {
3675
- on: {
3676
- [Focus]: Focused,
3677
- [MouseEnter]: Focused,
3678
- [MouseLeave]: LeavingVisible,
3679
- [Blur]: LeavingVisible,
3680
- [MouseDown]: Dismissed,
3681
- [SelectWithKeyboard]: Dismissed,
3682
- [GlobalMouseMove]: LeavingVisible
3683
- }
3684
- },
3685
- [LeavingVisible]: {
3686
- enter: () => {
3687
- startLeavingVisibleTimer();
3688
- },
3689
- leave: () => {
3690
- clearLeavingVisibleTimer();
3691
- clearContextId();
3692
- },
3693
- on: {
3694
- [MouseEnter]: Visible,
3695
- [Focus]: Visible,
3696
- [TimeComplete]: Idle
3697
- }
3698
- },
3699
- [Dismissed]: {
3700
- leave: () => {
3701
- clearContextId();
3702
- },
3703
- on: {
3704
- [MouseLeave]: Idle,
3705
- [Blur]: Idle
3706
- }
3707
- }
3708
- }
3709
- };
3710
- function transition(currentState, event, payload) {
3711
- const currentStateValue = currentState.state;
3712
- const stateDef = chart.states[currentState.state];
3713
- const nextState = stateDef?.on?.[event];
3714
-
3715
- // Really useful for debugging
3716
- // console.log({
3717
- // event,
3718
- // state: state.current.state,
3719
- // id: state.current.id,
3720
- // nextState,
3721
- // });
3722
-
3723
- if (!nextState) {
3724
- return currentState;
3725
- }
3726
- if (stateDef && stateDef.leave) {
3727
- stateDef.leave(currentStateValue, payload);
3728
- }
3729
- const nextStateValue = nextState;
3730
- const nextDef = chart.states[nextStateValue];
3731
- if (nextDef && nextDef.enter) {
3732
- nextDef.enter(nextStateValue, payload);
3733
- }
3734
- return {
3735
- ...currentState,
3736
- ...payload,
3737
- state: nextStateValue
3738
- };
3739
- }
3740
- function send(event, payload) {
3741
- const nextState = transition(state.current, event, payload);
3742
- if (state.current !== nextState) {
3743
- state.current = nextState;
3744
- subscription.notify();
3745
- }
3746
- }
3747
-
3748
- function useTooltip(childProps, childRef, tooltipProps) {
3749
- const {
3750
- onMouseEnter,
3751
- onMouseLeave,
3752
- onMouseMove,
3753
- onMouseDown,
3754
- onKeyDown,
3755
- onFocus,
3756
- onBlur
3757
- } = childProps;
3758
- const anchorEl = react.useRef(null);
3759
- const [visible, setVisible] = react.useState(false);
3760
- const id = react.useId();
3761
- react.useEffect(() => {
3762
- subscription.subscribe(() => {
3763
- setVisible((state.current.state === Visible || state.current.state === LeavingVisible) && state.current.id === id);
3764
- });
3765
- }, [id]);
3766
- function handleMouseEnter() {
3767
- send(MouseEnter, {
3768
- id
3769
- });
3770
- }
3771
- function handleMouseMove() {
3772
- send(MouseMove, {
3773
- id
3774
- });
3775
- }
3776
- function handleMouseLeave() {
3777
- send(MouseLeave);
3778
- }
3779
- function handleMouseDown() {
3780
- // Allow quick click from one tool to another
3781
- if (state.current.id === id) {
3782
- send(MouseDown);
3783
- }
3784
- }
3785
- function handleFocus() {
3786
- send(Focus, {
3787
- id
3788
- });
3789
- }
3790
- function handleBlur() {
3791
- // Allow quick click from one tool to another
3792
- if (state.current.id === id) {
3793
- send(Blur, undefined);
3794
- }
3795
- }
3796
- function handleKeyDown(event) {
3797
- if (event.key === 'Enter' || event.key === ' ') {
3798
- send(SelectWithKeyboard);
3799
- }
3800
- }
3801
- const {
3802
- label: children,
3803
- onMouseEnter: tooltipOnMouseEnter,
3804
- onMouseLeave: tooltipOnMouseLeave,
3805
- onMouseMove: tooltipOnMouseMove,
3806
- ...otherTooltipProps
3807
- } = tooltipProps;
3808
- const tooltipId = `tooltip-${id}`;
3809
- return [{
3810
- ...childProps,
3811
- ref: assignMultipleRefs(childRef, anchorEl),
3812
- ...(visible && !childProps['aria-label'] && {
3813
- 'aria-describedby': tooltipId
3814
- }),
3815
- onMouseEnter: wrapEvent(onMouseEnter, handleMouseEnter),
3816
- onMouseLeave: wrapEvent(onMouseLeave, handleMouseLeave),
3817
- onMouseMove: wrapEvent(onMouseMove, handleMouseMove),
3818
- onMouseDown: wrapEvent(onMouseDown, handleMouseDown),
3819
- onFocus: wrapEvent(onFocus, handleFocus),
3820
- onBlur: wrapEvent(onBlur, handleBlur),
3821
- onKeyDown: wrapEvent(onKeyDown, handleKeyDown)
3822
- }, {
3823
- id: tooltipId,
3824
- anchorEl,
3825
- visible,
3826
- children,
3827
- onMouseEnter: wrapEvent(tooltipOnMouseEnter, handleMouseEnter),
3828
- onMouseLeave: wrapEvent(tooltipOnMouseLeave, handleMouseLeave),
3829
- onMouseMove: wrapEvent(tooltipOnMouseMove, handleMouseMove),
3830
- role: 'tooltip',
3831
- ...otherTooltipProps
3832
- }];
3833
- }
3834
-
3835
- const Tooltip = /*#__PURE__*/react.forwardRef(function Tooltip(props, forwardedRef) {
3836
- const {
3837
- as: Comp = 'div',
3838
- innerAs,
3839
- children,
3840
- disabled = false,
3841
- ...otherProps
3842
- } = props;
3843
- const child = react.Children.only(children);
3844
- // React 19: ref lives in props. React <=18: ref lives on element directly.
3845
- const childRef = child.props.ref ?? child.ref;
3846
- const [childProps, {
3847
- visible,
3848
- ...tooltipProps
3849
- }] = useTooltip(child.props, childRef, otherProps);
3850
- if (disabled) {
3851
- return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
3852
- children: child
3853
- });
3854
- }
3855
- return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
3856
- children: [/*#__PURE__*/react.cloneElement(child, childProps), visible && /*#__PURE__*/jsxRuntime.jsx(Comp, {
3857
- as: innerAs,
3858
- ref: forwardedRef,
3859
- ...tooltipProps
3860
- })]
3861
- });
3862
- });
3863
-
3864
- exports.Accordion = Accordion;
3865
- exports.AccordionBody = AccordionBody;
3866
- exports.AccordionHeader = AccordionHeader;
3867
- exports.AccordionItem = AccordionItem;
3868
- exports.CheckBox = CheckBox;
3869
- exports.ComboBoxProvider = ComboBoxProvider;
3870
- exports.Combobox = Combobox;
3871
- exports.ComboboxButton = ComboboxButton;
3872
- exports.ComboboxInput = ComboboxInput;
3873
- exports.ComboboxLabel = ComboboxLabel;
3874
- exports.ComboboxList = ComboboxList;
3875
- exports.ComboboxOption = ComboboxOption;
3876
- exports.ComboboxPopover = ComboboxPopover;
3877
- exports.ContextMenuTrigger = ContextMenuTrigger;
3878
- exports.FocusLock = FocusLock;
3879
- exports.Menu = Menu;
3880
- exports.MenuButton = MenuButton;
3881
- exports.MenuItem = MenuItem;
3882
- exports.MenuList = MenuList;
3883
- exports.MenuPopover = MenuPopover;
3884
- exports.Modal = Modal;
3885
- exports.ModalBackdrop = ModalBackdrop;
3886
- exports.Popper = Popper;
3887
- exports.PopperArrow = PopperArrow;
3888
- exports.PopperProvider = PopperProvider;
3889
- exports.Portal = Portal;
3890
- exports.PortalSelectorProvider = PortalSelectorProvider;
3891
- exports.RadioButton = RadioButton;
3892
- exports.RadioGroup = RadioGroup;
3893
- exports.Slider = Slider;
3894
- exports.SliderHandle = SliderHandle;
3895
- exports.SliderInput = SliderInput;
3896
- exports.SliderMarker = SliderMarker;
3897
- exports.SliderRange = SliderRange;
3898
- exports.SliderTrack = SliderTrack;
3899
- exports.Spinner = Spinner;
3900
- exports.SpinnerButton = SpinnerButton;
3901
- exports.Tab = Tab;
3902
- exports.TabList = TabList;
3903
- exports.TabPanel = TabPanel;
3904
- exports.TabPanels = TabPanels;
3905
- exports.Tabs = Tabs;
3906
- exports.Tooltip = Tooltip;
3907
- exports.assignMultipleRefs = assignMultipleRefs;
3908
- exports.assignRef = assignRef;
3909
- exports.canUseDOM = canUseDOM;
3910
- exports.createContext = createContext;
3911
- exports.gestureHandlers = gestureHandlers;
3912
- exports.getCircularIndex = getCircularIndex;
3913
- exports.getOwnerDocument = getOwnerDocument;
3914
- exports.getScope = getScope;
3915
- exports.initialGestureHandlersState = initialGestureHandlersState;
3916
- exports.isRightClick = isRightClick;
3917
- exports.rubberBand = rubberBand;
3918
- exports.rubberBandClamp = rubberBandClamp;
3919
- exports.useAutoFocus = useAutoFocus;
3920
- exports.useChildrenCounterChild = useChildrenCounterChild;
3921
- exports.useChildrenCounterParent = useChildrenCounterParent;
3922
- exports.useComboBoxContext = useComboBoxContext;
3923
- exports.useControlledState = useControlledState;
3924
- exports.useFocusReturn = useFocusReturn;
3925
- exports.useFocusState = useFocusState;
3926
- exports.useGestureHandlers = useGestureHandlers;
3927
- exports.useMeasure = useMeasure;
3928
- exports.useOnClickOutside = useOnClickOutside;
3929
- exports.useOnKeyDown = useOnKeyDown;
3930
- exports.usePopperContext = usePopperContext;
3931
- exports.useReducerMachine = useReducerMachine;
3932
- exports.useRemoveBodyScroll = useRemoveBodyScroll;
3933
- exports.useScope = useScope;
3934
- exports.useStableCallback = useStableCallback;
3935
- exports.useStableLayoutCallback = useStableLayoutCallback;
3936
- exports.useThrottle = useThrottle;
3937
- exports.wrapEvent = wrapEvent;
3938
- //# sourceMappingURL=index.js.map