smarthr-ui 99.7.0 → 99.7.2
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/lib/components/Checkbox/client/ActualCheckbox.cjs +2 -1
- package/lib/components/Checkbox/client/ActualCheckbox.cjs.map +1 -1
- package/lib/components/Checkbox/client/ActualCheckbox.js +2 -1
- package/lib/components/Checkbox/client/ActualCheckbox.js.map +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs +2 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.cjs.map +1 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.js +2 -1
- package/lib/components/Combobox/MultiCombobox/MultiCombobox.js.map +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs +6 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs.map +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js +6 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js.map +1 -1
- package/lib/components/DropZone/client/DropZone.cjs +2 -1
- package/lib/components/DropZone/client/DropZone.cjs.map +1 -1
- package/lib/components/DropZone/client/DropZone.js +2 -1
- package/lib/components/DropZone/client/DropZone.js.map +1 -1
- package/lib/components/FormGroup/client/FormGroup.cjs +6 -1
- package/lib/components/FormGroup/client/FormGroup.cjs.map +1 -1
- package/lib/components/FormGroup/client/FormGroup.js +7 -2
- package/lib/components/FormGroup/client/FormGroup.js.map +1 -1
- package/lib/components/FormGroup/client/constants.cjs +2 -0
- package/lib/components/FormGroup/client/constants.cjs.map +1 -1
- package/lib/components/FormGroup/client/constants.d.ts +1 -0
- package/lib/components/FormGroup/client/constants.js +2 -1
- package/lib/components/FormGroup/client/constants.js.map +1 -1
- package/lib/components/Input/client/Input.cjs +2 -1
- package/lib/components/Input/client/Input.cjs.map +1 -1
- package/lib/components/Input/client/Input.js +2 -1
- package/lib/components/Input/client/Input.js.map +1 -1
- package/lib/components/InputFile/client/InputFileMultiplyAppendable.cjs +2 -1
- package/lib/components/InputFile/client/InputFileMultiplyAppendable.cjs.map +1 -1
- package/lib/components/InputFile/client/InputFileMultiplyAppendable.js +2 -1
- package/lib/components/InputFile/client/InputFileMultiplyAppendable.js.map +1 -1
- package/lib/components/InputFile/client/InputFileNative.cjs +2 -1
- package/lib/components/InputFile/client/InputFileNative.cjs.map +1 -1
- package/lib/components/InputFile/client/InputFileNative.js +2 -1
- package/lib/components/InputFile/client/InputFileNative.js.map +1 -1
- package/lib/components/Picker/DatetimeLocalPicker.cjs +2 -1
- package/lib/components/Picker/DatetimeLocalPicker.cjs.map +1 -1
- package/lib/components/Picker/DatetimeLocalPicker.js +2 -1
- package/lib/components/Picker/DatetimeLocalPicker.js.map +1 -1
- package/lib/components/Picker/MonthPicker.cjs +2 -1
- package/lib/components/Picker/MonthPicker.cjs.map +1 -1
- package/lib/components/Picker/MonthPicker.js +2 -1
- package/lib/components/Picker/MonthPicker.js.map +1 -1
- package/lib/components/Picker/TimePicker.cjs +2 -1
- package/lib/components/Picker/TimePicker.cjs.map +1 -1
- package/lib/components/Picker/TimePicker.js +2 -1
- package/lib/components/Picker/TimePicker.js.map +1 -1
- package/lib/components/Select/client/ActualSelect.cjs +20 -17
- package/lib/components/Select/client/ActualSelect.cjs.map +1 -1
- package/lib/components/Select/client/ActualSelect.js +20 -17
- package/lib/components/Select/client/ActualSelect.js.map +1 -1
- package/lib/components/Textarea/client/Textarea.cjs +2 -1
- package/lib/components/Textarea/client/Textarea.cjs.map +1 -1
- package/lib/components/Textarea/client/Textarea.js +2 -1
- package/lib/components/Textarea/client/Textarea.js.map +1 -1
- package/metadata.json +1 -1
- package/package.json +1 -1
|
@@ -75,9 +75,10 @@ const DropZone = forwardRef(({ children, onSelectFiles, multiple = true, disable
|
|
|
75
75
|
},
|
|
76
76
|
};
|
|
77
77
|
}, [latest]);
|
|
78
|
+
const errorAttr = error || undefined;
|
|
78
79
|
return (
|
|
79
80
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
80
|
-
jsxs("div", { className: classNames.wrapper, "data-files-dragged-over": filesDraggedOver || undefined, onDrop: functions.handleDrop, onDragOver: functions.handleDragOver, onDragLeave: functions.handleDragLeave, children: [children, jsx(SelectButton, { disabled: disabled, error: error, className: classNames.button, handleClick: functions.handleClickButton, label: selectButtonLabel }), jsx(VisuallyHiddenText, { children: jsx("input", { ...rest, ref: ref, type: "file", disabled: disabled, multiple: multiple, tabIndex: -1, "aria-invalid": error
|
|
81
|
+
jsxs("div", { className: classNames.wrapper, "data-files-dragged-over": filesDraggedOver || undefined, onDrop: functions.handleDrop, onDragOver: functions.handleDragOver, onDragLeave: functions.handleDragLeave, children: [children, jsx(SelectButton, { disabled: disabled, error: error, className: classNames.button, handleClick: functions.handleClickButton, label: selectButtonLabel }), jsx(VisuallyHiddenText, { children: jsx("input", { ...rest, ref: ref, type: "file", disabled: disabled, multiple: multiple, tabIndex: -1, "aria-invalid": errorAttr, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onChange: functions.handleChange }) })] }));
|
|
81
82
|
});
|
|
82
83
|
const SelectButton = memo(({ label, disabled, error, handleClick, className }) => (jsx(Button, { disabled: disabled, className: className, "data-error": error || undefined, onClick: handleClick, prefix: jsx(FaFolderOpenIcon, {}), children: label || jsx(Localizer, { id: "smarthr-ui/DropZone/selectButtonLabel", defaultText: "\u30D5\u30A1\u30A4\u30EB\u3092\u9078\u629E" }) })));
|
|
83
84
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DropZone.js","sources":["../../../../src/components/DropZone/client/DropZone.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithRef,\n type DragEvent,\n type MouseEvent,\n type PropsWithChildren,\n forwardRef,\n memo,\n useMemo,\n useState,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Localizer } from '../../../intl'\nimport { Button } from '../../Button'\nimport { FaFolderOpenIcon } from '../../Icon'\nimport { VisuallyHiddenText } from '../../VisuallyHiddenText'\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-DropZone',\n 'shr-relative',\n 'shr-border-shorthand shr-flex shr-flex-col shr-items-center shr-justify-center shr-bg-column shr-p-2.5',\n 'has-[.smarthr-ui-DropZone-Button[aria-disabled]]:shr-cursor-not-allowed',\n '[&:not([data-files-dragged-over])]:shr-border-dashed',\n 'data-[files-dragged-over]:shr-border-main',\n ],\n button: ['smarthr-ui-DropZone-Button', 'data-[error]:shr-border-danger'],\n },\n})\n\ntype BaseProps = PropsWithChildren<{\n /**\n * ボタンまたはドラッグ&ドロップでファイルが追加された時に発火するコールバック関数\n */\n onSelectFiles: (\n e: DragEvent<HTMLElement> | ChangeEvent<HTMLInputElement>,\n files: FileList | null,\n ) => void\n /**\n * 許可するファイル型を表す1つ以上の固有ファイル型指定子\n * <b>(ドラッグ&ドロップの挙動には影響しません)</b>\n */\n accept?: string\n /** 複数ファイルを選択できるかどうか */\n multiple?: boolean\n name?: string\n disabled?: boolean\n /** フォームにエラーがあるかどうか */\n error?: boolean\n /** ファイル選択ボタンのラベル */\n selectButtonLabel?: string\n}>\ntype Props = BaseProps & Omit<ComponentPropsWithRef<'div'>, keyof BaseProps>\n\nconst overrideEventDefault = (e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n}\n\nexport const DropZone = forwardRef<HTMLInputElement, Props>(\n (\n {\n children,\n onSelectFiles,\n multiple = true,\n disabled,\n error,\n selectButtonLabel,\n className,\n ...rest\n },\n ref,\n ) => {\n const [filesDraggedOver, setFilesDraggedOver] = useState(false)\n\n const classNames = useMemo(() => {\n const { wrapper, button } = classNameGenerator()\n return {\n wrapper: wrapper({ className }),\n button: button(),\n }\n }, [className])\n\n const latest = useLatest({ onSelectFiles })\n\n const functions = useMemo(() => {\n const inputFileSelector = 'input[type=\"file\"][data-smarthr-ui-input=\"true\"]'\n\n return {\n handleDrop: (e: DragEvent<HTMLElement>) => {\n overrideEventDefault(e)\n setFilesDraggedOver(false)\n\n if (e.dataTransfer.types.includes('Files')) {\n const input = e.currentTarget.querySelector<HTMLInputElement>(inputFileSelector)\n\n if (input) {\n input.files = e.dataTransfer.files\n }\n latest.onSelectFiles(e, e.dataTransfer.files)\n }\n },\n handleDragOver: (e: DragEvent<HTMLElement>) => {\n overrideEventDefault(e)\n setFilesDraggedOver(true)\n },\n handleDragLeave: () => {\n setFilesDraggedOver(false)\n },\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n latest.onSelectFiles(e, e.target.files)\n },\n handleClickButton: (e: MouseEvent<HTMLButtonElement>) => {\n e.currentTarget\n .closest('.smarthr-ui-DropZone')\n ?.querySelector<HTMLInputElement>(inputFileSelector)\n ?.click()\n },\n }\n }, [latest])\n\n return (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <div\n className={classNames.wrapper}\n data-files-dragged-over={filesDraggedOver || undefined}\n onDrop={functions.handleDrop}\n onDragOver={functions.handleDragOver}\n onDragLeave={functions.handleDragLeave}\n >\n {children}\n <SelectButton\n disabled={disabled}\n error={error}\n className={classNames.button}\n handleClick={functions.handleClickButton}\n label={selectButtonLabel}\n />\n <VisuallyHiddenText>\n {/* TODO: この input にアクセシブルネームが設定されていない。VisuallyHiddenText で視覚的に隠されているが aria-hidden ではないためアクセシビリティツリーに残る。aria-label 等で適切なラベルを付与する必要がある */}\n {/* eslint-disable-next-line smarthr/a11y-input-in-form-control */}\n <input\n {...rest}\n ref={ref}\n type=\"file\"\n disabled={disabled}\n multiple={multiple}\n tabIndex={-1}\n aria-invalid={error
|
|
1
|
+
{"version":3,"file":"DropZone.js","sources":["../../../../src/components/DropZone/client/DropZone.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type ComponentPropsWithRef,\n type DragEvent,\n type MouseEvent,\n type PropsWithChildren,\n forwardRef,\n memo,\n useMemo,\n useState,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Localizer } from '../../../intl'\nimport { Button } from '../../Button'\nimport { FaFolderOpenIcon } from '../../Icon'\nimport { VisuallyHiddenText } from '../../VisuallyHiddenText'\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-DropZone',\n 'shr-relative',\n 'shr-border-shorthand shr-flex shr-flex-col shr-items-center shr-justify-center shr-bg-column shr-p-2.5',\n 'has-[.smarthr-ui-DropZone-Button[aria-disabled]]:shr-cursor-not-allowed',\n '[&:not([data-files-dragged-over])]:shr-border-dashed',\n 'data-[files-dragged-over]:shr-border-main',\n ],\n button: ['smarthr-ui-DropZone-Button', 'data-[error]:shr-border-danger'],\n },\n})\n\ntype BaseProps = PropsWithChildren<{\n /**\n * ボタンまたはドラッグ&ドロップでファイルが追加された時に発火するコールバック関数\n */\n onSelectFiles: (\n e: DragEvent<HTMLElement> | ChangeEvent<HTMLInputElement>,\n files: FileList | null,\n ) => void\n /**\n * 許可するファイル型を表す1つ以上の固有ファイル型指定子\n * <b>(ドラッグ&ドロップの挙動には影響しません)</b>\n */\n accept?: string\n /** 複数ファイルを選択できるかどうか */\n multiple?: boolean\n name?: string\n disabled?: boolean\n /** フォームにエラーがあるかどうか */\n error?: boolean\n /** ファイル選択ボタンのラベル */\n selectButtonLabel?: string\n}>\ntype Props = BaseProps & Omit<ComponentPropsWithRef<'div'>, keyof BaseProps>\n\nconst overrideEventDefault = (e: DragEvent<HTMLElement>) => {\n e.preventDefault()\n e.stopPropagation()\n}\n\nexport const DropZone = forwardRef<HTMLInputElement, Props>(\n (\n {\n children,\n onSelectFiles,\n multiple = true,\n disabled,\n error,\n selectButtonLabel,\n className,\n ...rest\n },\n ref,\n ) => {\n const [filesDraggedOver, setFilesDraggedOver] = useState(false)\n\n const classNames = useMemo(() => {\n const { wrapper, button } = classNameGenerator()\n return {\n wrapper: wrapper({ className }),\n button: button(),\n }\n }, [className])\n\n const latest = useLatest({ onSelectFiles })\n\n const functions = useMemo(() => {\n const inputFileSelector = 'input[type=\"file\"][data-smarthr-ui-input=\"true\"]'\n\n return {\n handleDrop: (e: DragEvent<HTMLElement>) => {\n overrideEventDefault(e)\n setFilesDraggedOver(false)\n\n if (e.dataTransfer.types.includes('Files')) {\n const input = e.currentTarget.querySelector<HTMLInputElement>(inputFileSelector)\n\n if (input) {\n input.files = e.dataTransfer.files\n }\n latest.onSelectFiles(e, e.dataTransfer.files)\n }\n },\n handleDragOver: (e: DragEvent<HTMLElement>) => {\n overrideEventDefault(e)\n setFilesDraggedOver(true)\n },\n handleDragLeave: () => {\n setFilesDraggedOver(false)\n },\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n latest.onSelectFiles(e, e.target.files)\n },\n handleClickButton: (e: MouseEvent<HTMLButtonElement>) => {\n e.currentTarget\n .closest('.smarthr-ui-DropZone')\n ?.querySelector<HTMLInputElement>(inputFileSelector)\n ?.click()\n },\n }\n }, [latest])\n\n const errorAttr = error || undefined\n\n return (\n // eslint-disable-next-line jsx-a11y/no-static-element-interactions\n <div\n className={classNames.wrapper}\n data-files-dragged-over={filesDraggedOver || undefined}\n onDrop={functions.handleDrop}\n onDragOver={functions.handleDragOver}\n onDragLeave={functions.handleDragLeave}\n >\n {children}\n <SelectButton\n disabled={disabled}\n error={error}\n className={classNames.button}\n handleClick={functions.handleClickButton}\n label={selectButtonLabel}\n />\n <VisuallyHiddenText>\n {/* TODO: この input にアクセシブルネームが設定されていない。VisuallyHiddenText で視覚的に隠されているが aria-hidden ではないためアクセシビリティツリーに残る。aria-label 等で適切なラベルを付与する必要がある */}\n {/* eslint-disable-next-line smarthr/a11y-input-in-form-control */}\n <input\n {...rest}\n ref={ref}\n type=\"file\"\n disabled={disabled}\n multiple={multiple}\n tabIndex={-1}\n aria-invalid={errorAttr}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onChange={functions.handleChange}\n />\n </VisuallyHiddenText>\n </div>\n )\n },\n)\n\nconst SelectButton = memo<{\n label?: string\n disabled?: boolean\n error?: boolean\n handleClick: (e: MouseEvent<HTMLButtonElement>) => void\n className: string\n}>(({ label, disabled, error, handleClick, className }) => (\n <Button\n disabled={disabled}\n className={className}\n data-error={error || undefined}\n onClick={handleClick}\n prefix={<FaFolderOpenIcon />}\n >\n {label || <Localizer id=\"smarthr-ui/DropZone/selectButtonLabel\" defaultText=\"ファイルを選択\" />}\n </Button>\n))\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAqBA;AACE;AACE;;;;;;;AAOC;AACD;AACD;AACF;AA0BD;;;AAGA;AAEO;;AAgBH;;;AAGI;;;AAGJ;;AAIA;;;AAII;;;;;;;;;;;AAaA;;;;;;;AAOA;;;AAGA;AACE;;;;;;AAMN;AAEA;;;;AAqCF;AAGF;;"}
|
|
@@ -72,7 +72,12 @@ const FormGroup = ({ outerRef, label, subActionArea, innerMargin, statusLabels,
|
|
|
72
72
|
if (errorAttr === 'true') {
|
|
73
73
|
input.setAttribute('aria-invalid', 'true');
|
|
74
74
|
}
|
|
75
|
-
else
|
|
75
|
+
else if (
|
|
76
|
+
// HINT: 入力要素自体にerror属性が設定されている場合、そちらを優先する
|
|
77
|
+
input.getAttribute('data-smarthr-ui-input-error') !== 'true' &&
|
|
78
|
+
// HINT: Fieldset > FormControlのようにネストしている場合を考慮して
|
|
79
|
+
// autoBindErrorしているFormGroupが親方向に存在する場合、子の入力要素からaria-invalidを外さないようにする
|
|
80
|
+
!input.closest(`${components_FormGroup_client_constants.WRAPPER_SELECTOR}[data-auto-bind-error-input='true']`)) {
|
|
76
81
|
input.removeAttribute('aria-invalid');
|
|
77
82
|
}
|
|
78
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormGroup.cjs","sources":["../../../../src/components/FormGroup/client/FormGroup.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentType,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n type Ref,\n useCallback,\n useMemo,\n useRef,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { FaCircleExclamationIcon } from '../../Icon'\nimport { Cluster, Stack } from '../../Layout'\nimport { LiveRegion } from '../../LiveRegion'\nimport { Text } from '../../Text'\n\nimport { CHILDREN_WRAPPER_INPUT_SELECTOR } from './constants'\n\nimport type { CommonProps, LabelComponentProps, ObjectLabelType } from './type'\n\ntype Props = Omit<CommonProps, 'className'> & {\n outerRef: Ref<HTMLElement>\n /** グループのラベル名 */\n label: Omit<ObjectLabelType, 'id' | 'htmlFor'> & Required<Pick<ObjectLabelType, 'id' | 'htmlFor'>>\n as?: string | ComponentType<any>\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n LabelComponent: FC<LabelComponentProps>\n classNames: {\n wrapper: string\n childrenWrapper: string\n }\n}\n\n// HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n// undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\nconst EMPTY_ERROR_MESSAGES: ReactNode[] = []\n\nexport const FormGroup: FC<Props> = ({\n outerRef,\n label,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages: orgErrorMessages,\n supplementaryMessage,\n as = 'div',\n children,\n classNames,\n LabelComponent,\n autoBindErrorInput = true,\n ...rest\n}) => {\n // HINT: statusLabelsは設定されない場合が大半、かつ設定されてもRequiredLabelでmemo化されているため\n // memo化がかなりの確率で有用\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n // HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n // undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\n const errorMessages = orgErrorMessages\n ? Array.isArray(orgErrorMessages)\n ? orgErrorMessages.length === 0\n ? EMPTY_ERROR_MESSAGES\n : orgErrorMessages\n : [orgErrorMessages]\n : EMPTY_ERROR_MESSAGES\n\n const helpMessageId = helpMessage ? `${label.htmlFor}_helpMessage` : undefined\n const exampleMessageId = exampleMessage ? `${label.htmlFor}_exampleMessage` : undefined\n const supplementaryMessageId = supplementaryMessage\n ? `${label.htmlFor}_supplementaryMessage`\n : undefined\n const visibleErrorMessages = errorMessages.length > 0\n const errorMessagesId = visibleErrorMessages ? `${label.htmlFor}_errorMessages` : undefined\n\n const describedbyIds = useMemo(() => {\n const temp: string[] = []\n\n if (helpMessageId) {\n temp.push(helpMessageId)\n }\n if (exampleMessageId) {\n temp.push(exampleMessageId)\n }\n if (supplementaryMessageId) {\n temp.push(supplementaryMessageId)\n }\n if (errorMessagesId) {\n temp.push(errorMessagesId)\n }\n\n return temp.join(' ')\n }, [helpMessageId, exampleMessageId, supplementaryMessageId, errorMessagesId])\n\n const managedDescribedbyIdsRef = useRef<string[]>([])\n\n const innerCallbackRef = useCallback((node: HTMLElement | null) => {\n if (!node) {\n return\n }\n\n const action = () => {\n const input = node.querySelector<HTMLInputElement>(CHILDREN_WRAPPER_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const errorAttr = node.getAttribute('data-auto-bind-error-input')\n\n // HINT: そもそも対象の属性の値が存在しない場合、入力要素にaria-invalid属性を自動的にon/offする処理をしない\n if (errorAttr) {\n if (errorAttr === 'true') {\n input.setAttribute('aria-invalid', 'true')\n } else {\n input.removeAttribute('aria-invalid')\n }\n }\n\n const nextDescribedBy = node.getAttribute('data-auto-bind-aria-describedby-for-input')\n const ariaDescribedBy = input.getAttribute('aria-describedby') || ''\n const currentTokens = ariaDescribedBy ? ariaDescribedBy.split(' ') : []\n // HINT: 自分が過去に付与したid以外(=外部由来のid)だけを残す\n const externalTokens = currentTokens.filter(\n (token) => !managedDescribedbyIdsRef.current.includes(token),\n )\n const describedbyIdTokens = nextDescribedBy ? nextDescribedBy.split(' ') : []\n const nextValue = [...externalTokens, ...describedbyIdTokens].join(' ')\n\n if (nextValue !== ariaDescribedBy) {\n if (nextValue) {\n input.setAttribute('aria-describedby', nextValue)\n } else {\n input.removeAttribute('aria-describedby')\n }\n }\n\n managedDescribedbyIdsRef.current = describedbyIdTokens\n }\n\n action()\n\n const observer = new MutationObserver(action)\n observer.observe(node, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-auto-bind-error-input', 'data-auto-bind-aria-describedby-for-input'],\n })\n\n return () => {\n observer.disconnect()\n }\n }, [])\n\n const wrapperCallbackRef = useMergeRefs(innerCallbackRef, outerRef)\n\n return (\n <Stack\n {...rest}\n as={as}\n ref={wrapperCallbackRef}\n gap={innerMargin ?? 0.5}\n className={classNames.wrapper}\n aria-describedby={as === 'fieldset' ? describedbyIds || undefined : undefined}\n data-auto-bind-error-input={autoBindErrorInput ? visibleErrorMessages.toString() : undefined}\n data-auto-bind-aria-describedby-for-input={describedbyIds}\n >\n <LabelComponent\n managedLabelId={label.id}\n managedHtmlFor={label.htmlFor}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n label={label.text}\n subActionArea={subActionArea}\n />\n {helpMessage && (\n <p id={helpMessageId} className=\"smarthr-ui-FormControl-helpMessage\">\n {helpMessage}\n </p>\n )}\n {exampleMessage && (\n <Text\n as=\"p\"\n id={exampleMessageId}\n italic\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n )}\n {visibleErrorMessages && (\n <div id={errorMessagesId}>\n {errorMessages.map((message, index) => (\n <Text\n key={index}\n as=\"p\"\n className=\"smarthr-ui-FormControl-errorMessage\"\n icon={\n <FaCircleExclamationIcon className=\"smarthr-ui-FormControl-errorMessage-Icon shr-text-danger\" />\n }\n >\n <LiveRegion role=\"alert\">{message}</LiveRegion>\n </Text>\n ))}\n </div>\n )}\n <div className={classNames.childrenWrapper}>{children}</div>\n {supplementaryMessage && (\n <Text\n as=\"p\"\n id={supplementaryMessageId}\n size=\"S\"\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n )}\n </Stack>\n )\n}\n\nexport const LabelBody: FC<\n Pick<ComponentProps<typeof Text>, 'styleType' | 'icon' | 'children'> &\n Pick<LabelComponentProps, 'statusLabels'>\n> = ({ styleType, icon, children, statusLabels }) => (\n <>\n <Text styleType={styleType} icon={icon}>\n <span className=\"smarthr-ui-FormControl-labelText\">{children}</span>\n </Text>\n {statusLabels.length > 0 && (\n <Cluster as=\"span\" gap={0.25}>\n {statusLabels}\n </Cluster>\n )}\n </>\n)\n\nexport const LabelCluster: FC<\n PropsWithChildren<{ as?: 'label'; htmlFor?: string; id?: string; 'aria-hidden'?: 'true' }>\n> = ({ children, ...rest }) => (\n <Cluster {...rest} align=\"center\" className=\"smarthr-ui-FormControl-label\">\n {children}\n </Cluster>\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAsCA;AACA;AACA;;;;AAqBE;;;;AAQE;AACE;AACE;AACA;;;AAIN;AACA;;AAEE;;AAEF;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;;AAGF;AAEA;;;;;;;;;;;;AAgBM;AACE
|
|
1
|
+
{"version":3,"file":"FormGroup.cjs","sources":["../../../../src/components/FormGroup/client/FormGroup.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentType,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n type Ref,\n useCallback,\n useMemo,\n useRef,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { FaCircleExclamationIcon } from '../../Icon'\nimport { Cluster, Stack } from '../../Layout'\nimport { LiveRegion } from '../../LiveRegion'\nimport { Text } from '../../Text'\n\nimport { CHILDREN_WRAPPER_INPUT_SELECTOR, WRAPPER_SELECTOR } from './constants'\n\nimport type { CommonProps, LabelComponentProps, ObjectLabelType } from './type'\n\ntype Props = Omit<CommonProps, 'className'> & {\n outerRef: Ref<HTMLElement>\n /** グループのラベル名 */\n label: Omit<ObjectLabelType, 'id' | 'htmlFor'> & Required<Pick<ObjectLabelType, 'id' | 'htmlFor'>>\n as?: string | ComponentType<any>\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n LabelComponent: FC<LabelComponentProps>\n classNames: {\n wrapper: string\n childrenWrapper: string\n }\n}\n\n// HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n// undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\nconst EMPTY_ERROR_MESSAGES: ReactNode[] = []\n\nexport const FormGroup: FC<Props> = ({\n outerRef,\n label,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages: orgErrorMessages,\n supplementaryMessage,\n as = 'div',\n children,\n classNames,\n LabelComponent,\n autoBindErrorInput = true,\n ...rest\n}) => {\n // HINT: statusLabelsは設定されない場合が大半、かつ設定されてもRequiredLabelでmemo化されているため\n // memo化がかなりの確率で有用\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n // HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n // undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\n const errorMessages = orgErrorMessages\n ? Array.isArray(orgErrorMessages)\n ? orgErrorMessages.length === 0\n ? EMPTY_ERROR_MESSAGES\n : orgErrorMessages\n : [orgErrorMessages]\n : EMPTY_ERROR_MESSAGES\n\n const helpMessageId = helpMessage ? `${label.htmlFor}_helpMessage` : undefined\n const exampleMessageId = exampleMessage ? `${label.htmlFor}_exampleMessage` : undefined\n const supplementaryMessageId = supplementaryMessage\n ? `${label.htmlFor}_supplementaryMessage`\n : undefined\n const visibleErrorMessages = errorMessages.length > 0\n const errorMessagesId = visibleErrorMessages ? `${label.htmlFor}_errorMessages` : undefined\n\n const describedbyIds = useMemo(() => {\n const temp: string[] = []\n\n if (helpMessageId) {\n temp.push(helpMessageId)\n }\n if (exampleMessageId) {\n temp.push(exampleMessageId)\n }\n if (supplementaryMessageId) {\n temp.push(supplementaryMessageId)\n }\n if (errorMessagesId) {\n temp.push(errorMessagesId)\n }\n\n return temp.join(' ')\n }, [helpMessageId, exampleMessageId, supplementaryMessageId, errorMessagesId])\n\n const managedDescribedbyIdsRef = useRef<string[]>([])\n\n const innerCallbackRef = useCallback((node: HTMLElement | null) => {\n if (!node) {\n return\n }\n\n const action = () => {\n const input = node.querySelector<HTMLInputElement>(CHILDREN_WRAPPER_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const errorAttr = node.getAttribute('data-auto-bind-error-input')\n\n // HINT: そもそも対象の属性の値が存在しない場合、入力要素にaria-invalid属性を自動的にon/offする処理をしない\n if (errorAttr) {\n if (errorAttr === 'true') {\n input.setAttribute('aria-invalid', 'true')\n } else if (\n // HINT: 入力要素自体にerror属性が設定されている場合、そちらを優先する\n input.getAttribute('data-smarthr-ui-input-error') !== 'true' &&\n // HINT: Fieldset > FormControlのようにネストしている場合を考慮して\n // autoBindErrorしているFormGroupが親方向に存在する場合、子の入力要素からaria-invalidを外さないようにする\n !input.closest(`${WRAPPER_SELECTOR}[data-auto-bind-error-input='true']`)\n ) {\n input.removeAttribute('aria-invalid')\n }\n }\n\n const nextDescribedBy = node.getAttribute('data-auto-bind-aria-describedby-for-input')\n const ariaDescribedBy = input.getAttribute('aria-describedby') || ''\n const currentTokens = ariaDescribedBy ? ariaDescribedBy.split(' ') : []\n // HINT: 自分が過去に付与したid以外(=外部由来のid)だけを残す\n const externalTokens = currentTokens.filter(\n (token) => !managedDescribedbyIdsRef.current.includes(token),\n )\n const describedbyIdTokens = nextDescribedBy ? nextDescribedBy.split(' ') : []\n const nextValue = [...externalTokens, ...describedbyIdTokens].join(' ')\n\n if (nextValue !== ariaDescribedBy) {\n if (nextValue) {\n input.setAttribute('aria-describedby', nextValue)\n } else {\n input.removeAttribute('aria-describedby')\n }\n }\n\n managedDescribedbyIdsRef.current = describedbyIdTokens\n }\n\n action()\n\n const observer = new MutationObserver(action)\n observer.observe(node, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-auto-bind-error-input', 'data-auto-bind-aria-describedby-for-input'],\n })\n\n return () => {\n observer.disconnect()\n }\n }, [])\n\n const wrapperCallbackRef = useMergeRefs(innerCallbackRef, outerRef)\n\n return (\n <Stack\n {...rest}\n as={as}\n ref={wrapperCallbackRef}\n gap={innerMargin ?? 0.5}\n className={classNames.wrapper}\n aria-describedby={as === 'fieldset' ? describedbyIds || undefined : undefined}\n data-auto-bind-error-input={autoBindErrorInput ? visibleErrorMessages.toString() : undefined}\n data-auto-bind-aria-describedby-for-input={describedbyIds}\n >\n <LabelComponent\n managedLabelId={label.id}\n managedHtmlFor={label.htmlFor}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n label={label.text}\n subActionArea={subActionArea}\n />\n {helpMessage && (\n <p id={helpMessageId} className=\"smarthr-ui-FormControl-helpMessage\">\n {helpMessage}\n </p>\n )}\n {exampleMessage && (\n <Text\n as=\"p\"\n id={exampleMessageId}\n italic\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n )}\n {visibleErrorMessages && (\n <div id={errorMessagesId}>\n {errorMessages.map((message, index) => (\n <Text\n key={index}\n as=\"p\"\n className=\"smarthr-ui-FormControl-errorMessage\"\n icon={\n <FaCircleExclamationIcon className=\"smarthr-ui-FormControl-errorMessage-Icon shr-text-danger\" />\n }\n >\n <LiveRegion role=\"alert\">{message}</LiveRegion>\n </Text>\n ))}\n </div>\n )}\n <div className={classNames.childrenWrapper}>{children}</div>\n {supplementaryMessage && (\n <Text\n as=\"p\"\n id={supplementaryMessageId}\n size=\"S\"\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n )}\n </Stack>\n )\n}\n\nexport const LabelBody: FC<\n Pick<ComponentProps<typeof Text>, 'styleType' | 'icon' | 'children'> &\n Pick<LabelComponentProps, 'statusLabels'>\n> = ({ styleType, icon, children, statusLabels }) => (\n <>\n <Text styleType={styleType} icon={icon}>\n <span className=\"smarthr-ui-FormControl-labelText\">{children}</span>\n </Text>\n {statusLabels.length > 0 && (\n <Cluster as=\"span\" gap={0.25}>\n {statusLabels}\n </Cluster>\n )}\n </>\n)\n\nexport const LabelCluster: FC<\n PropsWithChildren<{ as?: 'label'; htmlFor?: string; id?: string; 'aria-hidden'?: 'true' }>\n> = ({ children, ...rest }) => (\n <Cluster {...rest} align=\"center\" className=\"smarthr-ui-FormControl-label\">\n {children}\n </Cluster>\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAsCA;AACA;AACA;;;;AAqBE;;;;AAQE;AACE;AACE;AACA;;;AAIN;AACA;;AAEE;;AAEF;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;;AAGF;AAEA;;;;;;;;;;;;AAgBM;AACE;;AACK;;AAEL;;;;AAKA;;;;;AAMJ;;;AAKA;AACA;AAEA;;AAEI;;;AAEA;;;AAIJ;AACF;AAEA;AAEA;AACA;AACE;AACA;AACA;AACA;AACD;AAED;;AAEA;;;;AAwEJ;AAEO;AAgBA;;;;"}
|
|
@@ -13,7 +13,7 @@ import '../../Layout/Center/Center.js';
|
|
|
13
13
|
import '../../Layout/Container/Container.js';
|
|
14
14
|
import { LiveRegion } from '../../LiveRegion/LiveRegion.js';
|
|
15
15
|
import { Text } from '../../Text/Text.js';
|
|
16
|
-
import { CHILDREN_WRAPPER_INPUT_SELECTOR } from './constants.js';
|
|
16
|
+
import { CHILDREN_WRAPPER_INPUT_SELECTOR, WRAPPER_SELECTOR } from './constants.js';
|
|
17
17
|
|
|
18
18
|
// HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい
|
|
19
19
|
// undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する
|
|
@@ -70,7 +70,12 @@ const FormGroup = ({ outerRef, label, subActionArea, innerMargin, statusLabels,
|
|
|
70
70
|
if (errorAttr === 'true') {
|
|
71
71
|
input.setAttribute('aria-invalid', 'true');
|
|
72
72
|
}
|
|
73
|
-
else
|
|
73
|
+
else if (
|
|
74
|
+
// HINT: 入力要素自体にerror属性が設定されている場合、そちらを優先する
|
|
75
|
+
input.getAttribute('data-smarthr-ui-input-error') !== 'true' &&
|
|
76
|
+
// HINT: Fieldset > FormControlのようにネストしている場合を考慮して
|
|
77
|
+
// autoBindErrorしているFormGroupが親方向に存在する場合、子の入力要素からaria-invalidを外さないようにする
|
|
78
|
+
!input.closest(`${WRAPPER_SELECTOR}[data-auto-bind-error-input='true']`)) {
|
|
74
79
|
input.removeAttribute('aria-invalid');
|
|
75
80
|
}
|
|
76
81
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormGroup.js","sources":["../../../../src/components/FormGroup/client/FormGroup.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentType,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n type Ref,\n useCallback,\n useMemo,\n useRef,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { FaCircleExclamationIcon } from '../../Icon'\nimport { Cluster, Stack } from '../../Layout'\nimport { LiveRegion } from '../../LiveRegion'\nimport { Text } from '../../Text'\n\nimport { CHILDREN_WRAPPER_INPUT_SELECTOR } from './constants'\n\nimport type { CommonProps, LabelComponentProps, ObjectLabelType } from './type'\n\ntype Props = Omit<CommonProps, 'className'> & {\n outerRef: Ref<HTMLElement>\n /** グループのラベル名 */\n label: Omit<ObjectLabelType, 'id' | 'htmlFor'> & Required<Pick<ObjectLabelType, 'id' | 'htmlFor'>>\n as?: string | ComponentType<any>\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n LabelComponent: FC<LabelComponentProps>\n classNames: {\n wrapper: string\n childrenWrapper: string\n }\n}\n\n// HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n// undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\nconst EMPTY_ERROR_MESSAGES: ReactNode[] = []\n\nexport const FormGroup: FC<Props> = ({\n outerRef,\n label,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages: orgErrorMessages,\n supplementaryMessage,\n as = 'div',\n children,\n classNames,\n LabelComponent,\n autoBindErrorInput = true,\n ...rest\n}) => {\n // HINT: statusLabelsは設定されない場合が大半、かつ設定されてもRequiredLabelでmemo化されているため\n // memo化がかなりの確率で有用\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n // HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n // undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\n const errorMessages = orgErrorMessages\n ? Array.isArray(orgErrorMessages)\n ? orgErrorMessages.length === 0\n ? EMPTY_ERROR_MESSAGES\n : orgErrorMessages\n : [orgErrorMessages]\n : EMPTY_ERROR_MESSAGES\n\n const helpMessageId = helpMessage ? `${label.htmlFor}_helpMessage` : undefined\n const exampleMessageId = exampleMessage ? `${label.htmlFor}_exampleMessage` : undefined\n const supplementaryMessageId = supplementaryMessage\n ? `${label.htmlFor}_supplementaryMessage`\n : undefined\n const visibleErrorMessages = errorMessages.length > 0\n const errorMessagesId = visibleErrorMessages ? `${label.htmlFor}_errorMessages` : undefined\n\n const describedbyIds = useMemo(() => {\n const temp: string[] = []\n\n if (helpMessageId) {\n temp.push(helpMessageId)\n }\n if (exampleMessageId) {\n temp.push(exampleMessageId)\n }\n if (supplementaryMessageId) {\n temp.push(supplementaryMessageId)\n }\n if (errorMessagesId) {\n temp.push(errorMessagesId)\n }\n\n return temp.join(' ')\n }, [helpMessageId, exampleMessageId, supplementaryMessageId, errorMessagesId])\n\n const managedDescribedbyIdsRef = useRef<string[]>([])\n\n const innerCallbackRef = useCallback((node: HTMLElement | null) => {\n if (!node) {\n return\n }\n\n const action = () => {\n const input = node.querySelector<HTMLInputElement>(CHILDREN_WRAPPER_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const errorAttr = node.getAttribute('data-auto-bind-error-input')\n\n // HINT: そもそも対象の属性の値が存在しない場合、入力要素にaria-invalid属性を自動的にon/offする処理をしない\n if (errorAttr) {\n if (errorAttr === 'true') {\n input.setAttribute('aria-invalid', 'true')\n } else {\n input.removeAttribute('aria-invalid')\n }\n }\n\n const nextDescribedBy = node.getAttribute('data-auto-bind-aria-describedby-for-input')\n const ariaDescribedBy = input.getAttribute('aria-describedby') || ''\n const currentTokens = ariaDescribedBy ? ariaDescribedBy.split(' ') : []\n // HINT: 自分が過去に付与したid以外(=外部由来のid)だけを残す\n const externalTokens = currentTokens.filter(\n (token) => !managedDescribedbyIdsRef.current.includes(token),\n )\n const describedbyIdTokens = nextDescribedBy ? nextDescribedBy.split(' ') : []\n const nextValue = [...externalTokens, ...describedbyIdTokens].join(' ')\n\n if (nextValue !== ariaDescribedBy) {\n if (nextValue) {\n input.setAttribute('aria-describedby', nextValue)\n } else {\n input.removeAttribute('aria-describedby')\n }\n }\n\n managedDescribedbyIdsRef.current = describedbyIdTokens\n }\n\n action()\n\n const observer = new MutationObserver(action)\n observer.observe(node, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-auto-bind-error-input', 'data-auto-bind-aria-describedby-for-input'],\n })\n\n return () => {\n observer.disconnect()\n }\n }, [])\n\n const wrapperCallbackRef = useMergeRefs(innerCallbackRef, outerRef)\n\n return (\n <Stack\n {...rest}\n as={as}\n ref={wrapperCallbackRef}\n gap={innerMargin ?? 0.5}\n className={classNames.wrapper}\n aria-describedby={as === 'fieldset' ? describedbyIds || undefined : undefined}\n data-auto-bind-error-input={autoBindErrorInput ? visibleErrorMessages.toString() : undefined}\n data-auto-bind-aria-describedby-for-input={describedbyIds}\n >\n <LabelComponent\n managedLabelId={label.id}\n managedHtmlFor={label.htmlFor}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n label={label.text}\n subActionArea={subActionArea}\n />\n {helpMessage && (\n <p id={helpMessageId} className=\"smarthr-ui-FormControl-helpMessage\">\n {helpMessage}\n </p>\n )}\n {exampleMessage && (\n <Text\n as=\"p\"\n id={exampleMessageId}\n italic\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n )}\n {visibleErrorMessages && (\n <div id={errorMessagesId}>\n {errorMessages.map((message, index) => (\n <Text\n key={index}\n as=\"p\"\n className=\"smarthr-ui-FormControl-errorMessage\"\n icon={\n <FaCircleExclamationIcon className=\"smarthr-ui-FormControl-errorMessage-Icon shr-text-danger\" />\n }\n >\n <LiveRegion role=\"alert\">{message}</LiveRegion>\n </Text>\n ))}\n </div>\n )}\n <div className={classNames.childrenWrapper}>{children}</div>\n {supplementaryMessage && (\n <Text\n as=\"p\"\n id={supplementaryMessageId}\n size=\"S\"\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n )}\n </Stack>\n )\n}\n\nexport const LabelBody: FC<\n Pick<ComponentProps<typeof Text>, 'styleType' | 'icon' | 'children'> &\n Pick<LabelComponentProps, 'statusLabels'>\n> = ({ styleType, icon, children, statusLabels }) => (\n <>\n <Text styleType={styleType} icon={icon}>\n <span className=\"smarthr-ui-FormControl-labelText\">{children}</span>\n </Text>\n {statusLabels.length > 0 && (\n <Cluster as=\"span\" gap={0.25}>\n {statusLabels}\n </Cluster>\n )}\n </>\n)\n\nexport const LabelCluster: FC<\n PropsWithChildren<{ as?: 'label'; htmlFor?: string; id?: string; 'aria-hidden'?: 'true' }>\n> = ({ children, ...rest }) => (\n <Cluster {...rest} align=\"center\" className=\"smarthr-ui-FormControl-label\">\n {children}\n </Cluster>\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAsCA;AACA;AACA;;;;AAqBE;;;;AAQE;AACE;AACE;AACA;;;AAIN;AACA;;AAEE;;AAEF;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;;AAGF;AAEA;;;;;;;;;;;;AAgBM;AACE
|
|
1
|
+
{"version":3,"file":"FormGroup.js","sources":["../../../../src/components/FormGroup/client/FormGroup.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type ComponentType,\n type FC,\n type PropsWithChildren,\n type ReactNode,\n type Ref,\n useCallback,\n useMemo,\n useRef,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { FaCircleExclamationIcon } from '../../Icon'\nimport { Cluster, Stack } from '../../Layout'\nimport { LiveRegion } from '../../LiveRegion'\nimport { Text } from '../../Text'\n\nimport { CHILDREN_WRAPPER_INPUT_SELECTOR, WRAPPER_SELECTOR } from './constants'\n\nimport type { CommonProps, LabelComponentProps, ObjectLabelType } from './type'\n\ntype Props = Omit<CommonProps, 'className'> & {\n outerRef: Ref<HTMLElement>\n /** グループのラベル名 */\n label: Omit<ObjectLabelType, 'id' | 'htmlFor'> & Required<Pick<ObjectLabelType, 'id' | 'htmlFor'>>\n as?: string | ComponentType<any>\n /** `true` のとき、文字色を `TEXT_DISABLED` にする */\n disabled?: boolean\n LabelComponent: FC<LabelComponentProps>\n classNames: {\n wrapper: string\n childrenWrapper: string\n }\n}\n\n// HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n// undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\nconst EMPTY_ERROR_MESSAGES: ReactNode[] = []\n\nexport const FormGroup: FC<Props> = ({\n outerRef,\n label,\n subActionArea,\n innerMargin,\n statusLabels,\n helpMessage,\n exampleMessage,\n errorMessages: orgErrorMessages,\n supplementaryMessage,\n as = 'div',\n children,\n classNames,\n LabelComponent,\n autoBindErrorInput = true,\n ...rest\n}) => {\n // HINT: statusLabelsは設定されない場合が大半、かつ設定されてもRequiredLabelでmemo化されているため\n // memo化がかなりの確率で有用\n const actualStatusLabels = useMemo(\n () => (statusLabels ? (Array.isArray(statusLabels) ? statusLabels : [statusLabels]) : []),\n [statusLabels],\n )\n\n // HINT: errorMessagesの利用方法とReactNodeのためuseMemoでは適切にmemo化しにくい\n // undefined、もしくは空配列の場合は定数のEMPTY_ERROR_MESSAGESと差し替えることで安定化する\n const errorMessages = orgErrorMessages\n ? Array.isArray(orgErrorMessages)\n ? orgErrorMessages.length === 0\n ? EMPTY_ERROR_MESSAGES\n : orgErrorMessages\n : [orgErrorMessages]\n : EMPTY_ERROR_MESSAGES\n\n const helpMessageId = helpMessage ? `${label.htmlFor}_helpMessage` : undefined\n const exampleMessageId = exampleMessage ? `${label.htmlFor}_exampleMessage` : undefined\n const supplementaryMessageId = supplementaryMessage\n ? `${label.htmlFor}_supplementaryMessage`\n : undefined\n const visibleErrorMessages = errorMessages.length > 0\n const errorMessagesId = visibleErrorMessages ? `${label.htmlFor}_errorMessages` : undefined\n\n const describedbyIds = useMemo(() => {\n const temp: string[] = []\n\n if (helpMessageId) {\n temp.push(helpMessageId)\n }\n if (exampleMessageId) {\n temp.push(exampleMessageId)\n }\n if (supplementaryMessageId) {\n temp.push(supplementaryMessageId)\n }\n if (errorMessagesId) {\n temp.push(errorMessagesId)\n }\n\n return temp.join(' ')\n }, [helpMessageId, exampleMessageId, supplementaryMessageId, errorMessagesId])\n\n const managedDescribedbyIdsRef = useRef<string[]>([])\n\n const innerCallbackRef = useCallback((node: HTMLElement | null) => {\n if (!node) {\n return\n }\n\n const action = () => {\n const input = node.querySelector<HTMLInputElement>(CHILDREN_WRAPPER_INPUT_SELECTOR)\n\n if (!input) {\n return\n }\n\n const errorAttr = node.getAttribute('data-auto-bind-error-input')\n\n // HINT: そもそも対象の属性の値が存在しない場合、入力要素にaria-invalid属性を自動的にon/offする処理をしない\n if (errorAttr) {\n if (errorAttr === 'true') {\n input.setAttribute('aria-invalid', 'true')\n } else if (\n // HINT: 入力要素自体にerror属性が設定されている場合、そちらを優先する\n input.getAttribute('data-smarthr-ui-input-error') !== 'true' &&\n // HINT: Fieldset > FormControlのようにネストしている場合を考慮して\n // autoBindErrorしているFormGroupが親方向に存在する場合、子の入力要素からaria-invalidを外さないようにする\n !input.closest(`${WRAPPER_SELECTOR}[data-auto-bind-error-input='true']`)\n ) {\n input.removeAttribute('aria-invalid')\n }\n }\n\n const nextDescribedBy = node.getAttribute('data-auto-bind-aria-describedby-for-input')\n const ariaDescribedBy = input.getAttribute('aria-describedby') || ''\n const currentTokens = ariaDescribedBy ? ariaDescribedBy.split(' ') : []\n // HINT: 自分が過去に付与したid以外(=外部由来のid)だけを残す\n const externalTokens = currentTokens.filter(\n (token) => !managedDescribedbyIdsRef.current.includes(token),\n )\n const describedbyIdTokens = nextDescribedBy ? nextDescribedBy.split(' ') : []\n const nextValue = [...externalTokens, ...describedbyIdTokens].join(' ')\n\n if (nextValue !== ariaDescribedBy) {\n if (nextValue) {\n input.setAttribute('aria-describedby', nextValue)\n } else {\n input.removeAttribute('aria-describedby')\n }\n }\n\n managedDescribedbyIdsRef.current = describedbyIdTokens\n }\n\n action()\n\n const observer = new MutationObserver(action)\n observer.observe(node, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-auto-bind-error-input', 'data-auto-bind-aria-describedby-for-input'],\n })\n\n return () => {\n observer.disconnect()\n }\n }, [])\n\n const wrapperCallbackRef = useMergeRefs(innerCallbackRef, outerRef)\n\n return (\n <Stack\n {...rest}\n as={as}\n ref={wrapperCallbackRef}\n gap={innerMargin ?? 0.5}\n className={classNames.wrapper}\n aria-describedby={as === 'fieldset' ? describedbyIds || undefined : undefined}\n data-auto-bind-error-input={autoBindErrorInput ? visibleErrorMessages.toString() : undefined}\n data-auto-bind-aria-describedby-for-input={describedbyIds}\n >\n <LabelComponent\n managedLabelId={label.id}\n managedHtmlFor={label.htmlFor}\n unrecommendedHideLabel={label.unrecommendedHide}\n labelType={label.styleType}\n labelIcon={label.icon}\n statusLabels={actualStatusLabels}\n label={label.text}\n subActionArea={subActionArea}\n />\n {helpMessage && (\n <p id={helpMessageId} className=\"smarthr-ui-FormControl-helpMessage\">\n {helpMessage}\n </p>\n )}\n {exampleMessage && (\n <Text\n as=\"p\"\n id={exampleMessageId}\n italic\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-exampleMessage\"\n >\n {exampleMessage}\n </Text>\n )}\n {visibleErrorMessages && (\n <div id={errorMessagesId}>\n {errorMessages.map((message, index) => (\n <Text\n key={index}\n as=\"p\"\n className=\"smarthr-ui-FormControl-errorMessage\"\n icon={\n <FaCircleExclamationIcon className=\"smarthr-ui-FormControl-errorMessage-Icon shr-text-danger\" />\n }\n >\n <LiveRegion role=\"alert\">{message}</LiveRegion>\n </Text>\n ))}\n </div>\n )}\n <div className={classNames.childrenWrapper}>{children}</div>\n {supplementaryMessage && (\n <Text\n as=\"p\"\n id={supplementaryMessageId}\n size=\"S\"\n color=\"TEXT_GREY\"\n className=\"smarthr-ui-FormControl-supplementaryMessage\"\n >\n {supplementaryMessage}\n </Text>\n )}\n </Stack>\n )\n}\n\nexport const LabelBody: FC<\n Pick<ComponentProps<typeof Text>, 'styleType' | 'icon' | 'children'> &\n Pick<LabelComponentProps, 'statusLabels'>\n> = ({ styleType, icon, children, statusLabels }) => (\n <>\n <Text styleType={styleType} icon={icon}>\n <span className=\"smarthr-ui-FormControl-labelText\">{children}</span>\n </Text>\n {statusLabels.length > 0 && (\n <Cluster as=\"span\" gap={0.25}>\n {statusLabels}\n </Cluster>\n )}\n </>\n)\n\nexport const LabelCluster: FC<\n PropsWithChildren<{ as?: 'label'; htmlFor?: string; id?: string; 'aria-hidden'?: 'true' }>\n> = ({ children, ...rest }) => (\n <Cluster {...rest} align=\"center\" className=\"smarthr-ui-FormControl-label\">\n {children}\n </Cluster>\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAsCA;AACA;AACA;;;;AAqBE;;;;AAQE;AACE;AACE;AACA;;;AAIN;AACA;;AAEE;;AAEF;AACA;AAEA;;;AAII;;;AAGA;;;AAGA;;;AAGA;;AAGF;;AAGF;AAEA;;;;;;;;;;;;AAgBM;AACE;;AACK;;AAEL;;;;AAKA;;;;;AAMJ;;;AAKA;AACA;AAEA;;AAEI;;;AAEA;;;AAIJ;AACF;AAEA;AAEA;AACA;AACE;AACA;AACA;AACA;AACD;AAED;;AAEA;;;;AAwEJ;AAEO;AAgBA;;"}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const CHILDREN_WRAPPER_INPUT_SELECTOR = `.smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input="true"]`;
|
|
4
4
|
const LABEL_TEXT_SELECTOR = '.smarthr-ui-FormControl-labelText';
|
|
5
|
+
const WRAPPER_SELECTOR = '.smarthr-ui-FormControl';
|
|
5
6
|
|
|
6
7
|
exports.CHILDREN_WRAPPER_INPUT_SELECTOR = CHILDREN_WRAPPER_INPUT_SELECTOR;
|
|
7
8
|
exports.LABEL_TEXT_SELECTOR = LABEL_TEXT_SELECTOR;
|
|
9
|
+
exports.WRAPPER_SELECTOR = WRAPPER_SELECTOR;
|
|
8
10
|
//# sourceMappingURL=constants.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs","sources":["../../../../src/components/FormGroup/client/constants.ts"],"sourcesContent":["export const CHILDREN_WRAPPER_INPUT_SELECTOR = `.smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input=\"true\"]`\nexport const LABEL_TEXT_SELECTOR = '.smarthr-ui-FormControl-labelText'\n"],"names":[],"mappings":";;AAAO,MAAM,+BAA+B,GAAG;AACxC,MAAM,mBAAmB,GAAG
|
|
1
|
+
{"version":3,"file":"constants.cjs","sources":["../../../../src/components/FormGroup/client/constants.ts"],"sourcesContent":["export const CHILDREN_WRAPPER_INPUT_SELECTOR = `.smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input=\"true\"]`\nexport const LABEL_TEXT_SELECTOR = '.smarthr-ui-FormControl-labelText'\nexport const WRAPPER_SELECTOR = '.smarthr-ui-FormControl'\n"],"names":[],"mappings":";;AAAO,MAAM,+BAA+B,GAAG;AACxC,MAAM,mBAAmB,GAAG;AAC5B,MAAM,gBAAgB,GAAG;;;;"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export declare const CHILDREN_WRAPPER_INPUT_SELECTOR = ".smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input=\"true\"]";
|
|
2
2
|
export declare const LABEL_TEXT_SELECTOR = ".smarthr-ui-FormControl-labelText";
|
|
3
|
+
export declare const WRAPPER_SELECTOR = ".smarthr-ui-FormControl";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const CHILDREN_WRAPPER_INPUT_SELECTOR = `.smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input="true"]`;
|
|
2
2
|
const LABEL_TEXT_SELECTOR = '.smarthr-ui-FormControl-labelText';
|
|
3
|
+
const WRAPPER_SELECTOR = '.smarthr-ui-FormControl';
|
|
3
4
|
|
|
4
|
-
export { CHILDREN_WRAPPER_INPUT_SELECTOR, LABEL_TEXT_SELECTOR };
|
|
5
|
+
export { CHILDREN_WRAPPER_INPUT_SELECTOR, LABEL_TEXT_SELECTOR, WRAPPER_SELECTOR };
|
|
5
6
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sources":["../../../../src/components/FormGroup/client/constants.ts"],"sourcesContent":["export const CHILDREN_WRAPPER_INPUT_SELECTOR = `.smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input=\"true\"]`\nexport const LABEL_TEXT_SELECTOR = '.smarthr-ui-FormControl-labelText'\n"],"names":[],"mappings":"AAAO,MAAM,+BAA+B,GAAG;AACxC,MAAM,mBAAmB,GAAG;;"}
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../../../../src/components/FormGroup/client/constants.ts"],"sourcesContent":["export const CHILDREN_WRAPPER_INPUT_SELECTOR = `.smarthr-ui-FormControl-childrenWrapper [data-smarthr-ui-input=\"true\"]`\nexport const LABEL_TEXT_SELECTOR = '.smarthr-ui-FormControl-labelText'\nexport const WRAPPER_SELECTOR = '.smarthr-ui-FormControl'\n"],"names":[],"mappings":"AAAO,MAAM,+BAA+B,GAAG;AACxC,MAAM,mBAAmB,GAAG;AAC5B,MAAM,gBAAgB,GAAG;;"}
|
|
@@ -73,6 +73,7 @@ const Input = React.forwardRef(({ onFocus, onBlur, autoFocus, prefix, suffix, cl
|
|
|
73
73
|
}, [className]);
|
|
74
74
|
const styleColor = bgColor ? theme.backgroundColor[backgroundColor[bgColor]] : undefined;
|
|
75
75
|
const styleMaxWidth = typeof width === 'number' ? `${width}px` : width;
|
|
76
|
+
const errorAttr = error || undefined;
|
|
76
77
|
return (jsxRuntime.jsxs("span", { role: "presentation", className: classNames.wrapper, style: {
|
|
77
78
|
borderColor: styleColor,
|
|
78
79
|
backgroundColor: styleColor,
|
|
@@ -82,7 +83,7 @@ const Input = React.forwardRef(({ onFocus, onBlur, autoFocus, prefix, suffix, cl
|
|
|
82
83
|
delegateEvent.currentTarget
|
|
83
84
|
.querySelector('[data-smarthr-ui-input="true"]')
|
|
84
85
|
?.focus();
|
|
85
|
-
}, children: [prefix && jsxRuntime.jsx("span", { className: classNames.prefix, children: prefix }), jsxRuntime.jsx("input", { ...rest, ref: mergedRef, type: type, disabled: disabled, readOnly: readOnly, max: max || (type && DEFAULT_MAX_ATTR[type]) || undefined, className: classNames.input, "aria-invalid": error
|
|
86
|
+
}, children: [prefix && jsxRuntime.jsx("span", { className: classNames.prefix, children: prefix }), jsxRuntime.jsx("input", { ...rest, ref: mergedRef, type: type, disabled: disabled, readOnly: readOnly, max: max || (type && DEFAULT_MAX_ATTR[type]) || undefined, className: classNames.input, "aria-invalid": errorAttr, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onWheel: type === 'number' ? disableWheel : undefined, onFocus: onFocus, onBlur: onBlur }), suffix && jsxRuntime.jsx("span", { className: classNames.suffix, children: suffix })] }));
|
|
86
87
|
});
|
|
87
88
|
const disableWheel = (e) => {
|
|
88
89
|
// wheel イベントに preventDefault はないため
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.cjs","sources":["../../../../src/components/Input/client/Input.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type MouseEvent,\n type ReactNode,\n type WheelEvent,\n forwardRef,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useOnce } from '../../../hooks/client/useOnce'\nimport { useTheme } from '../../../hooks/client/useTheme'\n\ntype BaseProps = {\n /** input 要素の `type` 値 */\n type?: HTMLInputElement['type']\n /** フォームにエラーがあるかどうか */\n error?: boolean\n /** コンポーネントの幅 */\n width?: number | string\n /** オートフォーカスを行うかどうか */\n autoFocus?: boolean\n /** コンポーネント内の先頭に表示する内容 */\n prefix?: ReactNode\n /** コンポーネント内の末尾に表示する内容 */\n suffix?: ReactNode\n /** 背景色。readOnly を下地の上に載せる場合に使う */\n bgColor?: keyof typeof backgroundColor\n /**\n * @deprecated placeholder属性は非推奨です。別途ヒント用要素を設置するか、それらの領域を確保出来ない場合はTooltipコンポーネントの利用を検討してください。\n */\n placeholder?: string\n}\ntype Props = BaseProps & Omit<ComponentPropsWithRef<'input'>, keyof BaseProps | 'onWheel'>\n\nconst backgroundColor = {\n BACKGROUND: 'background',\n COLUMN: 'column',\n BASE_GREY: 'base-grey',\n OVER_BACKGROUND: 'over-background',\n HEAD: 'head',\n BORDER: 'border',\n ACTION_BACKGROUND: 'action-background',\n} as const\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-Input',\n 'shr-border-shorthand shr-box-border shr-inline-flex shr-cursor-text shr-items-center shr-gap-0.5 shr-rounded-m shr-bg-white shr-px-0.5',\n 'contrast-more:shr-border-high-contrast',\n 'focus-within:shr-focus-indicator',\n 'has-[[aria-invalid]]:shr-border-danger',\n 'has-[:disabled]:[&&&]:shr-border-default/50',\n 'has-[:disabled]:shr-pointer-events-none has-[:disabled]:shr-bg-white-darken',\n 'has-[[readonly]:not(:disabled)]:[&&&]:shr-border-[theme(backgroundColor.column)] has-[[readonly]:not(:disabled)]:[&&&]:shr-bg-column',\n ],\n input: [\n 'smarthr-ui-Input-input',\n 'shr-inline-block shr-w-full shr-grow shr-border-none shr-bg-transparent shr-py-0.75 shr-text-base shr-leading-none shr-text-black shr-outline-none shr-outline-0',\n 'placeholder:shr-text-grey',\n 'disabled:shr-text-disabled disabled:shr-opacity-100',\n 'shr-h-[theme(fontSize.base)]',\n // HINT: 日付系inputがsafariなどで対応されていないため、input要素内が空白になりフォームが潰れる場合がある\n // マジックナンバーになるが、ほかに適切なプロパティがないため、min-widthで最低幅を指定することで防ぐ\n '[&[type=\"datetime-local\"]]:shr-min-w-[11em] [&[type=\"month\"]]:shr-min-w-[8em] [&[type=\"time\"]]:shr-min-w-[5em]',\n ],\n affix: [\n 'shr-flex shr-shrink-0 shr-items-center shr-text-grey',\n '[.smarthr-ui-Input:has(:disabled)_&]:shr-text-disabled [.smarthr-ui-Input:has(:disabled)_&]:shr-opacity-100',\n ],\n },\n})\n\n// HINT: PC版ブラウザで年が6桁入力できる場合、コピー&ペーストが正常に動作しないなど、UI上の問題が発生する場合がある\n// 回避のためmaxで年四桁を指定する\nconst DEFAULT_MAX_ATTR = {\n date: '9999-12-31',\n 'datetime-local': '9999-12-31T23:59',\n month: '9999-12',\n}\n\nexport const Input = forwardRef<HTMLInputElement, Props>(\n (\n {\n onFocus,\n onBlur,\n autoFocus,\n prefix,\n suffix,\n className,\n width,\n disabled,\n error,\n readOnly,\n bgColor,\n type,\n max,\n ...rest\n },\n ref,\n ) => {\n const theme = useTheme()\n\n const callbackRef = useOnce((node: HTMLInputElement | null) => {\n if (node && autoFocus) {\n node.focus()\n }\n })\n\n // HINT: useMergeRefsはv18でもcallbackRefのcleanup関数に対応している\n // もしuseMergeRefsをなくす場合、react v18対応が不要になっているかどうか確認する\n const mergedRef = useMergeRefs(callbackRef, ref)\n\n const classNames = useMemo(() => {\n const { wrapper, input, affix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n prefix: affix({ className: 'smarthr-ui-Input-prefix' }),\n suffix: affix({ className: 'smarthr-ui-Input-suffix' }),\n }\n }, [className])\n\n const styleColor = bgColor ? theme.backgroundColor[backgroundColor[bgColor]] : undefined\n const styleMaxWidth = typeof width === 'number' ? `${width}px` : width\n\n return (\n <span\n role=\"presentation\"\n className={classNames.wrapper}\n style={{\n borderColor: styleColor,\n backgroundColor: styleColor,\n maxWidth: styleMaxWidth,\n width: styleMaxWidth ? '100%' : undefined,\n }}\n onClick={(delegateEvent: MouseEvent<HTMLSpanElement>) => {\n delegateEvent.currentTarget\n .querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"]')\n ?.focus()\n }}\n >\n {prefix && <span className={classNames.prefix}>{prefix}</span>}\n <input\n {...rest}\n ref={mergedRef}\n type={type}\n disabled={disabled}\n readOnly={readOnly}\n max={\n max || (type && DEFAULT_MAX_ATTR[type as keyof typeof DEFAULT_MAX_ATTR]) || undefined\n }\n className={classNames.input}\n aria-invalid={error
|
|
1
|
+
{"version":3,"file":"Input.cjs","sources":["../../../../src/components/Input/client/Input.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type MouseEvent,\n type ReactNode,\n type WheelEvent,\n forwardRef,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useOnce } from '../../../hooks/client/useOnce'\nimport { useTheme } from '../../../hooks/client/useTheme'\n\ntype BaseProps = {\n /** input 要素の `type` 値 */\n type?: HTMLInputElement['type']\n /** フォームにエラーがあるかどうか */\n error?: boolean\n /** コンポーネントの幅 */\n width?: number | string\n /** オートフォーカスを行うかどうか */\n autoFocus?: boolean\n /** コンポーネント内の先頭に表示する内容 */\n prefix?: ReactNode\n /** コンポーネント内の末尾に表示する内容 */\n suffix?: ReactNode\n /** 背景色。readOnly を下地の上に載せる場合に使う */\n bgColor?: keyof typeof backgroundColor\n /**\n * @deprecated placeholder属性は非推奨です。別途ヒント用要素を設置するか、それらの領域を確保出来ない場合はTooltipコンポーネントの利用を検討してください。\n */\n placeholder?: string\n}\ntype Props = BaseProps & Omit<ComponentPropsWithRef<'input'>, keyof BaseProps | 'onWheel'>\n\nconst backgroundColor = {\n BACKGROUND: 'background',\n COLUMN: 'column',\n BASE_GREY: 'base-grey',\n OVER_BACKGROUND: 'over-background',\n HEAD: 'head',\n BORDER: 'border',\n ACTION_BACKGROUND: 'action-background',\n} as const\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-Input',\n 'shr-border-shorthand shr-box-border shr-inline-flex shr-cursor-text shr-items-center shr-gap-0.5 shr-rounded-m shr-bg-white shr-px-0.5',\n 'contrast-more:shr-border-high-contrast',\n 'focus-within:shr-focus-indicator',\n 'has-[[aria-invalid]]:shr-border-danger',\n 'has-[:disabled]:[&&&]:shr-border-default/50',\n 'has-[:disabled]:shr-pointer-events-none has-[:disabled]:shr-bg-white-darken',\n 'has-[[readonly]:not(:disabled)]:[&&&]:shr-border-[theme(backgroundColor.column)] has-[[readonly]:not(:disabled)]:[&&&]:shr-bg-column',\n ],\n input: [\n 'smarthr-ui-Input-input',\n 'shr-inline-block shr-w-full shr-grow shr-border-none shr-bg-transparent shr-py-0.75 shr-text-base shr-leading-none shr-text-black shr-outline-none shr-outline-0',\n 'placeholder:shr-text-grey',\n 'disabled:shr-text-disabled disabled:shr-opacity-100',\n 'shr-h-[theme(fontSize.base)]',\n // HINT: 日付系inputがsafariなどで対応されていないため、input要素内が空白になりフォームが潰れる場合がある\n // マジックナンバーになるが、ほかに適切なプロパティがないため、min-widthで最低幅を指定することで防ぐ\n '[&[type=\"datetime-local\"]]:shr-min-w-[11em] [&[type=\"month\"]]:shr-min-w-[8em] [&[type=\"time\"]]:shr-min-w-[5em]',\n ],\n affix: [\n 'shr-flex shr-shrink-0 shr-items-center shr-text-grey',\n '[.smarthr-ui-Input:has(:disabled)_&]:shr-text-disabled [.smarthr-ui-Input:has(:disabled)_&]:shr-opacity-100',\n ],\n },\n})\n\n// HINT: PC版ブラウザで年が6桁入力できる場合、コピー&ペーストが正常に動作しないなど、UI上の問題が発生する場合がある\n// 回避のためmaxで年四桁を指定する\nconst DEFAULT_MAX_ATTR = {\n date: '9999-12-31',\n 'datetime-local': '9999-12-31T23:59',\n month: '9999-12',\n}\n\nexport const Input = forwardRef<HTMLInputElement, Props>(\n (\n {\n onFocus,\n onBlur,\n autoFocus,\n prefix,\n suffix,\n className,\n width,\n disabled,\n error,\n readOnly,\n bgColor,\n type,\n max,\n ...rest\n },\n ref,\n ) => {\n const theme = useTheme()\n\n const callbackRef = useOnce((node: HTMLInputElement | null) => {\n if (node && autoFocus) {\n node.focus()\n }\n })\n\n // HINT: useMergeRefsはv18でもcallbackRefのcleanup関数に対応している\n // もしuseMergeRefsをなくす場合、react v18対応が不要になっているかどうか確認する\n const mergedRef = useMergeRefs(callbackRef, ref)\n\n const classNames = useMemo(() => {\n const { wrapper, input, affix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n prefix: affix({ className: 'smarthr-ui-Input-prefix' }),\n suffix: affix({ className: 'smarthr-ui-Input-suffix' }),\n }\n }, [className])\n\n const styleColor = bgColor ? theme.backgroundColor[backgroundColor[bgColor]] : undefined\n const styleMaxWidth = typeof width === 'number' ? `${width}px` : width\n const errorAttr = error || undefined\n\n return (\n <span\n role=\"presentation\"\n className={classNames.wrapper}\n style={{\n borderColor: styleColor,\n backgroundColor: styleColor,\n maxWidth: styleMaxWidth,\n width: styleMaxWidth ? '100%' : undefined,\n }}\n onClick={(delegateEvent: MouseEvent<HTMLSpanElement>) => {\n delegateEvent.currentTarget\n .querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"]')\n ?.focus()\n }}\n >\n {prefix && <span className={classNames.prefix}>{prefix}</span>}\n <input\n {...rest}\n ref={mergedRef}\n type={type}\n disabled={disabled}\n readOnly={readOnly}\n max={\n max || (type && DEFAULT_MAX_ATTR[type as keyof typeof DEFAULT_MAX_ATTR]) || undefined\n }\n className={classNames.input}\n aria-invalid={errorAttr}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onWheel={type === 'number' ? disableWheel : undefined}\n onFocus={onFocus}\n onBlur={onBlur}\n />\n {suffix && <span className={classNames.suffix}>{suffix}</span>}\n </span>\n )\n },\n)\n\nconst disableWheel = (e: WheelEvent) => {\n // wheel イベントに preventDefault はないため\n if (e.target) {\n ;(e.target as HTMLInputElement).blur()\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;AAsCA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;;AAGF;AACE;AACE;;;;;;;;;AASC;AACD;;;;;;;;;AASC;AACD;;;AAGC;AACF;AACF;AAED;AACA;AACA;AACE;AACA;AACA;;AAGK;AAoBH;AAEA;AACE;;;AAGF;;;;AAMA;;;AAII;;;;;AAKJ;AAEA;AACA;AACA;AAEA;AAKM;AACA;AACA;;AAED;AAEC;;;;AA0BR;AAGF;;AAEE;AACI;;AAEN;;"}
|
|
@@ -71,6 +71,7 @@ const Input = forwardRef(({ onFocus, onBlur, autoFocus, prefix, suffix, classNam
|
|
|
71
71
|
}, [className]);
|
|
72
72
|
const styleColor = bgColor ? theme.backgroundColor[backgroundColor[bgColor]] : undefined;
|
|
73
73
|
const styleMaxWidth = typeof width === 'number' ? `${width}px` : width;
|
|
74
|
+
const errorAttr = error || undefined;
|
|
74
75
|
return (jsxs("span", { role: "presentation", className: classNames.wrapper, style: {
|
|
75
76
|
borderColor: styleColor,
|
|
76
77
|
backgroundColor: styleColor,
|
|
@@ -80,7 +81,7 @@ const Input = forwardRef(({ onFocus, onBlur, autoFocus, prefix, suffix, classNam
|
|
|
80
81
|
delegateEvent.currentTarget
|
|
81
82
|
.querySelector('[data-smarthr-ui-input="true"]')
|
|
82
83
|
?.focus();
|
|
83
|
-
}, children: [prefix && jsx("span", { className: classNames.prefix, children: prefix }), jsx("input", { ...rest, ref: mergedRef, type: type, disabled: disabled, readOnly: readOnly, max: max || (type && DEFAULT_MAX_ATTR[type]) || undefined, className: classNames.input, "aria-invalid": error
|
|
84
|
+
}, children: [prefix && jsx("span", { className: classNames.prefix, children: prefix }), jsx("input", { ...rest, ref: mergedRef, type: type, disabled: disabled, readOnly: readOnly, max: max || (type && DEFAULT_MAX_ATTR[type]) || undefined, className: classNames.input, "aria-invalid": errorAttr, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onWheel: type === 'number' ? disableWheel : undefined, onFocus: onFocus, onBlur: onBlur }), suffix && jsx("span", { className: classNames.suffix, children: suffix })] }));
|
|
84
85
|
});
|
|
85
86
|
const disableWheel = (e) => {
|
|
86
87
|
// wheel イベントに preventDefault はないため
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.js","sources":["../../../../src/components/Input/client/Input.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type MouseEvent,\n type ReactNode,\n type WheelEvent,\n forwardRef,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useOnce } from '../../../hooks/client/useOnce'\nimport { useTheme } from '../../../hooks/client/useTheme'\n\ntype BaseProps = {\n /** input 要素の `type` 値 */\n type?: HTMLInputElement['type']\n /** フォームにエラーがあるかどうか */\n error?: boolean\n /** コンポーネントの幅 */\n width?: number | string\n /** オートフォーカスを行うかどうか */\n autoFocus?: boolean\n /** コンポーネント内の先頭に表示する内容 */\n prefix?: ReactNode\n /** コンポーネント内の末尾に表示する内容 */\n suffix?: ReactNode\n /** 背景色。readOnly を下地の上に載せる場合に使う */\n bgColor?: keyof typeof backgroundColor\n /**\n * @deprecated placeholder属性は非推奨です。別途ヒント用要素を設置するか、それらの領域を確保出来ない場合はTooltipコンポーネントの利用を検討してください。\n */\n placeholder?: string\n}\ntype Props = BaseProps & Omit<ComponentPropsWithRef<'input'>, keyof BaseProps | 'onWheel'>\n\nconst backgroundColor = {\n BACKGROUND: 'background',\n COLUMN: 'column',\n BASE_GREY: 'base-grey',\n OVER_BACKGROUND: 'over-background',\n HEAD: 'head',\n BORDER: 'border',\n ACTION_BACKGROUND: 'action-background',\n} as const\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-Input',\n 'shr-border-shorthand shr-box-border shr-inline-flex shr-cursor-text shr-items-center shr-gap-0.5 shr-rounded-m shr-bg-white shr-px-0.5',\n 'contrast-more:shr-border-high-contrast',\n 'focus-within:shr-focus-indicator',\n 'has-[[aria-invalid]]:shr-border-danger',\n 'has-[:disabled]:[&&&]:shr-border-default/50',\n 'has-[:disabled]:shr-pointer-events-none has-[:disabled]:shr-bg-white-darken',\n 'has-[[readonly]:not(:disabled)]:[&&&]:shr-border-[theme(backgroundColor.column)] has-[[readonly]:not(:disabled)]:[&&&]:shr-bg-column',\n ],\n input: [\n 'smarthr-ui-Input-input',\n 'shr-inline-block shr-w-full shr-grow shr-border-none shr-bg-transparent shr-py-0.75 shr-text-base shr-leading-none shr-text-black shr-outline-none shr-outline-0',\n 'placeholder:shr-text-grey',\n 'disabled:shr-text-disabled disabled:shr-opacity-100',\n 'shr-h-[theme(fontSize.base)]',\n // HINT: 日付系inputがsafariなどで対応されていないため、input要素内が空白になりフォームが潰れる場合がある\n // マジックナンバーになるが、ほかに適切なプロパティがないため、min-widthで最低幅を指定することで防ぐ\n '[&[type=\"datetime-local\"]]:shr-min-w-[11em] [&[type=\"month\"]]:shr-min-w-[8em] [&[type=\"time\"]]:shr-min-w-[5em]',\n ],\n affix: [\n 'shr-flex shr-shrink-0 shr-items-center shr-text-grey',\n '[.smarthr-ui-Input:has(:disabled)_&]:shr-text-disabled [.smarthr-ui-Input:has(:disabled)_&]:shr-opacity-100',\n ],\n },\n})\n\n// HINT: PC版ブラウザで年が6桁入力できる場合、コピー&ペーストが正常に動作しないなど、UI上の問題が発生する場合がある\n// 回避のためmaxで年四桁を指定する\nconst DEFAULT_MAX_ATTR = {\n date: '9999-12-31',\n 'datetime-local': '9999-12-31T23:59',\n month: '9999-12',\n}\n\nexport const Input = forwardRef<HTMLInputElement, Props>(\n (\n {\n onFocus,\n onBlur,\n autoFocus,\n prefix,\n suffix,\n className,\n width,\n disabled,\n error,\n readOnly,\n bgColor,\n type,\n max,\n ...rest\n },\n ref,\n ) => {\n const theme = useTheme()\n\n const callbackRef = useOnce((node: HTMLInputElement | null) => {\n if (node && autoFocus) {\n node.focus()\n }\n })\n\n // HINT: useMergeRefsはv18でもcallbackRefのcleanup関数に対応している\n // もしuseMergeRefsをなくす場合、react v18対応が不要になっているかどうか確認する\n const mergedRef = useMergeRefs(callbackRef, ref)\n\n const classNames = useMemo(() => {\n const { wrapper, input, affix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n prefix: affix({ className: 'smarthr-ui-Input-prefix' }),\n suffix: affix({ className: 'smarthr-ui-Input-suffix' }),\n }\n }, [className])\n\n const styleColor = bgColor ? theme.backgroundColor[backgroundColor[bgColor]] : undefined\n const styleMaxWidth = typeof width === 'number' ? `${width}px` : width\n\n return (\n <span\n role=\"presentation\"\n className={classNames.wrapper}\n style={{\n borderColor: styleColor,\n backgroundColor: styleColor,\n maxWidth: styleMaxWidth,\n width: styleMaxWidth ? '100%' : undefined,\n }}\n onClick={(delegateEvent: MouseEvent<HTMLSpanElement>) => {\n delegateEvent.currentTarget\n .querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"]')\n ?.focus()\n }}\n >\n {prefix && <span className={classNames.prefix}>{prefix}</span>}\n <input\n {...rest}\n ref={mergedRef}\n type={type}\n disabled={disabled}\n readOnly={readOnly}\n max={\n max || (type && DEFAULT_MAX_ATTR[type as keyof typeof DEFAULT_MAX_ATTR]) || undefined\n }\n className={classNames.input}\n aria-invalid={error
|
|
1
|
+
{"version":3,"file":"Input.js","sources":["../../../../src/components/Input/client/Input.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentPropsWithRef,\n type MouseEvent,\n type ReactNode,\n type WheelEvent,\n forwardRef,\n useMemo,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useOnce } from '../../../hooks/client/useOnce'\nimport { useTheme } from '../../../hooks/client/useTheme'\n\ntype BaseProps = {\n /** input 要素の `type` 値 */\n type?: HTMLInputElement['type']\n /** フォームにエラーがあるかどうか */\n error?: boolean\n /** コンポーネントの幅 */\n width?: number | string\n /** オートフォーカスを行うかどうか */\n autoFocus?: boolean\n /** コンポーネント内の先頭に表示する内容 */\n prefix?: ReactNode\n /** コンポーネント内の末尾に表示する内容 */\n suffix?: ReactNode\n /** 背景色。readOnly を下地の上に載せる場合に使う */\n bgColor?: keyof typeof backgroundColor\n /**\n * @deprecated placeholder属性は非推奨です。別途ヒント用要素を設置するか、それらの領域を確保出来ない場合はTooltipコンポーネントの利用を検討してください。\n */\n placeholder?: string\n}\ntype Props = BaseProps & Omit<ComponentPropsWithRef<'input'>, keyof BaseProps | 'onWheel'>\n\nconst backgroundColor = {\n BACKGROUND: 'background',\n COLUMN: 'column',\n BASE_GREY: 'base-grey',\n OVER_BACKGROUND: 'over-background',\n HEAD: 'head',\n BORDER: 'border',\n ACTION_BACKGROUND: 'action-background',\n} as const\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: [\n 'smarthr-ui-Input',\n 'shr-border-shorthand shr-box-border shr-inline-flex shr-cursor-text shr-items-center shr-gap-0.5 shr-rounded-m shr-bg-white shr-px-0.5',\n 'contrast-more:shr-border-high-contrast',\n 'focus-within:shr-focus-indicator',\n 'has-[[aria-invalid]]:shr-border-danger',\n 'has-[:disabled]:[&&&]:shr-border-default/50',\n 'has-[:disabled]:shr-pointer-events-none has-[:disabled]:shr-bg-white-darken',\n 'has-[[readonly]:not(:disabled)]:[&&&]:shr-border-[theme(backgroundColor.column)] has-[[readonly]:not(:disabled)]:[&&&]:shr-bg-column',\n ],\n input: [\n 'smarthr-ui-Input-input',\n 'shr-inline-block shr-w-full shr-grow shr-border-none shr-bg-transparent shr-py-0.75 shr-text-base shr-leading-none shr-text-black shr-outline-none shr-outline-0',\n 'placeholder:shr-text-grey',\n 'disabled:shr-text-disabled disabled:shr-opacity-100',\n 'shr-h-[theme(fontSize.base)]',\n // HINT: 日付系inputがsafariなどで対応されていないため、input要素内が空白になりフォームが潰れる場合がある\n // マジックナンバーになるが、ほかに適切なプロパティがないため、min-widthで最低幅を指定することで防ぐ\n '[&[type=\"datetime-local\"]]:shr-min-w-[11em] [&[type=\"month\"]]:shr-min-w-[8em] [&[type=\"time\"]]:shr-min-w-[5em]',\n ],\n affix: [\n 'shr-flex shr-shrink-0 shr-items-center shr-text-grey',\n '[.smarthr-ui-Input:has(:disabled)_&]:shr-text-disabled [.smarthr-ui-Input:has(:disabled)_&]:shr-opacity-100',\n ],\n },\n})\n\n// HINT: PC版ブラウザで年が6桁入力できる場合、コピー&ペーストが正常に動作しないなど、UI上の問題が発生する場合がある\n// 回避のためmaxで年四桁を指定する\nconst DEFAULT_MAX_ATTR = {\n date: '9999-12-31',\n 'datetime-local': '9999-12-31T23:59',\n month: '9999-12',\n}\n\nexport const Input = forwardRef<HTMLInputElement, Props>(\n (\n {\n onFocus,\n onBlur,\n autoFocus,\n prefix,\n suffix,\n className,\n width,\n disabled,\n error,\n readOnly,\n bgColor,\n type,\n max,\n ...rest\n },\n ref,\n ) => {\n const theme = useTheme()\n\n const callbackRef = useOnce((node: HTMLInputElement | null) => {\n if (node && autoFocus) {\n node.focus()\n }\n })\n\n // HINT: useMergeRefsはv18でもcallbackRefのcleanup関数に対応している\n // もしuseMergeRefsをなくす場合、react v18対応が不要になっているかどうか確認する\n const mergedRef = useMergeRefs(callbackRef, ref)\n\n const classNames = useMemo(() => {\n const { wrapper, input, affix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n input: input(),\n prefix: affix({ className: 'smarthr-ui-Input-prefix' }),\n suffix: affix({ className: 'smarthr-ui-Input-suffix' }),\n }\n }, [className])\n\n const styleColor = bgColor ? theme.backgroundColor[backgroundColor[bgColor]] : undefined\n const styleMaxWidth = typeof width === 'number' ? `${width}px` : width\n const errorAttr = error || undefined\n\n return (\n <span\n role=\"presentation\"\n className={classNames.wrapper}\n style={{\n borderColor: styleColor,\n backgroundColor: styleColor,\n maxWidth: styleMaxWidth,\n width: styleMaxWidth ? '100%' : undefined,\n }}\n onClick={(delegateEvent: MouseEvent<HTMLSpanElement>) => {\n delegateEvent.currentTarget\n .querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"]')\n ?.focus()\n }}\n >\n {prefix && <span className={classNames.prefix}>{prefix}</span>}\n <input\n {...rest}\n ref={mergedRef}\n type={type}\n disabled={disabled}\n readOnly={readOnly}\n max={\n max || (type && DEFAULT_MAX_ATTR[type as keyof typeof DEFAULT_MAX_ATTR]) || undefined\n }\n className={classNames.input}\n aria-invalid={errorAttr}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onWheel={type === 'number' ? disableWheel : undefined}\n onFocus={onFocus}\n onBlur={onBlur}\n />\n {suffix && <span className={classNames.suffix}>{suffix}</span>}\n </span>\n )\n },\n)\n\nconst disableWheel = (e: WheelEvent) => {\n // wheel イベントに preventDefault はないため\n if (e.target) {\n ;(e.target as HTMLInputElement).blur()\n }\n}\n"],"names":[],"mappings":";;;;;;;;AAsCA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;;AAGF;AACE;AACE;;;;;;;;;AASC;AACD;;;;;;;;;AASC;AACD;;;AAGC;AACF;AACF;AAED;AACA;AACA;AACE;AACA;AACA;;AAGK;AAoBH;AAEA;AACE;;;AAGF;;;;AAMA;;;AAII;;;;;AAKJ;AAEA;AACA;AACA;AAEA;AAKM;AACA;AACA;;AAED;AAEC;;;;AA0BR;AAGF;;AAEE;AACI;;AAEN;;"}
|
|
@@ -85,7 +85,8 @@ const InputFileMultiplyAppendable = React.forwardRef(({ className, size, label,
|
|
|
85
85
|
},
|
|
86
86
|
};
|
|
87
87
|
}, [latest]);
|
|
88
|
-
|
|
88
|
+
const errorAttr = error || undefined;
|
|
89
|
+
return (jsxRuntime.jsxs(components_Layout_Stack_Stack.Stack, { align: "flex-start", className: classNames.wrapper, children: [hasFileList && !disabled && files.length > 0 && (jsxRuntime.jsx(components_Panel_Groupbox_Groupbox.Groupbox, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsxRuntime.jsx(components_InputFile_client_parts.FileListItem, { file: file, index: index, previewable: !!previewable, className: classNames.fileItem, handleDeleteClick: functions.handleDelete, handlePreviewClick: setPreviewFile }, index))) })), jsxRuntime.jsxs("span", { className: classNames.inputWrapper, children: [jsxRuntime.jsx("input", { ...rest, ref: ref, type: "file", disabled: disabled, multiple: true, className: classNames.input, "aria-invalid": errorAttr, "aria-labelledby": labelId, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onChange: functions.handleChange }), jsxRuntime.jsx(components_InputFile_client_parts.StyledFaFolderOpenIcon, { className: classNames.prefix }), jsxRuntime.jsx(components_InputFile_client_parts.LabelRender, { id: labelId, label: label })] }), previewable && (jsxRuntime.jsx(components_InputFile_client_FilePreviewDialog.FilePreviewDialog, { file: previewFile, searchable: previewable?.searchable, handleClose: functions.handleClosePreview, handleDownload: functions.handleDownload }))] }));
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
exports.InputFileMultiplyAppendable = InputFileMultiplyAppendable;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputFileMultiplyAppendable.cjs","sources":["../../../../src/components/InputFile/client/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<\n HTMLInputElement,\n Omit<LowerProps, 'multiple'>\n>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (input: HTMLInputElement, newFiles: File[]) => {\n latest.onChange?.(newFiles)\n\n const buff = new DataTransfer()\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n input.files = buff.files\n isUpdatingFilesRef.current = false\n\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n // Safari において、input.files への直接代入時はonChangeを発火させない\n if (isUpdatingFilesRef.current) {\n return\n }\n\n const newFiles = Array.from(e.target.files ?? [])\n\n updateFiles(e.target, [...latest.files, ...newFiles])\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n const input = e.currentTarget\n .closest('.smarthr-ui-InputFile')\n ?.querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"][type=\"file\"]')\n\n if (!input) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n input.value = ''\n\n updateFiles(input, newFiles)\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={ref}\n type=\"file\"\n disabled={disabled}\n multiple\n className={classNames.input}\n aria-invalid={
|
|
1
|
+
{"version":3,"file":"InputFileMultiplyAppendable.cjs","sources":["../../../../src/components/InputFile/client/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<\n HTMLInputElement,\n Omit<LowerProps, 'multiple'>\n>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (input: HTMLInputElement, newFiles: File[]) => {\n latest.onChange?.(newFiles)\n\n const buff = new DataTransfer()\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n input.files = buff.files\n isUpdatingFilesRef.current = false\n\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n // Safari において、input.files への直接代入時はonChangeを発火させない\n if (isUpdatingFilesRef.current) {\n return\n }\n\n const newFiles = Array.from(e.target.files ?? [])\n\n updateFiles(e.target, [...latest.files, ...newFiles])\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n const input = e.currentTarget\n .closest('.smarthr-ui-InputFile')\n ?.querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"][type=\"file\"]')\n\n if (!input) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n input.value = ''\n\n updateFiles(input, newFiles)\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n const errorAttr = error || undefined\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={ref}\n type=\"file\"\n disabled={disabled}\n multiple\n className={classNames.input}\n aria-invalid={errorAttr}\n aria-labelledby={labelId}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onChange={functions.handleChange}\n />\n <StyledFaFolderOpenIcon className={classNames.prefix} />\n <LabelRender id={labelId} label={label} />\n </span>\n {previewable && (\n <FilePreviewDialog\n file={previewFile}\n searchable={previewable?.searchable}\n handleClose={functions.handleClosePreview}\n handleDownload={functions.handleDownload}\n />\n )}\n </Stack>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAsBA;AAEO;;;AAUH;AAEA;AACE;;AAGE;AACA;;;;;;AAMJ;;AAGA;AAEA;AAEA;AACE;AACE;AAEA;AACA;AACE;AACF;AAEA;AACA;AACA;;AAGF;;AAGE;;AAEE;;;AAIA;AAEA;;AAEF;AACE;;AAEE;;;;AAMF;AACA;;AAGA;AAEA;;;;;;AAMA;AACA;;;;AAIA;AACA;;AAEA;;;AAGN;AAEA;;AA8CF;;"}
|
|
@@ -83,7 +83,8 @@ const InputFileMultiplyAppendable = forwardRef(({ className, size, label, hasFil
|
|
|
83
83
|
},
|
|
84
84
|
};
|
|
85
85
|
}, [latest]);
|
|
86
|
-
|
|
86
|
+
const errorAttr = error || undefined;
|
|
87
|
+
return (jsxs(Stack, { align: "flex-start", className: classNames.wrapper, children: [hasFileList && !disabled && files.length > 0 && (jsx(Groupbox, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsx(FileListItem, { file: file, index: index, previewable: !!previewable, className: classNames.fileItem, handleDeleteClick: functions.handleDelete, handlePreviewClick: setPreviewFile }, index))) })), jsxs("span", { className: classNames.inputWrapper, children: [jsx("input", { ...rest, ref: ref, type: "file", disabled: disabled, multiple: true, className: classNames.input, "aria-invalid": errorAttr, "aria-labelledby": labelId, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onChange: functions.handleChange }), jsx(StyledFaFolderOpenIcon, { className: classNames.prefix }), jsx(LabelRender, { id: labelId, label: label })] }), previewable && (jsx(FilePreviewDialog, { file: previewFile, searchable: previewable?.searchable, handleClose: functions.handleClosePreview, handleDownload: functions.handleDownload }))] }));
|
|
87
88
|
});
|
|
88
89
|
|
|
89
90
|
export { InputFileMultiplyAppendable };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputFileMultiplyAppendable.js","sources":["../../../../src/components/InputFile/client/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<\n HTMLInputElement,\n Omit<LowerProps, 'multiple'>\n>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (input: HTMLInputElement, newFiles: File[]) => {\n latest.onChange?.(newFiles)\n\n const buff = new DataTransfer()\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n input.files = buff.files\n isUpdatingFilesRef.current = false\n\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n // Safari において、input.files への直接代入時はonChangeを発火させない\n if (isUpdatingFilesRef.current) {\n return\n }\n\n const newFiles = Array.from(e.target.files ?? [])\n\n updateFiles(e.target, [...latest.files, ...newFiles])\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n const input = e.currentTarget\n .closest('.smarthr-ui-InputFile')\n ?.querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"][type=\"file\"]')\n\n if (!input) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n input.value = ''\n\n updateFiles(input, newFiles)\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={ref}\n type=\"file\"\n disabled={disabled}\n multiple\n className={classNames.input}\n aria-invalid={
|
|
1
|
+
{"version":3,"file":"InputFileMultiplyAppendable.js","sources":["../../../../src/components/InputFile/client/InputFileMultiplyAppendable.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\nexport const InputFileMultiplyAppendable = forwardRef<\n HTMLInputElement,\n Omit<LowerProps, 'multiple'>\n>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (input: HTMLInputElement, newFiles: File[]) => {\n latest.onChange?.(newFiles)\n\n const buff = new DataTransfer()\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n input.files = buff.files\n isUpdatingFilesRef.current = false\n\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n // Safari において、input.files への直接代入時はonChangeを発火させない\n if (isUpdatingFilesRef.current) {\n return\n }\n\n const newFiles = Array.from(e.target.files ?? [])\n\n updateFiles(e.target, [...latest.files, ...newFiles])\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n const input = e.currentTarget\n .closest('.smarthr-ui-InputFile')\n ?.querySelector<HTMLInputElement>('[data-smarthr-ui-input=\"true\"][type=\"file\"]')\n\n if (!input) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n // 削除後、同一ファイルを再選択可能にするためinput.valueをリセット\n input.value = ''\n\n updateFiles(input, newFiles)\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n const errorAttr = error || undefined\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={ref}\n type=\"file\"\n disabled={disabled}\n multiple\n className={classNames.input}\n aria-invalid={errorAttr}\n aria-labelledby={labelId}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onChange={functions.handleChange}\n />\n <StyledFaFolderOpenIcon className={classNames.prefix} />\n <LabelRender id={labelId} label={label} />\n </span>\n {previewable && (\n <FilePreviewDialog\n file={previewFile}\n searchable={previewable?.searchable}\n handleClose={functions.handleClosePreview}\n handleDownload={functions.handleDownload}\n />\n )}\n </Stack>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAsBA;AAEO;;;AAUH;AAEA;AACE;;AAGE;AACA;;;;;;AAMJ;;AAGA;AAEA;AAEA;AACE;AACE;AAEA;AACA;AACE;AACF;AAEA;AACA;AACA;;AAGF;;AAGE;;AAEE;;;AAIA;AAEA;;AAEF;AACE;;AAEE;;;;AAMF;AACA;;AAGA;AAEA;;;;;;AAMA;AACA;;;;AAIA;AACA;;AAEA;;;AAGN;AAEA;;AA8CF;;"}
|
|
@@ -80,7 +80,8 @@ const InputFileNative = React.forwardRef(({ className, size, label, hasFileList
|
|
|
80
80
|
},
|
|
81
81
|
};
|
|
82
82
|
}, [latest]);
|
|
83
|
-
|
|
83
|
+
const errorAttr = error || undefined;
|
|
84
|
+
return (jsxRuntime.jsxs(components_Layout_Stack_Stack.Stack, { align: "flex-start", className: classNames.wrapper, children: [hasFileList && !disabled && files.length > 0 && (jsxRuntime.jsx(components_Panel_Groupbox_Groupbox.Groupbox, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsxRuntime.jsx(components_InputFile_client_parts.FileListItem, { file: file, index: index, previewable: !!previewable, className: classNames.fileItem, handleDeleteClick: functions.handleDelete, handlePreviewClick: setPreviewFile }, index))) })), jsxRuntime.jsxs("span", { className: classNames.inputWrapper, children: [jsxRuntime.jsx("input", { ...rest, ref: mergedRef, type: "file", disabled: disabled, className: classNames.input, "aria-invalid": errorAttr, "aria-labelledby": labelId, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onChange: functions.handleChange }), jsxRuntime.jsx(components_InputFile_client_parts.StyledFaFolderOpenIcon, { className: classNames.prefix }), jsxRuntime.jsx(components_InputFile_client_parts.LabelRender, { id: labelId, label: label })] }), previewable && (jsxRuntime.jsx(components_InputFile_client_FilePreviewDialog.FilePreviewDialog, { file: previewFile, searchable: previewable?.searchable, handleClose: functions.handleClosePreview, handleDownload: functions.handleDownload }))] }));
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
exports.InputFileNative = InputFileNative;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputFileNative.cjs","sources":["../../../../src/components/InputFile/client/InputFileNative.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\ntype Props = Omit<LowerProps, 'multiple'> & {\n multiple?: boolean\n}\n\nexport const InputFileNative = forwardRef<HTMLInputElement, Props>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const innerRef = useRef<HTMLInputElement>(null)\n const mergedRef = useMergeRefs(innerRef, ref)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (newFiles: File[]) => {\n latest.onChange?.(newFiles)\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n if (!isUpdatingFilesRef.current) {\n updateFiles(Array.from(e.target.files ?? []))\n }\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n if (!innerRef.current) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n updateFiles(newFiles)\n\n const buff = new DataTransfer()\n\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n innerRef.current.files = buff.files\n isUpdatingFilesRef.current = false\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={mergedRef}\n type=\"file\"\n disabled={disabled}\n className={classNames.input}\n aria-invalid={
|
|
1
|
+
{"version":3,"file":"InputFileNative.cjs","sources":["../../../../src/components/InputFile/client/InputFileNative.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\ntype Props = Omit<LowerProps, 'multiple'> & {\n multiple?: boolean\n}\n\nexport const InputFileNative = forwardRef<HTMLInputElement, Props>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const innerRef = useRef<HTMLInputElement>(null)\n const mergedRef = useMergeRefs(innerRef, ref)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (newFiles: File[]) => {\n latest.onChange?.(newFiles)\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n if (!isUpdatingFilesRef.current) {\n updateFiles(Array.from(e.target.files ?? []))\n }\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n if (!innerRef.current) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n updateFiles(newFiles)\n\n const buff = new DataTransfer()\n\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n innerRef.current.files = buff.files\n isUpdatingFilesRef.current = false\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n const errorAttr = error || undefined\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={mergedRef}\n type=\"file\"\n disabled={disabled}\n className={classNames.input}\n aria-invalid={errorAttr}\n aria-labelledby={labelId}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onChange={functions.handleChange}\n />\n <StyledFaFolderOpenIcon className={classNames.prefix} />\n <LabelRender id={labelId} label={label} />\n </span>\n {previewable && (\n <FilePreviewDialog\n file={previewFile}\n searchable={previewable?.searchable}\n handleClose={functions.handleClosePreview}\n handleDownload={functions.handleDownload}\n />\n )}\n </Stack>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;AAuBA;AAMO;;;AAOH;AAEA;AACE;;AAGE;AACA;;;;;;AAMJ;;AAGA;AAEA;;AAGA;AAEA;AACE;AACE;;AAEF;;AAGE;AACE;AACE;;;AAGJ;AACE;;;AAIA;AACA;;AAIA;AAEA;AACE;AACF;AAEA;;AAEA;;;;;;AAMA;AACA;;;;AAIA;AACA;;AAEA;;;AAGN;AAEA;;AA6CF;;"}
|
|
@@ -78,7 +78,8 @@ const InputFileNative = forwardRef(({ className, size, label, hasFileList = true
|
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
80
|
}, [latest]);
|
|
81
|
-
|
|
81
|
+
const errorAttr = error || undefined;
|
|
82
|
+
return (jsxs(Stack, { align: "flex-start", className: classNames.wrapper, children: [hasFileList && !disabled && files.length > 0 && (jsx(Groupbox, { as: "ul", padding: BASE_COLUMN_PADDING, className: classNames.fileList, children: files.map((file, index) => (jsx(FileListItem, { file: file, index: index, previewable: !!previewable, className: classNames.fileItem, handleDeleteClick: functions.handleDelete, handlePreviewClick: setPreviewFile }, index))) })), jsxs("span", { className: classNames.inputWrapper, children: [jsx("input", { ...rest, ref: mergedRef, type: "file", disabled: disabled, className: classNames.input, "aria-invalid": errorAttr, "aria-labelledby": labelId, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true", onChange: functions.handleChange }), jsx(StyledFaFolderOpenIcon, { className: classNames.prefix }), jsx(LabelRender, { id: labelId, label: label })] }), previewable && (jsx(FilePreviewDialog, { file: previewFile, searchable: previewable?.searchable, handleClose: functions.handleClosePreview, handleDownload: functions.handleDownload }))] }));
|
|
82
83
|
});
|
|
83
84
|
|
|
84
85
|
export { InputFileNative };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputFileNative.js","sources":["../../../../src/components/InputFile/client/InputFileNative.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\ntype Props = Omit<LowerProps, 'multiple'> & {\n multiple?: boolean\n}\n\nexport const InputFileNative = forwardRef<HTMLInputElement, Props>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const innerRef = useRef<HTMLInputElement>(null)\n const mergedRef = useMergeRefs(innerRef, ref)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (newFiles: File[]) => {\n latest.onChange?.(newFiles)\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n if (!isUpdatingFilesRef.current) {\n updateFiles(Array.from(e.target.files ?? []))\n }\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n if (!innerRef.current) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n updateFiles(newFiles)\n\n const buff = new DataTransfer()\n\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n innerRef.current.files = buff.files\n isUpdatingFilesRef.current = false\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={mergedRef}\n type=\"file\"\n disabled={disabled}\n className={classNames.input}\n aria-invalid={
|
|
1
|
+
{"version":3,"file":"InputFileNative.js","sources":["../../../../src/components/InputFile/client/InputFileNative.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ChangeEvent,\n type MouseEvent,\n forwardRef,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { useMergeRefs } from '../../../hooks/client/useMergeRefs'\nimport { useLatest } from '../../../hooks/useLatest'\nimport { Stack } from '../../Layout'\nimport { Groupbox } from '../../Panel'\n\nimport { FilePreviewDialog } from './FilePreviewDialog'\nimport { FileListItem, LabelRender, StyledFaFolderOpenIcon } from './parts'\nimport { classNameGenerator } from './style'\n\nimport type { LowerProps } from '../types'\n\nconst BASE_COLUMN_PADDING = { block: 0.5, inline: 1 } as const\n\ntype Props = Omit<LowerProps, 'multiple'> & {\n multiple?: boolean\n}\n\nexport const InputFileNative = forwardRef<HTMLInputElement, Props>(\n (\n { className, size, label, hasFileList = true, previewable, onChange, disabled, error, ...rest },\n ref,\n ) => {\n const [files, setFiles] = useState<File[]>([])\n const [previewFile, setPreviewFile] = useState<File | null>(null)\n const labelId = useId()\n\n const classNames = useMemo(() => {\n const { wrapper, fileList, fileItem, inputWrapper, input, prefix } = classNameGenerator()\n\n return {\n wrapper: wrapper({ className }),\n inputWrapper: inputWrapper({ size }),\n fileList: fileList(),\n fileItem: fileItem(),\n input: input(),\n prefix: prefix(),\n }\n }, [size, className])\n\n // Safari において、input.files への直接代入時に onChange が発火することを防ぐためのフラグ\n const isUpdatingFilesRef = useRef(false)\n\n const innerRef = useRef<HTMLInputElement>(null)\n const mergedRef = useMergeRefs(innerRef, ref)\n\n const latest = useLatest({ onChange, files, previewFile })\n\n const functions = useMemo(() => {\n const updateFiles = (newFiles: File[]) => {\n latest.onChange?.(newFiles)\n setFiles(newFiles)\n }\n\n return {\n handleChange: (e: ChangeEvent<HTMLInputElement>) => {\n if (!isUpdatingFilesRef.current) {\n updateFiles(Array.from(e.target.files ?? []))\n }\n },\n handleDelete: (e: MouseEvent<HTMLButtonElement>) => {\n if (!innerRef.current) {\n return\n }\n\n const index = parseInt(e.currentTarget.value, 10)\n const newFiles = latest.files.filter((_, i) => index !== i)\n\n updateFiles(newFiles)\n\n const buff = new DataTransfer()\n\n newFiles.forEach((file) => {\n buff.items.add(file)\n })\n\n isUpdatingFilesRef.current = true\n innerRef.current.files = buff.files\n isUpdatingFilesRef.current = false\n },\n handleClosePreview: () => {\n setPreviewFile(null)\n },\n handleDownload: () => {\n const file = latest.previewFile\n if (!file) return\n\n const url = URL.createObjectURL(file)\n const a = document.createElement('a')\n a.href = url\n a.download = file.name\n a.click()\n URL.revokeObjectURL(url)\n },\n }\n }, [latest])\n\n const errorAttr = error || undefined\n\n return (\n <Stack align=\"flex-start\" className={classNames.wrapper}>\n {hasFileList && !disabled && files.length > 0 && (\n <Groupbox as=\"ul\" padding={BASE_COLUMN_PADDING} className={classNames.fileList}>\n {files.map((file, index) => (\n <FileListItem\n key={index}\n file={file}\n index={index}\n previewable={!!previewable}\n className={classNames.fileItem}\n handleDeleteClick={functions.handleDelete}\n handlePreviewClick={setPreviewFile}\n />\n ))}\n </Groupbox>\n )}\n <span className={classNames.inputWrapper}>\n <input\n {...rest}\n ref={mergedRef}\n type=\"file\"\n disabled={disabled}\n className={classNames.input}\n aria-invalid={errorAttr}\n aria-labelledby={labelId}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n onChange={functions.handleChange}\n />\n <StyledFaFolderOpenIcon className={classNames.prefix} />\n <LabelRender id={labelId} label={label} />\n </span>\n {previewable && (\n <FilePreviewDialog\n file={previewFile}\n searchable={previewable?.searchable}\n handleClose={functions.handleClosePreview}\n handleDownload={functions.handleDownload}\n />\n )}\n </Stack>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAuBA;AAMO;;;AAOH;AAEA;AACE;;AAGE;AACA;;;;;;AAMJ;;AAGA;AAEA;;AAGA;AAEA;AACE;AACE;;AAEF;;AAGE;AACE;AACE;;;AAGJ;AACE;;;AAIA;AACA;;AAIA;AAEA;AACE;AACF;AAEA;;AAEA;;;;;;AAMA;AACA;;;;AAIA;AACA;;AAEA;;;AAGN;AAEA;;AA6CF;;"}
|
|
@@ -13,7 +13,8 @@ const DatetimeLocalPicker = React.forwardRef(({ error, className, ...rest }, ref
|
|
|
13
13
|
inner: inner(),
|
|
14
14
|
};
|
|
15
15
|
}, [className]);
|
|
16
|
-
|
|
16
|
+
const errorAttr = error || undefined;
|
|
17
|
+
return (jsxRuntime.jsx("span", { className: classNames.wrapper, children: jsxRuntime.jsx("input", { ...rest, ref: ref, type: "datetime-local", className: classNames.inner, "aria-invalid": errorAttr, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true" }) }));
|
|
17
18
|
});
|
|
18
19
|
|
|
19
20
|
exports.DatetimeLocalPicker = DatetimeLocalPicker;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatetimeLocalPicker.cjs","sources":["../../../src/components/Picker/DatetimeLocalPicker.tsx"],"sourcesContent":["import { forwardRef, useMemo } from 'react'\n\nimport { classNameGenerator } from './style'\n\nimport type { PickerProps } from './types'\n\ntype Props = {\n /** フォームにエラーがあるかどうか */\n error?: boolean\n}\n\n/** @deprecated DatetimeLocalPicker は非推奨です。Input[type=\"datetime-local\"] を使ってください。 */\nexport const DatetimeLocalPicker = forwardRef<HTMLInputElement, PickerProps<Props>>(\n ({ error, className, ...rest }, ref) => {\n const classNames = useMemo(() => {\n const { wrapper, inner } = classNameGenerator('DatetimeLocal')\n\n return {\n wrapper: wrapper({ className }),\n inner: inner(),\n }\n }, [className])\n\n return (\n <span className={classNames.wrapper}>\n {/* eslint-disable-next-line smarthr/a11y-input-in-form-control */}\n <input\n {...rest}\n ref={ref}\n type=\"datetime-local\"\n className={classNames.inner}\n aria-invalid={error
|
|
1
|
+
{"version":3,"file":"DatetimeLocalPicker.cjs","sources":["../../../src/components/Picker/DatetimeLocalPicker.tsx"],"sourcesContent":["import { forwardRef, useMemo } from 'react'\n\nimport { classNameGenerator } from './style'\n\nimport type { PickerProps } from './types'\n\ntype Props = {\n /** フォームにエラーがあるかどうか */\n error?: boolean\n}\n\n/** @deprecated DatetimeLocalPicker は非推奨です。Input[type=\"datetime-local\"] を使ってください。 */\nexport const DatetimeLocalPicker = forwardRef<HTMLInputElement, PickerProps<Props>>(\n ({ error, className, ...rest }, ref) => {\n const classNames = useMemo(() => {\n const { wrapper, inner } = classNameGenerator('DatetimeLocal')\n\n return {\n wrapper: wrapper({ className }),\n inner: inner(),\n }\n }, [className])\n\n const errorAttr = error || undefined\n\n return (\n <span className={classNames.wrapper}>\n {/* eslint-disable-next-line smarthr/a11y-input-in-form-control */}\n <input\n {...rest}\n ref={ref}\n type=\"datetime-local\"\n className={classNames.inner}\n aria-invalid={errorAttr}\n data-smarthr-ui-input-error={errorAttr}\n data-smarthr-ui-input=\"true\"\n />\n </span>\n )\n },\n)\n"],"names":["forwardRef","useMemo","classNameGenerator","_jsx"],"mappings":";;;;;;AAWA;AACO,MAAM,mBAAmB,GAAGA,gBAAU,CAC3C,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,KAAI;AACrC,IAAA,MAAM,UAAU,GAAGC,aAAO,CAAC,MAAK;QAC9B,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAGC,0CAAkB,CAAC,eAAe,CAAC;QAE9D,OAAO;AACL,YAAA,OAAO,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;YAC/B,KAAK,EAAE,KAAK,EAAE;SACf;AACH,IAAA,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;AAEf,IAAA,MAAM,SAAS,GAAG,KAAK,IAAI,SAAS;AAEpC,IAAA,QACEC,cAAA,CAAA,MAAA,EAAA,EAAM,SAAS,EAAE,UAAU,CAAC,OAAO,EAAA,QAAA,EAEjCA,cAAA,CAAA,OAAA,EAAA,EAAA,GACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,IAAI,EAAC,gBAAgB,EACrB,SAAS,EAAE,UAAU,CAAC,KAAK,EAAA,cAAA,EACb,SAAS,EAAA,6BAAA,EACM,SAAS,EAAA,uBAAA,EAChB,MAAM,EAAA,CAC5B,EAAA,CACG;AAEX,CAAC;;"}
|
|
@@ -11,7 +11,8 @@ const DatetimeLocalPicker = forwardRef(({ error, className, ...rest }, ref) => {
|
|
|
11
11
|
inner: inner(),
|
|
12
12
|
};
|
|
13
13
|
}, [className]);
|
|
14
|
-
|
|
14
|
+
const errorAttr = error || undefined;
|
|
15
|
+
return (jsx("span", { className: classNames.wrapper, children: jsx("input", { ...rest, ref: ref, type: "datetime-local", className: classNames.inner, "aria-invalid": errorAttr, "data-smarthr-ui-input-error": errorAttr, "data-smarthr-ui-input": "true" }) }));
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
export { DatetimeLocalPicker };
|