@dbcdk/react-components 0.0.57 → 0.0.58
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.
|
@@ -9,8 +9,10 @@ import { Popover } from '../../../components/popover/Popover';
|
|
|
9
9
|
import styles from './Typeahead.module.css';
|
|
10
10
|
export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'chips', multiSelectedValuesDisplayMode = 'hidden', multiSelectedValueChipContent = 'label', selectedValue = null, onChange, placeholder, variant = 'outlined', disabled = false, fullWidth = false, onClear, emptyMessage = 'Ingen resultater', filterOptions, inputProps, inputSize, width, minWidth, autoComplete, autoCorrect, autoCapitalize, spellCheck, popoverAnchorRef, }) {
|
|
11
11
|
var _a;
|
|
12
|
+
const rootRef = useRef(null);
|
|
12
13
|
const inputRef = useRef(null);
|
|
13
14
|
const listboxRef = useRef(null);
|
|
15
|
+
const popoverContentRef = useRef(null);
|
|
14
16
|
const optionRefs = useRef([]);
|
|
15
17
|
const interactingWithOptionsRef = useRef(false);
|
|
16
18
|
const listboxId = useId();
|
|
@@ -105,6 +107,75 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
|
|
|
105
107
|
const activeEl = optionRefs.current[activeIndex];
|
|
106
108
|
(_a = activeEl === null || activeEl === void 0 ? void 0 : activeEl.scrollIntoView) === null || _a === void 0 ? void 0 : _a.call(activeEl, { block: 'nearest' });
|
|
107
109
|
}, [open, activeIndex, filteredOptions]);
|
|
110
|
+
const getFocusableElements = React.useCallback(() => {
|
|
111
|
+
const selector = [
|
|
112
|
+
'a[href]',
|
|
113
|
+
'button:not([disabled])',
|
|
114
|
+
'input:not([disabled])',
|
|
115
|
+
'select:not([disabled])',
|
|
116
|
+
'textarea:not([disabled])',
|
|
117
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
118
|
+
].join(',');
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
const focusables = [];
|
|
121
|
+
for (const container of [rootRef.current, popoverContentRef.current]) {
|
|
122
|
+
if (!container)
|
|
123
|
+
continue;
|
|
124
|
+
const elements = container.matches(selector)
|
|
125
|
+
? [container]
|
|
126
|
+
: Array.from(container.querySelectorAll(selector));
|
|
127
|
+
for (const element of elements) {
|
|
128
|
+
if (seen.has(element) ||
|
|
129
|
+
element.getAttribute('aria-hidden') === 'true' ||
|
|
130
|
+
element.tabIndex < 0) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
seen.add(element);
|
|
134
|
+
focusables.push(element);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return focusables;
|
|
138
|
+
}, []);
|
|
139
|
+
const isFocusWithinTypeahead = React.useCallback((target) => {
|
|
140
|
+
var _a, _b;
|
|
141
|
+
if (!(target instanceof Node))
|
|
142
|
+
return false;
|
|
143
|
+
return Boolean(((_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(target)) || ((_b = popoverContentRef.current) === null || _b === void 0 ? void 0 : _b.contains(target)));
|
|
144
|
+
}, []);
|
|
145
|
+
const handleTabKeyDown = React.useCallback((event) => {
|
|
146
|
+
var _a, _b, _c;
|
|
147
|
+
if (event.key !== 'Tab' || !open)
|
|
148
|
+
return;
|
|
149
|
+
const focusables = getFocusableElements();
|
|
150
|
+
if (focusables.length === 0)
|
|
151
|
+
return;
|
|
152
|
+
const eventTarget = event.target instanceof HTMLElement
|
|
153
|
+
? event.target
|
|
154
|
+
: document.activeElement;
|
|
155
|
+
const activeElement = eventTarget && focusables.includes(eventTarget)
|
|
156
|
+
? eventTarget
|
|
157
|
+
: ((_a = document.activeElement) !== null && _a !== void 0 ? _a : null);
|
|
158
|
+
const activeIndexInScope = activeElement ? focusables.indexOf(activeElement) : -1;
|
|
159
|
+
const boundaryIndex = event.shiftKey ? 0 : focusables.length - 1;
|
|
160
|
+
const boundaryElement = focusables[boundaryIndex];
|
|
161
|
+
if (mode === 'multi') {
|
|
162
|
+
if (activeIndexInScope === -1) {
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
(_b = focusables[0]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
event.preventDefault();
|
|
168
|
+
const nextIndex = event.shiftKey
|
|
169
|
+
? (activeIndexInScope - 1 + focusables.length) % focusables.length
|
|
170
|
+
: (activeIndexInScope + 1) % focusables.length;
|
|
171
|
+
(_c = focusables[nextIndex]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (activeIndexInScope !== -1 && activeElement === boundaryElement) {
|
|
175
|
+
setOpen(false);
|
|
176
|
+
setActiveIndex(-1);
|
|
177
|
+
}
|
|
178
|
+
}, [open, getFocusableElements, mode]);
|
|
108
179
|
const commitSelection = (option) => {
|
|
109
180
|
var _a, _b;
|
|
110
181
|
if (mode === 'multi') {
|
|
@@ -144,7 +215,9 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
|
|
|
144
215
|
onChange(null);
|
|
145
216
|
}
|
|
146
217
|
};
|
|
147
|
-
const handleBlur = () => {
|
|
218
|
+
const handleBlur = (nextFocusedTarget) => {
|
|
219
|
+
if (isFocusWithinTypeahead(nextFocusedTarget))
|
|
220
|
+
return;
|
|
148
221
|
if (mode === 'multi') {
|
|
149
222
|
if (interactingWithOptionsRef.current) {
|
|
150
223
|
interactingWithOptionsRef.current = false;
|
|
@@ -261,7 +334,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
|
|
|
261
334
|
return;
|
|
262
335
|
}
|
|
263
336
|
};
|
|
264
|
-
return (_jsxs("div", { style: {
|
|
337
|
+
return (_jsxs("div", { ref: rootRef, style: {
|
|
265
338
|
display: 'flex',
|
|
266
339
|
flexDirection: 'column',
|
|
267
340
|
gap: mode === 'multi' &&
|
|
@@ -284,7 +357,7 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
|
|
|
284
357
|
else {
|
|
285
358
|
setActiveIndex(-1);
|
|
286
359
|
}
|
|
287
|
-
}, fullWidth: fullWidth, autoFocusContent: false, returnFocus: false, trigger: (openPopover, icon) => {
|
|
360
|
+
}, fullWidth: fullWidth, autoFocusContent: false, returnFocus: false, overlayRef: popoverContentRef, trigger: (openPopover, icon) => {
|
|
288
361
|
var _a, _b, _c, _d, _e;
|
|
289
362
|
return (_jsx(Input, { ...passthroughInputProps, ref: inputRef, value: inputValue, startAdornment: multiSelectionAdornment || inputPropsStartAdornment ? (_jsxs(_Fragment, { children: [multiSelectionAdornment, inputPropsStartAdornment] })) : undefined, endAdornment: inputPropsEndAdornment || icon ? (_jsxs(_Fragment, { children: [inputPropsEndAdornment, icon] })) : undefined, onFocus: e => {
|
|
290
363
|
inputPropsOnFocus === null || inputPropsOnFocus === void 0 ? void 0 : inputPropsOnFocus(e);
|
|
@@ -325,9 +398,12 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
|
|
|
325
398
|
inputPropsOnBlur === null || inputPropsOnBlur === void 0 ? void 0 : inputPropsOnBlur(e);
|
|
326
399
|
if (e.defaultPrevented)
|
|
327
400
|
return;
|
|
328
|
-
handleBlur();
|
|
401
|
+
handleBlur(e.relatedTarget);
|
|
329
402
|
}, onKeyDown: e => {
|
|
330
403
|
inputPropsOnKeyDown === null || inputPropsOnKeyDown === void 0 ? void 0 : inputPropsOnKeyDown(e);
|
|
404
|
+
if (e.defaultPrevented)
|
|
405
|
+
return;
|
|
406
|
+
handleTabKeyDown(e);
|
|
331
407
|
if (e.defaultPrevented)
|
|
332
408
|
return;
|
|
333
409
|
handleKeyDown(e);
|
|
@@ -363,11 +439,12 @@ export function Typeahead({ options, mode = 'single', multiValueDisplayMode = 'c
|
|
|
363
439
|
interactingWithOptionsRef.current = true;
|
|
364
440
|
e.preventDefault();
|
|
365
441
|
},
|
|
442
|
+
onKeyDown: e => handleTabKeyDown(e),
|
|
366
443
|
}, label: _jsx("span", { children: option.label }), onCheckedChange: () => commitSelection(option) }, option.value)) : (_jsx(Menu.Item, { active: isActive, selected: isSelected, children: _jsx("button", { ref: node => {
|
|
367
444
|
optionRefs.current[index] = node;
|
|
368
445
|
}, id: optionId, type: "button", role: "option", "aria-selected": isSelected, onMouseEnter: () => setActiveIndex(index), onMouseDown: e => {
|
|
369
446
|
e.preventDefault();
|
|
370
|
-
}, onClick: () => commitSelection(option), children: _jsx("span", { children: option.label }) }) }, option.value));
|
|
447
|
+
}, onKeyDown: e => handleTabKeyDown(e), onClick: () => commitSelection(option), children: _jsx("span", { children: option.label }) }) }, option.value));
|
|
371
448
|
})) : (_jsx(Menu.Item, { disabled: true, children: emptyMessage })) }) }), mode === 'multi' &&
|
|
372
449
|
multiSelectedValuesDisplayMode === 'below-input' &&
|
|
373
450
|
selectedOptions.length > 0 && (_jsx("div", { style: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
4
5
|
import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
|
|
5
6
|
import { createPortal } from 'react-dom';
|
|
6
7
|
import styles from './Popover.module.css';
|
|
@@ -37,7 +38,7 @@ function parseMinWidthPx(minWidth, elForEm) {
|
|
|
37
38
|
}
|
|
38
39
|
return 0;
|
|
39
40
|
}
|
|
40
|
-
export const Popover = forwardRef(function Popover({ trigger: Trigger, children, open, defaultOpen = false, onOpenChange, contentId, minWidth = '200px', matchTriggerWidth = true, viewportPadding = 8, edgeBuffer = 100, dataCy, fullWidth = false, autoFocusContent = false, returnFocus = true, anchorRef, }, ref) {
|
|
41
|
+
export const Popover = forwardRef(function Popover({ trigger: Trigger, children, open, defaultOpen = false, onOpenChange, contentId, minWidth = '200px', matchTriggerWidth = true, viewportPadding = 8, edgeBuffer = 100, dataCy, fullWidth = false, autoFocusContent = false, returnFocus = true, anchorRef, overlayRef, }, ref) {
|
|
41
42
|
const internalId = useId();
|
|
42
43
|
const resolvedContentId = contentId !== null && contentId !== void 0 ? contentId : `popover-${internalId}`;
|
|
43
44
|
const isControlled = open !== undefined;
|
|
@@ -216,9 +217,23 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
|
|
|
216
217
|
(_b = (_a = triggerElRef.current) === null || _a === void 0 ? void 0 : _a.focus) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
217
218
|
}, [isOpen, returnFocus]);
|
|
218
219
|
const icon = isOpen ? _jsx(ChevronUp, { size: 20 }) : _jsx(ChevronDown, { size: 20 });
|
|
220
|
+
const setOverlayRef = React.useCallback((node) => {
|
|
221
|
+
if (!overlayRef)
|
|
222
|
+
return;
|
|
223
|
+
if (typeof overlayRef === 'function') {
|
|
224
|
+
overlayRef(node);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const mutableRef = overlayRef;
|
|
228
|
+
mutableRef.current = node;
|
|
229
|
+
}, [overlayRef]);
|
|
219
230
|
return (_jsxs("div", { className: [styles.container, fullWidth ? styles.fullWidth : ''].filter(Boolean).join(' '), ref: containerRef, children: [Trigger(togglePopover, icon, isOpen), mounted &&
|
|
220
231
|
isOpen &&
|
|
221
|
-
createPortal(_jsx("div", { id: resolvedContentId, ref:
|
|
232
|
+
createPortal(_jsx("div", { id: resolvedContentId, ref: node => {
|
|
233
|
+
const mutableContentRef = contentRef;
|
|
234
|
+
mutableContentRef.current = node;
|
|
235
|
+
setOverlayRef(node);
|
|
236
|
+
}, className: styles.content, style: {
|
|
222
237
|
top: pos.top,
|
|
223
238
|
left: pos.left,
|
|
224
239
|
// Content-driven sizing by default.
|