periplo-ui 3.59.0 → 3.60.0
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.
- package/dist/components/InputDate/InputDate.js.map +1 -0
- package/dist/components/InputDate/MaskedInput.js.map +1 -0
- package/dist/components/InputDate/manualDateFormat.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/InputDateMask/InputDate.js.map +0 -1
- package/dist/components/InputDateMask/MaskedInput.js.map +0 -1
- package/dist/components/InputDateMask/manualDateFormat.js.map +0 -1
- /package/dist/components/{InputDateMask → InputDate}/InputDate.d.ts +0 -0
- /package/dist/components/{InputDateMask → InputDate}/InputDate.js +0 -0
- /package/dist/components/{InputDateMask → InputDate}/MaskedInput.d.ts +0 -0
- /package/dist/components/{InputDateMask → InputDate}/MaskedInput.js +0 -0
- /package/dist/components/{InputDateMask → InputDate}/index.d.ts +0 -0
- /package/dist/components/{InputDateMask → InputDate}/index.js +0 -0
- /package/dist/components/{InputDateMask → InputDate}/index.js.map +0 -0
- /package/dist/components/{InputDateMask → InputDate}/manualDateFormat.d.ts +0 -0
- /package/dist/components/{InputDateMask → InputDate}/manualDateFormat.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InputDate.js","sources":["../../../src/components/InputDate/InputDate.tsx"],"sourcesContent":["import { CalendarBlank } from '@phosphor-icons/react/dist/ssr/CalendarBlank'\nimport { WarningCircle } from '@phosphor-icons/react/dist/ssr/WarningCircle'\nimport { X } from '@phosphor-icons/react/dist/ssr/X'\nimport * as React from 'react'\n\nimport { type DateInputFormat } from './manualDateFormat'\nimport { MaskedInput } from './MaskedInput'\n\nimport { cn } from '@/lib/utils'\n\nexport type InputDateProps = {\n className?: string\n endContent?: React.ReactElement\n disabled?: boolean\n error?: boolean | string\n errorMessage?: string\n inputClassName?: string\n inputFormat?: DateInputFormat\n onClear?: boolean | (() => void)\n value?: string\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void\n} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'>\n\nconst InputDate = React.forwardRef<HTMLInputElement, InputDateProps>(\n (\n {\n className,\n endContent,\n disabled,\n error,\n errorMessage,\n value: controlledValue,\n onChange: controlledOnChange,\n inputClassName,\n onClear = true,\n inputFormat,\n 'aria-invalid': ariaInvalid,\n ...props\n },\n ref,\n ) => {\n const [internalValue, setInternalValue] = React.useState(controlledValue ?? '')\n const [isHovered, setIsHovered] = React.useState(false)\n\n React.useEffect(() => {\n setInternalValue(controlledValue ?? '')\n }, [controlledValue])\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setInternalValue(event.target.value)\n controlledOnChange?.(event)\n }\n\n const handleMaskedChange = (newValue: string) => {\n setInternalValue(newValue)\n controlledOnChange?.({\n target: { value: newValue },\n } as React.ChangeEvent<HTMLInputElement>)\n }\n\n const handleClear = () => {\n handleChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>)\n if (typeof onClear === 'function') onClear()\n }\n\n const hasValue = Boolean(internalValue && internalValue.trim() !== '')\n const showClearIcon = Boolean(onClear && hasValue && isHovered && !disabled)\n const resolvedErrorMessage = typeof error === 'string' ? error : errorMessage\n const hasError = Boolean(error ?? resolvedErrorMessage)\n\n const renderCalendarIcon = () => <CalendarBlank size={20} />\n\n const renderErrorIcon = () => {\n if (!hasError) return null\n return (\n <div>\n <WarningCircle\n data-testid=\"exclaim-icon\"\n className={cn(\n 'text-error-500 transition-opacity duration-150',\n showClearIcon ? 'opacity-0' : 'opacity-100',\n )}\n size={18}\n />\n </div>\n )\n }\n\n const renderClearIcon = () => {\n if (disabled || !onClear) return null\n return (\n <div\n className={cn(\n 'absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg',\n endContent || error\n ? 'bg-transparent'\n : 'bg-gradient-to-l from-white from-60% via-white/80 via-80% to-transparent transition-opacity duration-150',\n showClearIcon ? 'opacity-100' : 'opacity-0',\n )}\n >\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150',\n showClearIcon ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onClick={handleClear}\n />\n </div>\n )\n }\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <div className=\"flex w-full items-center\">\n <label\n data-testid=\"input-date-mask-control\"\n className={cn(\n 'relative flex h-12 w-full items-center rounded-lg border border-neutral-200 bg-white px-3 transition-colors focus-within:border-neutral-950',\n disabled && 'bg-neutral-50',\n hasError && 'border-error-400 focus-within:border-error-700',\n className,\n )}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {renderCalendarIcon()}\n <MaskedInput\n ref={ref}\n inputFormat={inputFormat}\n placeholder={inputFormat}\n aria-invalid={ariaInvalid ?? hasError}\n disabled={disabled}\n value={internalValue}\n onChange={handleMaskedChange}\n className={cn(\n 'w-full bg-transparent px-2 outline-0 transition-colors placeholder:text-neutral-300 disabled:cursor-not-allowed disabled:opacity-50',\n inputClassName,\n )}\n {...props}\n />\n {renderErrorIcon()}\n {renderClearIcon()}\n </label>\n </div>\n {resolvedErrorMessage && <span className=\"text-error-500 text-sm\">{resolvedErrorMessage}</span>}\n </div>\n )\n },\n)\n\nInputDate.displayName = 'InputDate'\n\nexport { InputDate }\n"],"names":[],"mappings":";;;;;;;;AAuBA,MAAM,YAAY,KAAA,CAAM,UAAA;AAAA,EACtB,CACE;AAAA,IACE,SAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,YAAA;AAAA,IACA,KAAA,EAAO,eAAA;AAAA,IACP,QAAA,EAAU,kBAAA;AAAA,IACV,cAAA;AAAA,IACA,OAAA,GAAU,IAAA;AAAA,IACV,WAAA;AAAA,IACA,cAAA,EAAgB,WAAA;AAAA,IAChB,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,IAAI,KAAA,CAAM,QAAA,CAAS,mBAAmB,EAAE,CAAA;AAC9E,IAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,KAAA,CAAM,SAAS,KAAK,CAAA;AAEtD,IAAA,KAAA,CAAM,UAAU,MAAM;AACpB,MAAA,gBAAA,CAAiB,mBAAmB,EAAE,CAAA;AAAA,KACxC,EAAG,CAAC,eAAe,CAAC,CAAA;AAEpB,IAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAA+C;AACnE,MAAA,gBAAA,CAAiB,KAAA,CAAM,OAAO,KAAK,CAAA;AACnC,MAAA,kBAAA,GAAqB,KAAK,CAAA;AAAA,KAC5B;AAEA,IAAA,MAAM,kBAAA,GAAqB,CAAC,QAAA,KAAqB;AAC/C,MAAA,gBAAA,CAAiB,QAAQ,CAAA;AACzB,MAAA,kBAAA,GAAqB;AAAA,QACnB,MAAA,EAAQ,EAAE,KAAA,EAAO,QAAA;AAAS,OACY,CAAA;AAAA,KAC1C;AAEA,IAAA,MAAM,cAAc,MAAM;AACxB,MAAA,YAAA,CAAa,EAAE,MAAA,EAAQ,EAAE,KAAA,EAAO,EAAA,IAA6C,CAAA;AAC7E,MAAA,IAAI,OAAO,OAAA,KAAY,UAAA,EAAY,OAAA,EAAQ;AAAA,KAC7C;AAEA,IAAA,MAAM,WAAW,OAAA,CAAQ,aAAA,IAAiB,aAAA,CAAc,IAAA,OAAW,EAAE,CAAA;AACrE,IAAA,MAAM,gBAAgB,OAAA,CAAQ,OAAA,IAAW,QAAA,IAAY,SAAA,IAAa,CAAC,QAAQ,CAAA;AAC3E,IAAA,MAAM,oBAAA,GAAuB,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,YAAA;AACjE,IAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,KAAA,IAAS,oBAAoB,CAAA;AAEtD,IAAA,MAAM,kBAAA,GAAqB,sBAAM,GAAA,CAAC,aAAA,EAAA,EAAc,MAAM,EAAA,EAAI,CAAA;AAE1D,IAAA,MAAM,kBAAkB,MAAM;AAC5B,MAAA,IAAI,CAAC,UAAU,OAAO,IAAA;AACtB,MAAA,2BACG,KAAA,EAAA,EACC,QAAA,kBAAA,GAAA;AAAA,QAAC,aAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAY,cAAA;AAAA,UACZ,SAAA,EAAW,EAAA;AAAA,YACT,gDAAA;AAAA,YACA,gBAAgB,WAAA,GAAc;AAAA,WAChC;AAAA,UACA,IAAA,EAAM;AAAA;AAAA,OACR,EACF,CAAA;AAAA,KAEJ;AAEA,IAAA,MAAM,kBAAkB,MAAM;AAC5B,MAAA,IAAI,QAAA,IAAY,CAAC,OAAA,EAAS,OAAO,IAAA;AACjC,MAAA,uBACE,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAW,EAAA;AAAA,YACT,8EAAA;AAAA,YACA,UAAA,IAAc,QACV,gBAAA,GACA,0GAAA;AAAA,YACJ,gBAAgB,aAAA,GAAgB;AAAA,WAClC;AAAA,UAEA,QAAA,kBAAA,GAAA;AAAA,YAAC,CAAA;AAAA,YAAA;AAAA,cACC,aAAA,EAAY,cAAA;AAAA,cACZ,SAAA,EAAW,EAAA;AAAA,gBACT,iEAAA;AAAA,gBACA,gBAAgB,8BAAA,GAAiC;AAAA,eACnD;AAAA,cACA,OAAA,EAAS;AAAA;AAAA;AACX;AAAA,OACF;AAAA,KAEJ;AAEA,IAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,4BAAA,EACb,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,0BAAA,EACb,QAAA,kBAAA,IAAA;AAAA,QAAC,OAAA;AAAA,QAAA;AAAA,UACC,aAAA,EAAY,yBAAA;AAAA,UACZ,SAAA,EAAW,EAAA;AAAA,YACT,6IAAA;AAAA,YACA,QAAA,IAAY,eAAA;AAAA,YACZ,QAAA,IAAY,gDAAA;AAAA,YACZ;AAAA,WACF;AAAA,UACA,YAAA,EAAc,MAAM,YAAA,CAAa,IAAI,CAAA;AAAA,UACrC,YAAA,EAAc,MAAM,YAAA,CAAa,KAAK,CAAA;AAAA,UAErC,QAAA,EAAA;AAAA,YAAA,kBAAA,EAAmB;AAAA,4BACpB,GAAA;AAAA,cAAC,WAAA;AAAA,cAAA;AAAA,gBACC,GAAA;AAAA,gBACA,WAAA;AAAA,gBACA,WAAA,EAAa,WAAA;AAAA,gBACb,gBAAc,WAAA,IAAe,QAAA;AAAA,gBAC7B,QAAA;AAAA,gBACA,KAAA,EAAO,aAAA;AAAA,gBACP,QAAA,EAAU,kBAAA;AAAA,gBACV,SAAA,EAAW,EAAA;AAAA,kBACT,qIAAA;AAAA,kBACA;AAAA,iBACF;AAAA,gBACC,GAAG;AAAA;AAAA,aACN;AAAA,YACC,eAAA,EAAgB;AAAA,YAChB,eAAA;AAAgB;AAAA;AAAA,OACnB,EACF,CAAA;AAAA,MACC,oBAAA,oBAAwB,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,0BAA0B,QAAA,EAAA,oBAAA,EAAqB;AAAA,KAAA,EAC1F,CAAA;AAAA;AAGN;AAEA,SAAA,CAAU,WAAA,GAAc,WAAA;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MaskedInput.js","sources":["../../../src/components/InputDate/MaskedInput.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nimport { normalizeManualInput, parseManualDateString } from './manualDateFormat'\n\nimport { cn } from '@/lib/utils'\n\nexport type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD'\n\ntype Props = {\n value: string\n onChange: (value: string) => void\n onBlur?: React.FocusEventHandler<HTMLInputElement>\n onFocus?: React.FocusEventHandler<HTMLInputElement>\n onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>\n placeholder?: string\n disabled?: boolean\n id?: string\n name?: string\n className?: string\n 'aria-invalid'?: React.InputHTMLAttributes<HTMLInputElement>['aria-invalid']\n inputFormat?: DateInputFormat\n}\n\ntype SegmentType = 'day' | 'month' | 'year'\n\ntype FormatConfig = {\n template: string\n order: [SegmentType, SegmentType, SegmentType]\n placeholders: Record<number, string>\n separators: [number, number]\n}\n\nconst FORMAT_CONFIG: Record<DateInputFormat, FormatConfig> = {\n 'DD/MM/YYYY': {\n template: 'DD/MM/YYYY',\n order: ['day', 'month', 'year'],\n placeholders: { 0: 'D', 1: 'D', 3: 'M', 4: 'M', 6: 'Y', 7: 'Y', 8: 'Y', 9: 'Y' },\n separators: [2, 5],\n },\n 'MM/DD/YYYY': {\n template: 'MM/DD/YYYY',\n order: ['month', 'day', 'year'],\n placeholders: { 0: 'M', 1: 'M', 3: 'D', 4: 'D', 6: 'Y', 7: 'Y', 8: 'Y', 9: 'Y' },\n separators: [2, 5],\n },\n 'YYYY/MM/DD': {\n template: 'YYYY/MM/DD',\n order: ['year', 'month', 'day'],\n placeholders: { 0: 'Y', 1: 'Y', 2: 'Y', 3: 'Y', 5: 'M', 6: 'M', 8: 'D', 9: 'D' },\n separators: [4, 7],\n },\n}\n\nconst getSegmentIndex = (pos: number, s1: number, s2: number): number => {\n if (pos < s1) return 0\n if (pos < s2) return 1\n return 2\n}\n\nconst getFirstPosOfSegment = (segmentIndex: number, separators: [number, number]): number => {\n const [sep1, sep2] = separators\n if (segmentIndex === 0) return 0\n if (segmentIndex === 1) return sep1 + 1\n return sep2 + 1\n}\n\nconst applyFirstDigitAutopad = (\n newChars: Array<string>,\n targetPos: number,\n digit: string,\n segmentType: SegmentType,\n): number => {\n const shouldPadDay = segmentType === 'day' && Number.parseInt(digit) > 3\n const shouldPadMonth = segmentType === 'month' && Number.parseInt(digit) > 1\n\n if (shouldPadDay || shouldPadMonth) {\n newChars[targetPos] = '0'\n newChars[targetPos + 1] = digit\n return targetPos + 1\n }\n return targetPos\n}\n\nconst validateSecondDigit = (newChars: Array<string>, targetPos: number, segmentType: SegmentType): boolean => {\n const firstPos = targetPos - 1\n if (segmentType === 'day') {\n const day = Number.parseInt(newChars[firstPos] + newChars[targetPos])\n return newChars[firstPos] === 'D' || day <= 31\n }\n if (segmentType === 'month') {\n const month = Number.parseInt(newChars[firstPos] + newChars[targetPos])\n return newChars[firstPos] === 'M' || month <= 12\n }\n return true\n}\n\nconst validateYearDigit = (\n newChars: Array<string>,\n targetPos: number,\n digit: string,\n yearFirstPos: number,\n): boolean => {\n const yearDigitIndex = targetPos - yearFirstPos\n\n if (yearDigitIndex === 0) {\n return digit === '1' || digit === '2'\n }\n\n if (yearDigitIndex === 1) {\n const firstDigit = newChars[yearFirstPos]\n if (firstDigit === '1') return digit === '9'\n if (firstDigit === '2') return digit === '0'\n }\n\n if (yearDigitIndex === 2) {\n const twoDigits = newChars[yearFirstPos] + newChars[yearFirstPos + 1]\n if (twoDigits === '20') return Number.parseInt(digit) <= 5\n }\n\n if (yearDigitIndex === 3) {\n const yearStr = newChars.slice(yearFirstPos, yearFirstPos + 3).join('') + digit\n const year = Number.parseInt(yearStr, 10)\n return !Number.isNaN(year) && year >= 1922 && year <= 2300\n }\n\n return true\n}\n\nexport const MaskedInput = React.forwardRef<HTMLInputElement, Props>(\n (\n { value, onChange, placeholder, inputFormat = 'DD/MM/YYYY', onBlur, onFocus, onKeyDown, className, ...rest },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n React.useImperativeHandle(ref, () => inputRef.current!)\n\n const config = FORMAT_CONFIG[inputFormat]\n const [sep1, sep2] = config.separators\n\n const maxPos = 9\n\n const getNextPos = (pos: number): number => {\n const next = pos + 1\n if (next === sep1 || next === sep2) return next + 1\n return Math.min(next, maxPos + 1)\n }\n\n const handleDelete = (pos: number, isDelete: boolean) => {\n const input = inputRef.current\n if (!input) return\n\n let targetPos = isDelete ? pos : pos - 1\n if (targetPos === sep1 || targetPos === sep2) {\n targetPos = isDelete ? targetPos + 1 : targetPos - 1\n }\n if (targetPos < 0 || targetPos > maxPos) return\n\n const chars = (value || config.template).split('')\n chars[targetPos] = config.placeholders[targetPos] ?? chars[targetPos]\n\n const newValue = chars.join('')\n onChange(newValue === config.template ? '' : newValue)\n\n requestAnimationFrame(() => {\n input.setSelectionRange(targetPos, targetPos)\n })\n }\n\n const handleDigit = (pos: number, digit: string) => {\n const input = inputRef.current\n if (!input) return\n\n let targetPos = pos\n if (targetPos === sep1 || targetPos === sep2) targetPos++\n if (targetPos > maxPos) return\n\n const chars = (value || config.template).split('')\n const newChars = [...chars]\n newChars[targetPos] = digit\n\n const segmentIndex = getSegmentIndex(targetPos, sep1, sep2)\n const segmentType = config.order[segmentIndex]\n const firstPosOfSegment = getFirstPosOfSegment(segmentIndex, config.separators)\n const isFirstDigit = targetPos === firstPosOfSegment\n const isSecondDigit = targetPos === firstPosOfSegment + 1\n\n if (isFirstDigit) {\n targetPos = applyFirstDigitAutopad(newChars, targetPos, digit, segmentType)\n }\n\n if (isSecondDigit && !validateSecondDigit(newChars, targetPos, segmentType)) {\n return\n }\n\n if (segmentType === 'year' && !validateYearDigit(newChars, targetPos, digit, firstPosOfSegment)) {\n return\n }\n\n newChars[targetPos] = digit\n const nextPos = getNextPos(targetPos)\n const result = newChars.join('')\n onChange(result === config.template ? '' : result)\n\n requestAnimationFrame(() => {\n input.setSelectionRange(nextPos, nextPos)\n })\n }\n\n const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const trimmed = (value || '').trim()\n if (trimmed) {\n const normalized = normalizeManualInput(trimmed, inputFormat)\n const parsed = parseManualDateString(normalized, inputFormat)\n if (!parsed) {\n onChange('')\n } else if (normalized !== value) {\n onChange(normalized)\n }\n }\n onBlur?.(event)\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event)\n\n const input = inputRef.current\n if (!input) return\n\n const pos = input.selectionStart\n if (pos === null) return\n\n if (\n event.key === 'Tab' ||\n event.key === 'ArrowLeft' ||\n event.key === 'ArrowRight' ||\n event.key === 'Enter' ||\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.ctrlKey ||\n event.metaKey\n ) {\n return\n }\n\n event.preventDefault()\n\n if (event.key === 'Backspace' || event.key === 'Delete') {\n handleDelete(pos, event.key === 'Delete')\n return\n }\n\n if (!/^\\d$/.test(event.key)) return\n handleDigit(pos, event.key)\n }\n\n const handleClick = () => {\n const input = inputRef.current\n if (!input) return\n\n const pos = input.selectionStart ?? 0\n\n const getTargetPos = (position: number): number => {\n if (position === sep1) return position - 1\n if (position === sep2) return position - 1\n return position\n }\n\n requestAnimationFrame(() => {\n input.setSelectionRange(getTargetPos(pos), getTargetPos(pos))\n })\n }\n const renderOverlay = () => {\n if (!value) return null\n\n const display = value.padEnd(10, ' ')\n const template = config.template\n\n return (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 flex items-center px-2 text-base select-none\"\n >\n {display.split('').map((char, index) => {\n const isPlaceholderChar = template[index] !== '/' && char === template[index]\n\n const stableKey = `${template[index]}-${index}`\n return (\n <span key={stableKey} className={isPlaceholderChar ? 'text-neutral-300' : 'text-neutral-900'}>\n {char}\n </span>\n )\n })}\n </div>\n )\n }\n\n return (\n <div className=\"relative flex w-full items-center\">\n <input\n ref={inputRef}\n type=\"text\"\n autoComplete=\"off\"\n value={value || ''}\n placeholder={placeholder ?? inputFormat}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n onChange={() => {}}\n onBlur={handleBlur}\n onFocus={onFocus}\n size={1}\n className={cn(className, value ? 'text-transparent caret-neutral-900' : '')}\n {...rest}\n />\n {renderOverlay()}\n </div>\n )\n },\n)\n\nMaskedInput.displayName = 'MaskedInput'\n"],"names":[],"mappings":";;;;;;AAkCA;AAA6D;AAC7C;AACF;AACoB;AACiD;AAC9D;AACnB;AACc;AACF;AACoB;AACiD;AAC9D;AACnB;AACc;AACF;AACoB;AACiD;AAC9D;AAErB;AAEA;AACE;AACA;AACA;AACF;AAEA;AACE;AACA;AACA;AACA;AACF;AAEA;AAME;AACA;AAEA;AACE;AACA;AACA;AAAmB;AAErB;AACF;AAEA;AACE;AACA;AACE;AACA;AAA4C;AAE9C;AACE;AACA;AAA8C;AAEhD;AACF;AAEA;AAME;AAEA;AACE;AAAkC;AAGpC;AACE;AACA;AACA;AAAyC;AAG3C;AACE;AACA;AAAyD;AAG3D;AACE;AACA;AACA;AAAsD;AAGxD;AACF;AAEO;AAA0B;AAK7B;AACA;AAEA;AACA;AAEA;AAEA;AACE;AACA;AACA;AAAgC;AAGlC;AACE;AACA;AAEA;AACA;AACE;AAAmD;AAErD;AAEA;AACA;AAEA;AACA;AAEA;AACE;AAA4C;AAC7C;AAGH;AACE;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACE;AAA0E;AAG5E;AACE;AAAA;AAGF;AACE;AAAA;AAGF;AACA;AACA;AACA;AAEA;AACE;AAAwC;AACzC;AAGH;AACE;AACA;AACE;AACA;AACA;AACE;AAAW;AAEX;AAAmB;AACrB;AAEF;AAAc;AAGhB;AACE;AAEA;AACA;AAEA;AACA;AAEA;AAUE;AAAA;AAGF;AAEA;AACE;AACA;AAAA;AAGF;AACA;AAA0B;AAG5B;AACE;AACA;AAEA;AAEA;AACE;AACA;AACA;AAAO;AAGT;AACE;AAA4D;AAC7D;AAEH;AACE;AAEA;AACA;AAEA;AACE;AAAC;AAAA;AACa;AACF;AAGR;AAEA;AACA;AAGE;AAEH;AAAA;AACH;AAIJ;AAEI;AAAA;AAAC;AAAA;AACM;AACA;AACQ;AACG;AACY;AACjB;AACF;AACO;AAAC;AACT;AACR;AACM;AACoE;AACtE;AAAA;AACN;AACe;AACjB;AAGN;AAEA;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manualDateFormat.js","sources":["../../../src/components/InputDate/manualDateFormat.ts"],"sourcesContent":["import { isValid, parseISO } from 'date-fns'\n\nexport type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD'\n\ntype DateSegment = 'day' | 'month' | 'year'\n\ntype FormatRow = {\n dateFns: string\n segmentOrder: [DateSegment, DateSegment, DateSegment]\n}\n\nexport const DATE_INPUT_FORMATS: Record<DateInputFormat, FormatRow> = {\n 'DD/MM/YYYY': {\n dateFns: 'dd/MM/yyyy',\n segmentOrder: ['day', 'month', 'year'],\n },\n 'MM/DD/YYYY': {\n dateFns: 'MM/dd/yyyy',\n segmentOrder: ['month', 'day', 'year'],\n },\n 'YYYY/MM/DD': {\n dateFns: 'yyyy/MM/dd',\n segmentOrder: ['year', 'month', 'day'],\n },\n}\n\nexport const getDateInputDateFns = (format: DateInputFormat): string => DATE_INPUT_FORMATS[format].dateFns\n\nexport function parseManualDateString(raw: string, format: DateInputFormat): Date | undefined {\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n const parsed = parseISO(trimmed)\n return isValid(parsed) ? parsed : undefined\n }\n\n const parts = trimmed.split('/')\n if (parts.length !== 3) return undefined\n\n const { segmentOrder } = DATE_INPUT_FORMATS[format]\n const dayIdx = segmentOrder.indexOf('day')\n const monthIdx = segmentOrder.indexOf('month')\n const yearIdx = segmentOrder.indexOf('year')\n if (dayIdx === -1 || monthIdx === -1 || yearIdx === -1) return undefined\n\n const day = Number.parseInt(parts[dayIdx], 10)\n const month = Number.parseInt(parts[monthIdx], 10)\n const year = Number.parseInt(parts[yearIdx], 10)\n if (Number.isNaN(day) || Number.isNaN(month) || Number.isNaN(year)) return undefined\n\n const date = new Date(year, month - 1, day)\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return undefined\n }\n return date\n}\n\nexport function normalizeManualInput(text: string, format: DateInputFormat): string {\n const parts = text.split('/')\n if (parts.length !== 3) return ''\n\n const { segmentOrder } = DATE_INPUT_FORMATS[format]\n const parsed: Record<DateSegment, string | null> = { day: null, month: null, year: null }\n\n parts.forEach((part, index) => {\n const type = segmentOrder[index]\n const isYear = type === 'year'\n const digits = part.replaceAll(/[DMY]/g, '').trim()\n if (!digits) return\n if (isYear && digits.length < 4) return\n parsed[type] = digits.padStart(isYear ? 4 : 2, '0')\n })\n\n if (parsed.day == null || parsed.month == null || parsed.year == null) return ''\n\n return segmentOrder.map((type) => parsed[type]).join('/')\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -44,4 +44,5 @@ export { ToggleGroup, ToggleGroupItem } from './components/ToggleGroup/ToggleGro
|
|
|
44
44
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from './components/Tooltip/Tooltip.js';
|
|
45
45
|
export { TruncatedTypographyWithTooltip } from './components/TruncatedTypographyWithTooltip/TruncatedTypographyWithTooltip.js';
|
|
46
46
|
export { Typography, typographyVariants } from './components/Typography/Typography.js';
|
|
47
|
+
export { InputDate } from './components/InputDate/InputDate.js';
|
|
47
48
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InputDate.js","sources":["../../../src/components/InputDateMask/InputDate.tsx"],"sourcesContent":["import { CalendarBlank } from '@phosphor-icons/react/dist/ssr/CalendarBlank'\nimport { WarningCircle } from '@phosphor-icons/react/dist/ssr/WarningCircle'\nimport { X } from '@phosphor-icons/react/dist/ssr/X'\nimport * as React from 'react'\n\nimport { type DateInputFormat } from './manualDateFormat'\nimport { MaskedInput } from './MaskedInput'\n\nimport { cn } from '@/lib/utils'\n\nexport type InputDateProps = {\n className?: string\n endContent?: React.ReactElement\n disabled?: boolean\n error?: boolean | string\n errorMessage?: string\n inputClassName?: string\n inputFormat?: DateInputFormat\n onClear?: boolean | (() => void)\n value?: string\n onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void\n} & Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onChange' | 'type'>\n\nconst InputDate = React.forwardRef<HTMLInputElement, InputDateProps>(\n (\n {\n className,\n endContent,\n disabled,\n error,\n errorMessage,\n value: controlledValue,\n onChange: controlledOnChange,\n inputClassName,\n onClear = true,\n inputFormat,\n 'aria-invalid': ariaInvalid,\n ...props\n },\n ref,\n ) => {\n const [internalValue, setInternalValue] = React.useState(controlledValue ?? '')\n const [isHovered, setIsHovered] = React.useState(false)\n\n React.useEffect(() => {\n setInternalValue(controlledValue ?? '')\n }, [controlledValue])\n\n const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n setInternalValue(event.target.value)\n controlledOnChange?.(event)\n }\n\n const handleMaskedChange = (newValue: string) => {\n setInternalValue(newValue)\n controlledOnChange?.({\n target: { value: newValue },\n } as React.ChangeEvent<HTMLInputElement>)\n }\n\n const handleClear = () => {\n handleChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>)\n if (typeof onClear === 'function') onClear()\n }\n\n const hasValue = Boolean(internalValue && internalValue.trim() !== '')\n const showClearIcon = Boolean(onClear && hasValue && isHovered && !disabled)\n const resolvedErrorMessage = typeof error === 'string' ? error : errorMessage\n const hasError = Boolean(error ?? resolvedErrorMessage)\n\n const renderCalendarIcon = () => <CalendarBlank size={20} />\n\n const renderErrorIcon = () => {\n if (!hasError) return null\n return (\n <div>\n <WarningCircle\n data-testid=\"exclaim-icon\"\n className={cn(\n 'text-error-500 transition-opacity duration-150',\n showClearIcon ? 'opacity-0' : 'opacity-100',\n )}\n size={18}\n />\n </div>\n )\n }\n\n const renderClearIcon = () => {\n if (disabled || !onClear) return null\n return (\n <div\n className={cn(\n 'absolute right-3 z-10 flex h-full w-10 items-center justify-end rounded-r-lg',\n endContent || error\n ? 'bg-transparent'\n : 'bg-gradient-to-l from-white from-60% via-white/80 via-80% to-transparent transition-opacity duration-150',\n showClearIcon ? 'opacity-100' : 'opacity-0',\n )}\n >\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150',\n showClearIcon ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onClick={handleClear}\n />\n </div>\n )\n }\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <div className=\"flex w-full items-center\">\n <label\n data-testid=\"input-date-mask-control\"\n className={cn(\n 'relative flex h-12 w-full items-center rounded-lg border border-neutral-200 bg-white px-3 transition-colors focus-within:border-neutral-950',\n disabled && 'bg-neutral-50',\n hasError && 'border-error-400 focus-within:border-error-700',\n className,\n )}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {renderCalendarIcon()}\n <MaskedInput\n ref={ref}\n inputFormat={inputFormat}\n placeholder={inputFormat}\n aria-invalid={ariaInvalid ?? hasError}\n disabled={disabled}\n value={internalValue}\n onChange={handleMaskedChange}\n className={cn(\n 'w-full bg-transparent px-2 outline-0 transition-colors placeholder:text-neutral-300 disabled:cursor-not-allowed disabled:opacity-50',\n inputClassName,\n )}\n {...props}\n />\n {renderErrorIcon()}\n {renderClearIcon()}\n </label>\n </div>\n {resolvedErrorMessage && <span className=\"text-error-500 text-sm\">{resolvedErrorMessage}</span>}\n </div>\n )\n },\n)\n\nInputDate.displayName = 'InputDate'\n\nexport { InputDate }\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"MaskedInput.js","sources":["../../../src/components/InputDateMask/MaskedInput.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\nimport { normalizeManualInput, parseManualDateString } from './manualDateFormat'\n\nimport { cn } from '@/lib/utils'\n\nexport type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD'\n\ntype Props = {\n value: string\n onChange: (value: string) => void\n onBlur?: React.FocusEventHandler<HTMLInputElement>\n onFocus?: React.FocusEventHandler<HTMLInputElement>\n onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>\n placeholder?: string\n disabled?: boolean\n id?: string\n name?: string\n className?: string\n 'aria-invalid'?: React.InputHTMLAttributes<HTMLInputElement>['aria-invalid']\n inputFormat?: DateInputFormat\n}\n\ntype SegmentType = 'day' | 'month' | 'year'\n\ntype FormatConfig = {\n template: string\n order: [SegmentType, SegmentType, SegmentType]\n placeholders: Record<number, string>\n separators: [number, number]\n}\n\nconst FORMAT_CONFIG: Record<DateInputFormat, FormatConfig> = {\n 'DD/MM/YYYY': {\n template: 'DD/MM/YYYY',\n order: ['day', 'month', 'year'],\n placeholders: { 0: 'D', 1: 'D', 3: 'M', 4: 'M', 6: 'Y', 7: 'Y', 8: 'Y', 9: 'Y' },\n separators: [2, 5],\n },\n 'MM/DD/YYYY': {\n template: 'MM/DD/YYYY',\n order: ['month', 'day', 'year'],\n placeholders: { 0: 'M', 1: 'M', 3: 'D', 4: 'D', 6: 'Y', 7: 'Y', 8: 'Y', 9: 'Y' },\n separators: [2, 5],\n },\n 'YYYY/MM/DD': {\n template: 'YYYY/MM/DD',\n order: ['year', 'month', 'day'],\n placeholders: { 0: 'Y', 1: 'Y', 2: 'Y', 3: 'Y', 5: 'M', 6: 'M', 8: 'D', 9: 'D' },\n separators: [4, 7],\n },\n}\n\nconst getSegmentIndex = (pos: number, s1: number, s2: number): number => {\n if (pos < s1) return 0\n if (pos < s2) return 1\n return 2\n}\n\nconst getFirstPosOfSegment = (segmentIndex: number, separators: [number, number]): number => {\n const [sep1, sep2] = separators\n if (segmentIndex === 0) return 0\n if (segmentIndex === 1) return sep1 + 1\n return sep2 + 1\n}\n\nconst applyFirstDigitAutopad = (\n newChars: Array<string>,\n targetPos: number,\n digit: string,\n segmentType: SegmentType,\n): number => {\n const shouldPadDay = segmentType === 'day' && Number.parseInt(digit) > 3\n const shouldPadMonth = segmentType === 'month' && Number.parseInt(digit) > 1\n\n if (shouldPadDay || shouldPadMonth) {\n newChars[targetPos] = '0'\n newChars[targetPos + 1] = digit\n return targetPos + 1\n }\n return targetPos\n}\n\nconst validateSecondDigit = (newChars: Array<string>, targetPos: number, segmentType: SegmentType): boolean => {\n const firstPos = targetPos - 1\n if (segmentType === 'day') {\n const day = Number.parseInt(newChars[firstPos] + newChars[targetPos])\n return newChars[firstPos] === 'D' || day <= 31\n }\n if (segmentType === 'month') {\n const month = Number.parseInt(newChars[firstPos] + newChars[targetPos])\n return newChars[firstPos] === 'M' || month <= 12\n }\n return true\n}\n\nconst validateYearDigit = (\n newChars: Array<string>,\n targetPos: number,\n digit: string,\n yearFirstPos: number,\n): boolean => {\n const yearDigitIndex = targetPos - yearFirstPos\n\n if (yearDigitIndex === 0) {\n return digit === '1' || digit === '2'\n }\n\n if (yearDigitIndex === 1) {\n const firstDigit = newChars[yearFirstPos]\n if (firstDigit === '1') return digit === '9'\n if (firstDigit === '2') return digit === '0'\n }\n\n if (yearDigitIndex === 2) {\n const twoDigits = newChars[yearFirstPos] + newChars[yearFirstPos + 1]\n if (twoDigits === '20') return Number.parseInt(digit) <= 5\n }\n\n if (yearDigitIndex === 3) {\n const yearStr = newChars.slice(yearFirstPos, yearFirstPos + 3).join('') + digit\n const year = Number.parseInt(yearStr, 10)\n return !Number.isNaN(year) && year >= 1922 && year <= 2300\n }\n\n return true\n}\n\nexport const MaskedInput = React.forwardRef<HTMLInputElement, Props>(\n (\n { value, onChange, placeholder, inputFormat = 'DD/MM/YYYY', onBlur, onFocus, onKeyDown, className, ...rest },\n ref,\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null)\n React.useImperativeHandle(ref, () => inputRef.current!)\n\n const config = FORMAT_CONFIG[inputFormat]\n const [sep1, sep2] = config.separators\n\n const maxPos = 9\n\n const getNextPos = (pos: number): number => {\n const next = pos + 1\n if (next === sep1 || next === sep2) return next + 1\n return Math.min(next, maxPos + 1)\n }\n\n const handleDelete = (pos: number, isDelete: boolean) => {\n const input = inputRef.current\n if (!input) return\n\n let targetPos = isDelete ? pos : pos - 1\n if (targetPos === sep1 || targetPos === sep2) {\n targetPos = isDelete ? targetPos + 1 : targetPos - 1\n }\n if (targetPos < 0 || targetPos > maxPos) return\n\n const chars = (value || config.template).split('')\n chars[targetPos] = config.placeholders[targetPos] ?? chars[targetPos]\n\n const newValue = chars.join('')\n onChange(newValue === config.template ? '' : newValue)\n\n requestAnimationFrame(() => {\n input.setSelectionRange(targetPos, targetPos)\n })\n }\n\n const handleDigit = (pos: number, digit: string) => {\n const input = inputRef.current\n if (!input) return\n\n let targetPos = pos\n if (targetPos === sep1 || targetPos === sep2) targetPos++\n if (targetPos > maxPos) return\n\n const chars = (value || config.template).split('')\n const newChars = [...chars]\n newChars[targetPos] = digit\n\n const segmentIndex = getSegmentIndex(targetPos, sep1, sep2)\n const segmentType = config.order[segmentIndex]\n const firstPosOfSegment = getFirstPosOfSegment(segmentIndex, config.separators)\n const isFirstDigit = targetPos === firstPosOfSegment\n const isSecondDigit = targetPos === firstPosOfSegment + 1\n\n if (isFirstDigit) {\n targetPos = applyFirstDigitAutopad(newChars, targetPos, digit, segmentType)\n }\n\n if (isSecondDigit && !validateSecondDigit(newChars, targetPos, segmentType)) {\n return\n }\n\n if (segmentType === 'year' && !validateYearDigit(newChars, targetPos, digit, firstPosOfSegment)) {\n return\n }\n\n newChars[targetPos] = digit\n const nextPos = getNextPos(targetPos)\n const result = newChars.join('')\n onChange(result === config.template ? '' : result)\n\n requestAnimationFrame(() => {\n input.setSelectionRange(nextPos, nextPos)\n })\n }\n\n const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {\n const trimmed = (value || '').trim()\n if (trimmed) {\n const normalized = normalizeManualInput(trimmed, inputFormat)\n const parsed = parseManualDateString(normalized, inputFormat)\n if (!parsed) {\n onChange('')\n } else if (normalized !== value) {\n onChange(normalized)\n }\n }\n onBlur?.(event)\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {\n onKeyDown?.(event)\n\n const input = inputRef.current\n if (!input) return\n\n const pos = input.selectionStart\n if (pos === null) return\n\n if (\n event.key === 'Tab' ||\n event.key === 'ArrowLeft' ||\n event.key === 'ArrowRight' ||\n event.key === 'Enter' ||\n event.key === 'ArrowUp' ||\n event.key === 'ArrowDown' ||\n event.ctrlKey ||\n event.metaKey\n ) {\n return\n }\n\n event.preventDefault()\n\n if (event.key === 'Backspace' || event.key === 'Delete') {\n handleDelete(pos, event.key === 'Delete')\n return\n }\n\n if (!/^\\d$/.test(event.key)) return\n handleDigit(pos, event.key)\n }\n\n const handleClick = () => {\n const input = inputRef.current\n if (!input) return\n\n const pos = input.selectionStart ?? 0\n\n const getTargetPos = (position: number): number => {\n if (position === sep1) return position - 1\n if (position === sep2) return position - 1\n return position\n }\n\n requestAnimationFrame(() => {\n input.setSelectionRange(getTargetPos(pos), getTargetPos(pos))\n })\n }\n const renderOverlay = () => {\n if (!value) return null\n\n const display = value.padEnd(10, ' ')\n const template = config.template\n\n return (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 flex items-center px-2 text-base select-none\"\n >\n {display.split('').map((char, index) => {\n const isPlaceholderChar = template[index] !== '/' && char === template[index]\n\n const stableKey = `${template[index]}-${index}`\n return (\n <span key={stableKey} className={isPlaceholderChar ? 'text-neutral-300' : 'text-neutral-900'}>\n {char}\n </span>\n )\n })}\n </div>\n )\n }\n\n return (\n <div className=\"relative flex w-full items-center\">\n <input\n ref={inputRef}\n type=\"text\"\n autoComplete=\"off\"\n value={value || ''}\n placeholder={placeholder ?? inputFormat}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n onChange={() => {}}\n onBlur={handleBlur}\n onFocus={onFocus}\n size={1}\n className={cn(className, value ? 'text-transparent caret-neutral-900' : '')}\n {...rest}\n />\n {renderOverlay()}\n </div>\n )\n },\n)\n\nMaskedInput.displayName = 'MaskedInput'\n"],"names":[],"mappings":";;;;;;AAkCA;AAA6D;AAC7C;AACF;AACoB;AACiD;AAC9D;AACnB;AACc;AACF;AACoB;AACiD;AAC9D;AACnB;AACc;AACF;AACoB;AACiD;AAC9D;AAErB;AAEA;AACE;AACA;AACA;AACF;AAEA;AACE;AACA;AACA;AACA;AACF;AAEA;AAME;AACA;AAEA;AACE;AACA;AACA;AAAmB;AAErB;AACF;AAEA;AACE;AACA;AACE;AACA;AAA4C;AAE9C;AACE;AACA;AAA8C;AAEhD;AACF;AAEA;AAME;AAEA;AACE;AAAkC;AAGpC;AACE;AACA;AACA;AAAyC;AAG3C;AACE;AACA;AAAyD;AAG3D;AACE;AACA;AACA;AAAsD;AAGxD;AACF;AAEO;AAA0B;AAK7B;AACA;AAEA;AACA;AAEA;AAEA;AACE;AACA;AACA;AAAgC;AAGlC;AACE;AACA;AAEA;AACA;AACE;AAAmD;AAErD;AAEA;AACA;AAEA;AACA;AAEA;AACE;AAA4C;AAC7C;AAGH;AACE;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACE;AAA0E;AAG5E;AACE;AAAA;AAGF;AACE;AAAA;AAGF;AACA;AACA;AACA;AAEA;AACE;AAAwC;AACzC;AAGH;AACE;AACA;AACE;AACA;AACA;AACE;AAAW;AAEX;AAAmB;AACrB;AAEF;AAAc;AAGhB;AACE;AAEA;AACA;AAEA;AACA;AAEA;AAUE;AAAA;AAGF;AAEA;AACE;AACA;AAAA;AAGF;AACA;AAA0B;AAG5B;AACE;AACA;AAEA;AAEA;AACE;AACA;AACA;AAAO;AAGT;AACE;AAA4D;AAC7D;AAEH;AACE;AAEA;AACA;AAEA;AACE;AAAC;AAAA;AACa;AACF;AAGR;AAEA;AACA;AAGE;AAEH;AAAA;AACH;AAIJ;AAEI;AAAA;AAAC;AAAA;AACM;AACA;AACQ;AACG;AACY;AACjB;AACF;AACO;AAAC;AACT;AACR;AACM;AACoE;AACtE;AAAA;AACN;AACe;AACjB;AAGN;AAEA;;"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"manualDateFormat.js","sources":["../../../src/components/InputDateMask/manualDateFormat.ts"],"sourcesContent":["import { isValid, parseISO } from 'date-fns'\n\nexport type DateInputFormat = 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD'\n\ntype DateSegment = 'day' | 'month' | 'year'\n\ntype FormatRow = {\n dateFns: string\n segmentOrder: [DateSegment, DateSegment, DateSegment]\n}\n\nexport const DATE_INPUT_FORMATS: Record<DateInputFormat, FormatRow> = {\n 'DD/MM/YYYY': {\n dateFns: 'dd/MM/yyyy',\n segmentOrder: ['day', 'month', 'year'],\n },\n 'MM/DD/YYYY': {\n dateFns: 'MM/dd/yyyy',\n segmentOrder: ['month', 'day', 'year'],\n },\n 'YYYY/MM/DD': {\n dateFns: 'yyyy/MM/dd',\n segmentOrder: ['year', 'month', 'day'],\n },\n}\n\nexport const getDateInputDateFns = (format: DateInputFormat): string => DATE_INPUT_FORMATS[format].dateFns\n\nexport function parseManualDateString(raw: string, format: DateInputFormat): Date | undefined {\n const trimmed = raw.trim()\n if (!trimmed) return undefined\n\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n const parsed = parseISO(trimmed)\n return isValid(parsed) ? parsed : undefined\n }\n\n const parts = trimmed.split('/')\n if (parts.length !== 3) return undefined\n\n const { segmentOrder } = DATE_INPUT_FORMATS[format]\n const dayIdx = segmentOrder.indexOf('day')\n const monthIdx = segmentOrder.indexOf('month')\n const yearIdx = segmentOrder.indexOf('year')\n if (dayIdx === -1 || monthIdx === -1 || yearIdx === -1) return undefined\n\n const day = Number.parseInt(parts[dayIdx], 10)\n const month = Number.parseInt(parts[monthIdx], 10)\n const year = Number.parseInt(parts[yearIdx], 10)\n if (Number.isNaN(day) || Number.isNaN(month) || Number.isNaN(year)) return undefined\n\n const date = new Date(year, month - 1, day)\n if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {\n return undefined\n }\n return date\n}\n\nexport function normalizeManualInput(text: string, format: DateInputFormat): string {\n const parts = text.split('/')\n if (parts.length !== 3) return ''\n\n const { segmentOrder } = DATE_INPUT_FORMATS[format]\n const parsed: Record<DateSegment, string | null> = { day: null, month: null, year: null }\n\n parts.forEach((part, index) => {\n const type = segmentOrder[index]\n const isYear = type === 'year'\n const digits = part.replaceAll(/[DMY]/g, '').trim()\n if (!digits) return\n if (isYear && digits.length < 4) return\n parsed[type] = digits.padStart(isYear ? 4 : 2, '0')\n })\n\n if (parsed.day == null || parsed.month == null || parsed.year == null) return ''\n\n return segmentOrder.map((type) => parsed[type]).join('/')\n}\n"],"names":[],"mappings":"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"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|