UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

473 lines (472 loc) 18.5 kB
"use client"; import React, { useCallback, useLayoutEffect, useMemo, useRef } from 'react'; import clsx from 'clsx'; import { listAllSections, pickSectionDomProps, setSectionDomApi } from "./dom.js"; import { distributeValueFromStart, extractValidChars, getDisplayValue, insertChar, insertCharIntoSection, joinValues, removeChar } from "./utils.js"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; export default function SegmentedFieldSection({ groupId, inputId, itemProps, value, overwriteMode, delimiter, groupDelimiter, disabled, valuesRef, inputs, scopeRef, sectionRefs, caretPositionsRef, sectionSelectionModeRef, wholeGroupSelectionUi, clearGroupSelection, clearSectionSelection, selectWholeGroup, selectSection, setSectionCaret, focusSection, onChange, onGroupFocus, onGroupBlur, ...sharedProps }) { var _spinButton$parseValu, _spinButton$parseValu2; const { label, mask, spinButton, className, placeholder, onCopy: onCopyExternal, onPaste: onPasteExternal, ...htmlProps } = itemProps; const displayValue = getDisplayValue({ value, placeholder: String(placeholder || ''), length: mask.length }); const hasTypedValue = value.length > 0; const handledPasteTimestampRef = useRef(undefined); const setRef = useCallback(element => { sectionRefs.current[inputId] = element; }, [inputId, sectionRefs]); useLayoutEffect(() => { const element = sectionRefs.current[inputId]; if (!element) { return; } setSectionDomApi({ element, inputId, displayValue, caretPositionsRef, sectionSelectionModeRef, selectSection, setSectionCaret }); }, [caretPositionsRef, displayValue, inputId, selectSection, sectionRefs, sectionSelectionModeRef, setSectionCaret, value]); useLayoutEffect(() => { var _caretPositionsRef$cu; const element = sectionRefs.current[inputId]; if (!element || document.activeElement !== element) { return; } if (wholeGroupSelectionUi) { return; } if (sectionSelectionModeRef.current[inputId] === 'all') { selectSection(inputId); return; } setSectionCaret(inputId, (_caretPositionsRef$cu = caretPositionsRef.current[inputId]) !== null && _caretPositionsRef$cu !== void 0 ? _caretPositionsRef$cu : displayValue.length); }, [caretPositionsRef, displayValue.length, inputId, sectionRefs, sectionSelectionModeRef, selectSection, setSectionCaret, value, wholeGroupSelectionUi]); const updateValue = useCallback(nextValue => { var _caretPositionsRef$cu2; caretPositionsRef.current[inputId] = Math.min((_caretPositionsRef$cu2 = caretPositionsRef.current[inputId]) !== null && _caretPositionsRef$cu2 !== void 0 ? _caretPositionsRef$cu2 : 0, nextValue.length); onChange(inputId, nextValue); }, [caretPositionsRef, inputId, onChange]); const stepSpinButton = useCallback(direction => { var _valuesRef$current$in, _parseValue, _formatValue; if (!spinButton) { return false; } const { min, max, step = 1, wrap = true, getInitialValue, parseValue, formatValue } = spinButton; const currentValue = (_valuesRef$current$in = valuesRef.current[inputId]) !== null && _valuesRef$current$in !== void 0 ? _valuesRef$current$in : ''; const parsedValue = (_parseValue = parseValue === null || parseValue === void 0 ? void 0 : parseValue(currentValue)) !== null && _parseValue !== void 0 ? _parseValue : currentValue ? Number(currentValue) : undefined; let nextValue = parsedValue; if (typeof parsedValue !== 'number' || Number.isNaN(parsedValue)) { const initialValue = getInitialValue === null || getInitialValue === void 0 ? void 0 : getInitialValue(); nextValue = typeof initialValue === 'number' && !Number.isNaN(initialValue) ? initialValue : direction === 'up' ? min : max; } else { nextValue = parsedValue + (direction === 'up' ? step : step * -1); if (nextValue > max) { nextValue = wrap ? min : max; } else if (nextValue < min) { nextValue = wrap ? max : min; } } const formattedValue = ((_formatValue = formatValue === null || formatValue === void 0 ? void 0 : formatValue(nextValue)) !== null && _formatValue !== void 0 ? _formatValue : String(nextValue).padStart(mask.length, '0')).slice(0, mask.length); updateValue(formattedValue); selectSection(inputId); return true; }, [inputId, mask.length, selectSection, spinButton, updateValue, valuesRef]); const getNextSectionId = useCallback((direction, { withinGroup = false } = {}) => { var _sections2; const current = sectionRefs.current[inputId]; if (!current) { return undefined; } const root = withinGroup ? current.closest('.dnb-segmented-field__group') || undefined : scopeRef.current || undefined; const sections = listAllSections(root); const index = sections.findIndex(section => section === current); if (index < 0) { return undefined; } if (direction === 'prev') { var _sections; return (_sections = sections[index - 1]) === null || _sections === void 0 ? void 0 : _sections.dataset.segmentedInputId; } return (_sections2 = sections[index + 1]) === null || _sections2 === void 0 ? void 0 : _sections2.dataset.segmentedInputId; }, [inputId, scopeRef, sectionRefs]); const focusAdjacentSection = useCallback(direction => { const current = sectionRefs.current[inputId]; if (!current) { return false; } const sections = listAllSections(scopeRef.current || undefined); const index = sections.findIndex(section => section === current); if (index < 0) { return false; } const target = direction === 'prev' ? sections[index - 1] : sections[index + 1]; if (!target) { return false; } target.focus(); return true; }, [inputId, scopeRef, sectionRefs]); const replaceWithChar = useCallback(char => { var _valuesRef$current$in2, _caretPositionsRef$cu3, _mask$Math$min; const currentValue = (_valuesRef$current$in2 = valuesRef.current[inputId]) !== null && _valuesRef$current$in2 !== void 0 ? _valuesRef$current$in2 : ''; const currentPosition = (_caretPositionsRef$cu3 = caretPositionsRef.current[inputId]) !== null && _caretPositionsRef$cu3 !== void 0 ? _caretPositionsRef$cu3 : 0; const isAllSelected = sectionSelectionModeRef.current[inputId] === 'all'; if (!((_mask$Math$min = mask[Math.min(currentPosition, mask.length - 1)]) !== null && _mask$Math$min !== void 0 && _mask$Math$min.test(char))) { return false; } if (!isAllSelected && currentPosition >= mask.length && currentValue.length >= mask.length) { const nextSectionId = getNextSectionId('next', { withinGroup: true }); if (nextSectionId) { var _inputs$find, _nextMask$; const nextMask = (_inputs$find = inputs.find(({ id }) => id === nextSectionId)) === null || _inputs$find === void 0 ? void 0 : _inputs$find.mask; if (!(nextMask !== null && nextMask !== void 0 && (_nextMask$ = nextMask[0]) !== null && _nextMask$ !== void 0 && _nextMask$.test(char))) { return false; } focusSection(nextSectionId, 'all'); caretPositionsRef.current[nextSectionId] = 0; return insertCharIntoSection({ char, inputId: nextSectionId, overwriteMode, valuesRef, inputs, caretPositionsRef, sectionSelectionModeRef, onChange, focusSection, setSectionCaret }); } if (focusAdjacentSection('next')) { var _document$activeEleme; ; (_document$activeEleme = document.activeElement) === null || _document$activeEleme === void 0 || _document$activeEleme.dispatchEvent(new KeyboardEvent('keydown', { key: char, bubbles: true, cancelable: true })); return true; } return false; } const sourceValue = isAllSelected ? '' : currentValue; const sourcePosition = isAllSelected ? 0 : currentPosition; const nextValue = insertChar(sourceValue, char, sourcePosition, { overwriteMode, maxLength: mask.length }); updateValue(nextValue); const nextPosition = Math.min(sourcePosition + 1, mask.length); if (nextValue.length >= mask.length && nextPosition >= mask.length) { const nextSectionId = getNextSectionId('next', { withinGroup: true }); if (nextSectionId) { focusSection(nextSectionId, 'all'); } else if (!focusAdjacentSection('next')) { setSectionCaret(inputId, mask.length); } } else { setSectionCaret(inputId, nextPosition); } return true; }, [caretPositionsRef, focusAdjacentSection, focusSection, getNextSectionId, inputId, inputs, mask, onChange, overwriteMode, sectionSelectionModeRef, setSectionCaret, updateValue, valuesRef]); const sectionProps = useMemo(() => ({ ...pickSectionDomProps(htmlProps), ...pickSectionDomProps(sharedProps) }), [htmlProps, sharedProps]); const handlePaste = useCallback(event => { if (handledPasteTimestampRef.current === event.timeStamp) { return; } handledPasteTimestampRef.current = event.timeStamp; onPasteExternal === null || onPasteExternal === void 0 || onPasteExternal(event); if (event.defaultPrevented) { return; } const pastedValue = event.clipboardData.getData('text/plain'); if (!pastedValue) { return; } event.preventDefault(); const nextValues = pastedValue.length > mask.length || /[./\-\s]/.test(pastedValue) ? distributeValueFromStart({ value: pastedValue, inputs, existingValues: valuesRef.current }) : { ...valuesRef.current, [inputId]: extractValidChars(pastedValue, mask) }; Object.entries(nextValues).forEach(([key, nextValue]) => { onChange(key, nextValue); }); if (pastedValue.length > mask.length || /[./\-\s]/.test(pastedValue)) { const lastFilledSection = inputs.map(({ id }) => id).reverse().find(key => nextValues[key]); if (lastFilledSection) { focusSection(lastFilledSection, 'end'); } } else { setSectionCaret(inputId, Math.min(nextValues[inputId].length, mask.length)); } }, [focusSection, inputId, inputs, mask, onChange, onPasteExternal, setSectionCaret, valuesRef]); const clearWholeGroup = useCallback(() => { var _inputs$; if (!groupDelimiter) { return false; } inputs.forEach(({ id }) => { onChange(id, ''); sectionSelectionModeRef.current[id] = 'caret'; caretPositionsRef.current[id] = 0; }); clearGroupSelection(); const firstSectionId = (_inputs$ = inputs[0]) === null || _inputs$ === void 0 ? void 0 : _inputs$.id; if (firstSectionId) { focusSection(firstSectionId, 'start'); } else { setSectionCaret(inputId, 0); } return true; }, [caretPositionsRef, clearGroupSelection, focusSection, groupDelimiter, inputId, inputs, onChange, sectionSelectionModeRef, setSectionCaret]); return _jsxs(_Fragment, { children: [_jsx("span", { id: `${groupId}-${inputId}`, ref: setRef, className: clsx("dnb-input__input dnb-segmented-field__section", className, hasTypedValue && 'dnb-segmented-field__section--highlight'), "data-segmented-input-id": inputId, contentEditable: !disabled && !wholeGroupSelectionUi, suppressContentEditableWarning: true, role: spinButton ? 'spinbutton' : 'textbox', tabIndex: disabled ? -1 : 0, spellCheck: false, "aria-label": String(label), "aria-readonly": disabled, "aria-disabled": disabled, "aria-valuemin": spinButton ? spinButton.min : undefined, "aria-valuemax": spinButton ? spinButton.max : undefined, "aria-valuenow": spinButton && hasTypedValue ? (_spinButton$parseValu = spinButton === null || spinButton === void 0 || (_spinButton$parseValu2 = spinButton.parseValue) === null || _spinButton$parseValu2 === void 0 ? void 0 : _spinButton$parseValu2.call(spinButton, value)) !== null && _spinButton$parseValu !== void 0 ? _spinButton$parseValu : Number(value) : undefined, "aria-valuetext": spinButton ? hasTypedValue ? value : 'Empty' : undefined, onFocus: () => { clearGroupSelection(); onGroupFocus(); selectSection(inputId); }, onBlur: onGroupBlur, onMouseDown: event => { clearGroupSelection(); event.preventDefault(); if (document.activeElement !== sectionRefs.current[inputId]) { var _sectionRefs$current$; (_sectionRefs$current$ = sectionRefs.current[inputId]) === null || _sectionRefs$current$ === void 0 || _sectionRefs$current$.focus(); selectSection(inputId); return; } selectSection(inputId); }, onBeforeInput: event => { if (event.nativeEvent.inputType === 'insertParagraph' || event.nativeEvent.inputType === 'insertLineBreak') { event.preventDefault(); } }, onInput: event => { event.preventDefault(); }, onKeyDown: event => { if (disabled) { return; } const key = event.key; if ((event.metaKey || event.ctrlKey) && key.toLowerCase() === 'a') { event.preventDefault(); selectWholeGroup(inputId); return; } if ((event.metaKey || event.ctrlKey) && key.toLowerCase() === 'c') { return; } if (event.metaKey || event.ctrlKey) { return; } const hadWholeGroupSelected = wholeGroupSelectionUi; clearGroupSelection(); if (key === 'Enter') { event.preventDefault(); return; } if (key === 'Tab') { clearSectionSelection(); return; } if (key === 'ArrowRight') { event.preventDefault(); focusAdjacentSection('next'); return; } if (key === 'ArrowLeft') { event.preventDefault(); focusAdjacentSection('prev'); return; } if (key === 'ArrowUp' || key === 'ArrowDown') { event.preventDefault(); stepSpinButton(key === 'ArrowUp' ? 'up' : 'down'); return; } if (key === 'Backspace') { var _valuesRef$current$in3, _caretPositionsRef$cu4; event.preventDefault(); if (hadWholeGroupSelected) { clearWholeGroup(); return; } const isAllSelected = sectionSelectionModeRef.current[inputId] === 'all'; const currentValue = (_valuesRef$current$in3 = valuesRef.current[inputId]) !== null && _valuesRef$current$in3 !== void 0 ? _valuesRef$current$in3 : ''; const currentPosition = (_caretPositionsRef$cu4 = caretPositionsRef.current[inputId]) !== null && _caretPositionsRef$cu4 !== void 0 ? _caretPositionsRef$cu4 : 0; if (isAllSelected && currentValue.length > 0) { updateValue(''); selectSection(inputId); return; } if (currentValue.length === 0 || currentPosition <= 0) { const previousSectionId = getNextSectionId('prev', { withinGroup: true }); if (previousSectionId) { focusSection(previousSectionId, 'all'); } else { if (currentValue.length === 0) { focusAdjacentSection('prev'); } return; } return; } const nextValue = removeChar(currentValue, currentPosition - 1); updateValue(nextValue); setSectionCaret(inputId, currentPosition - 1); return; } if (key === 'Delete') { var _valuesRef$current$in4, _caretPositionsRef$cu5; event.preventDefault(); if (hadWholeGroupSelected) { clearWholeGroup(); return; } const isAllSelected = sectionSelectionModeRef.current[inputId] === 'all'; const currentValue = (_valuesRef$current$in4 = valuesRef.current[inputId]) !== null && _valuesRef$current$in4 !== void 0 ? _valuesRef$current$in4 : ''; const currentPosition = (_caretPositionsRef$cu5 = caretPositionsRef.current[inputId]) !== null && _caretPositionsRef$cu5 !== void 0 ? _caretPositionsRef$cu5 : 0; if (isAllSelected) { updateValue(''); selectSection(inputId); return; } const nextValue = removeChar(currentValue, currentPosition); updateValue(nextValue); setSectionCaret(inputId, currentPosition); return; } if (key.length === 1) { event.preventDefault(); if (hadWholeGroupSelected) { var _inputs$2; const firstSectionId = (_inputs$2 = inputs[0]) === null || _inputs$2 === void 0 ? void 0 : _inputs$2.id; if (firstSectionId && clearWholeGroup()) { insertCharIntoSection({ char: key, inputId: firstSectionId, overwriteMode, valuesRef, inputs, caretPositionsRef, sectionSelectionModeRef, onChange, focusSection, setSectionCaret }); return; } } replaceWithChar(key); return; } }, onCopy: event => { onCopyExternal === null || onCopyExternal === void 0 || onCopyExternal(event); if (event.defaultPrevented) { return; } event.preventDefault(); event.clipboardData.setData('text/plain', joinValues(valuesRef.current, groupDelimiter)); }, onPasteCapture: handlePaste, onPaste: handlePaste, ...sectionProps, children: displayValue }), delimiter && _jsx("span", { "aria-hidden": true, className: 'dnb-segmented-field__delimiter' + (hasTypedValue ? " dnb-segmented-field__delimiter--highlight" : ""), children: delimiter })] }); } //# sourceMappingURL=SegmentedFieldSection.js.map