smarthr-ui 98.0.0 → 98.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/Combobox/ItemButton.cjs +27 -26
- package/lib/components/Combobox/ItemButton.cjs.map +1 -1
- package/lib/components/Combobox/ItemButton.js +28 -27
- package/lib/components/Combobox/ItemButton.js.map +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs +35 -52
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.cjs.map +1 -1
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js +35 -52
- package/lib/components/Combobox/SingleCombobox/SingleCombobox.js.map +1 -1
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs +9 -3
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.cjs.map +1 -1
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js +9 -3
- package/lib/components/Dialog/RemoteDialogTrigger/RemoteDialogTrigger.js.map +1 -1
- package/lib/components/Disclosure/DisclosureTrigger.cjs +8 -4
- package/lib/components/Disclosure/DisclosureTrigger.cjs.map +1 -1
- package/lib/components/Disclosure/DisclosureTrigger.js +8 -4
- package/lib/components/Disclosure/DisclosureTrigger.js.map +1 -1
- package/lib/components/FileViewer/FileViewer.cjs +35 -2
- package/lib/components/FileViewer/FileViewer.cjs.map +1 -1
- package/lib/components/FileViewer/FileViewer.js +37 -4
- package/lib/components/FileViewer/FileViewer.js.map +1 -1
- package/lib/components/FileViewer/PDFViewer.cjs +59 -2
- package/lib/components/FileViewer/PDFViewer.cjs.map +1 -1
- package/lib/components/FileViewer/PDFViewer.d.ts +6 -1
- package/lib/components/FileViewer/PDFViewer.js +60 -3
- package/lib/components/FileViewer/PDFViewer.js.map +1 -1
- package/lib/components/FileViewer/SearchController.cjs +105 -0
- package/lib/components/FileViewer/SearchController.cjs.map +1 -0
- package/lib/components/FileViewer/SearchController.d.ts +7 -0
- package/lib/components/FileViewer/SearchController.js +103 -0
- package/lib/components/FileViewer/SearchController.js.map +1 -0
- package/lib/components/FileViewer/buildCustomTextRenderer.cjs +55 -0
- package/lib/components/FileViewer/buildCustomTextRenderer.cjs.map +1 -0
- package/lib/components/FileViewer/buildCustomTextRenderer.d.ts +9 -0
- package/lib/components/FileViewer/buildCustomTextRenderer.js +50 -0
- package/lib/components/FileViewer/buildCustomTextRenderer.js.map +1 -0
- package/lib/components/FileViewer/types.d.ts +7 -0
- package/lib/components/FileViewer/usePDFSearch.cjs +148 -0
- package/lib/components/FileViewer/usePDFSearch.cjs.map +1 -0
- package/lib/components/FileViewer/usePDFSearch.d.ts +19 -0
- package/lib/components/FileViewer/usePDFSearch.js +143 -0
- package/lib/components/FileViewer/usePDFSearch.js.map +1 -0
- package/lib/components/Icon/FaIcon.js +1 -1
- package/lib/hooks/useLatest.cjs +33 -0
- package/lib/hooks/useLatest.cjs.map +1 -0
- package/lib/hooks/useLatest.d.ts +19 -0
- package/lib/hooks/useLatest.js +31 -0
- package/lib/hooks/useLatest.js.map +1 -0
- package/lib/intl/locales/en_us.json.cjs +3 -0
- package/lib/intl/locales/en_us.json.cjs.map +1 -1
- package/lib/intl/locales/en_us.json.js +3 -0
- package/lib/intl/locales/en_us.json.js.map +1 -1
- package/lib/intl/locales/id_id.json.cjs +3 -0
- package/lib/intl/locales/id_id.json.cjs.map +1 -1
- package/lib/intl/locales/id_id.json.js +3 -0
- package/lib/intl/locales/id_id.json.js.map +1 -1
- package/lib/intl/locales/index.d.ts +27 -0
- package/lib/intl/locales/ja.cjs +3 -0
- package/lib/intl/locales/ja.cjs.map +1 -1
- package/lib/intl/locales/ja.d.ts +3 -0
- package/lib/intl/locales/ja.js +3 -0
- package/lib/intl/locales/ja.js.map +1 -1
- package/lib/intl/locales/ja.json.cjs +3 -0
- package/lib/intl/locales/ja.json.cjs.map +1 -1
- package/lib/intl/locales/ja.json.js +3 -0
- package/lib/intl/locales/ja.json.js.map +1 -1
- package/lib/intl/locales/ja_easy.json.cjs +3 -0
- package/lib/intl/locales/ja_easy.json.cjs.map +1 -1
- package/lib/intl/locales/ja_easy.json.js +3 -0
- package/lib/intl/locales/ja_easy.json.js.map +1 -1
- package/lib/intl/locales/ko_kr.json.cjs +3 -0
- package/lib/intl/locales/ko_kr.json.cjs.map +1 -1
- package/lib/intl/locales/ko_kr.json.js +3 -0
- package/lib/intl/locales/ko_kr.json.js.map +1 -1
- package/lib/intl/locales/pt_br.json.cjs +3 -0
- package/lib/intl/locales/pt_br.json.cjs.map +1 -1
- package/lib/intl/locales/pt_br.json.js +3 -0
- package/lib/intl/locales/pt_br.json.js.map +1 -1
- package/lib/intl/locales/vi_vn.json.cjs +3 -0
- package/lib/intl/locales/vi_vn.json.cjs.map +1 -1
- package/lib/intl/locales/vi_vn.json.js +3 -0
- package/lib/intl/locales/vi_vn.json.js.map +1 -1
- package/lib/intl/locales/zh_hans_cn.json.cjs +3 -0
- package/lib/intl/locales/zh_hans_cn.json.cjs.map +1 -1
- package/lib/intl/locales/zh_hans_cn.json.js +3 -0
- package/lib/intl/locales/zh_hans_cn.json.js.map +1 -1
- package/lib/intl/locales/zh_hant_tw.json.cjs +3 -0
- package/lib/intl/locales/zh_hant_tw.json.cjs.map +1 -1
- package/lib/intl/locales/zh_hant_tw.json.js +3 -0
- package/lib/intl/locales/zh_hant_tw.json.js.map +1 -1
- package/metadata.json +1 -1
- package/package.json +1 -1
- package/smarthr-ui.css +35 -4
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { Decimal } from './../../vendor/.pnpm/decimal.js@10.6.0/vendor/decimal.js/decimal.js';
|
|
4
|
-
import { memo,
|
|
4
|
+
import { memo, useMemo, useCallback, useRef, useState, useEffect } from 'react';
|
|
5
|
+
import { tv as ce } from './../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
|
|
6
|
+
import { useEnvironment } from '../../hooks/useEnvironment/useEnvironment.js';
|
|
7
|
+
import '../../themes/createSpacing.js';
|
|
8
|
+
import '../../_virtual/index.js';
|
|
9
|
+
import '../../_virtual/index2.js';
|
|
10
|
+
import '../../themes/createColor.js';
|
|
11
|
+
import '../../themes/createTextColor.js';
|
|
12
|
+
import 'styled-components';
|
|
13
|
+
import '../../themes/createShadow/defaultShadow.js';
|
|
14
|
+
import '../../themes/createWidth.js';
|
|
15
|
+
import '../../themes/createBackgroundColor.js';
|
|
16
|
+
import '../../themes/createBorder.js';
|
|
17
|
+
import '../../hooks/useTheme.js';
|
|
5
18
|
import '../../intl/IntlProvider.js';
|
|
6
19
|
import 'react-intl';
|
|
7
20
|
import { Localizer } from '../../intl/Localizer.js';
|
|
@@ -32,6 +45,8 @@ import { Scroller } from '../Scroller/Scroller.js';
|
|
|
32
45
|
import { VisuallyHiddenText } from '../VisuallyHiddenText/VisuallyHiddenText.js';
|
|
33
46
|
import { ImageViewer } from './ImageViewer.js';
|
|
34
47
|
import { PDFViewer } from './PDFViewer.js';
|
|
48
|
+
import { SearchController } from './SearchController.js';
|
|
49
|
+
import { usePDFSearch } from './usePDFSearch.js';
|
|
35
50
|
|
|
36
51
|
const defaultScaleStep = new Decimal(0.2);
|
|
37
52
|
const defaultScaleSteps = [0.2, 0.6, 1, 1.6, 2, 3];
|
|
@@ -41,6 +56,8 @@ const FileViewer = ({ file, scaleStep, scaleSteps, width: fixedWidth, onPassword
|
|
|
41
56
|
const [loaded, setLoaded] = useState(false);
|
|
42
57
|
const [rotation, setRotation] = useState(undefined);
|
|
43
58
|
const [width, setWidth] = useState(fixedWidth ?? 0);
|
|
59
|
+
const isPDF = file.contentType === 'application/pdf';
|
|
60
|
+
const search = usePDFSearch(file.url);
|
|
44
61
|
const internalScaleStep = useMemo(() => (scaleStep ? new Decimal(scaleStep) : defaultScaleStep), [scaleStep]);
|
|
45
62
|
const scaleUp = useCallback(() => {
|
|
46
63
|
setScale((currentScale) => new Decimal(currentScale).add(internalScaleStep).toNumber());
|
|
@@ -72,9 +89,25 @@ const FileViewer = ({ file, scaleStep, scaleSteps, width: fixedWidth, onPassword
|
|
|
72
89
|
resizeObserver.disconnect();
|
|
73
90
|
};
|
|
74
91
|
}, [fixedWidth]);
|
|
75
|
-
return (jsxs(Scroller, { direction: "both", className: "shr-flex shr-h-full shr-w-full shr-flex-col shr-gap-2 shr-bg-scrim shr-bg-[radial-gradient(theme(textColor.black)_1px,_transparent_0)] shr-bg-[length:16px_16px]", ref: ref, children: [jsx("div", { className: "shr-sticky shr-start-0 shr-top-0 shr-z-[1] shr-flex shr-w-full shr-flex-shrink-0 shr-gap-0.5", children: jsx(Controller, { scale: scale, setScale: setScale, scaleSteps: scaleSteps || defaultScaleSteps, onClickScaleUpButton: scaleUp, onClickScaleDownButton: scaleDown, onClickRotateButton: rotate }) }), jsxs("div", { className: "shr-z-[0] shr-mx-auto shr-my-0 shr-box-border shr-flex shr-w-fit shr-flex-shrink-0 shr-grow shr-items-center shr-justify-center shr-px-2 shr-pb-2", children: [!loaded && (jsx("div", { className: "shr-pointer-events-none shr-fixed shr-inset-0 shr-flex shr-h-full shr-w-full shr-items-center shr-justify-center", children: jsx(Loader, { type: "light", size: "M" }) })), jsx("div", { className: !loaded ? 'shr-invisible' : '', children:
|
|
92
|
+
return (jsxs(Scroller, { direction: "both", className: "shr-flex shr-h-full shr-w-full shr-flex-col shr-gap-2 shr-bg-scrim shr-bg-[radial-gradient(theme(textColor.black)_1px,_transparent_0)] shr-bg-[length:16px_16px]", ref: ref, children: [jsx("div", { className: "shr-sticky shr-start-0 shr-top-0 shr-z-[1] shr-flex shr-w-full shr-flex-shrink-0 shr-gap-0.5", children: jsx(Controller, { scale: scale, setScale: setScale, scaleSteps: scaleSteps || defaultScaleSteps, onClickScaleUpButton: scaleUp, onClickScaleDownButton: scaleDown, onClickRotateButton: rotate, searchController: isPDF ? jsx(SearchController, { search: search }) : undefined }) }), jsxs("div", { className: "shr-z-[0] shr-mx-auto shr-my-0 shr-box-border shr-flex shr-w-fit shr-flex-shrink-0 shr-grow shr-items-center shr-justify-center shr-px-2 shr-pb-2", children: [!loaded && (jsx("div", { className: "shr-pointer-events-none shr-fixed shr-inset-0 shr-flex shr-h-full shr-w-full shr-items-center shr-justify-center", children: jsx(Loader, { type: "light", size: "M" }) })), jsx("div", { className: !loaded ? 'shr-invisible' : '', children: isPDF ? (jsx(PDFViewer, { scale: scale, rotation: rotation, file: file, width: width, onLoad: handleLoaded, onPDFLoaded: handlePDFLoaded, onPassword: onPassword, onLoadError: onLoadError, search: search })) : file.contentType.startsWith('image/') ? (jsx(ImageViewer, { scale: scale, rotation: rotation, file: file, width: width, onLoad: handleLoaded, onLoadError: onLoadError })) : (jsx(Localizer, { id: "smarthr-ui/FileViewer/unsupportedFileText", defaultText: "\u30B5\u30DD\u30FC\u30C8\u3055\u308C\u3066\u3044\u306A\u3044\u5F62\u5F0F\u306E\u30D5\u30A1\u30A4\u30EB\u3067\u3059\u3002" })) })] })] }));
|
|
76
93
|
};
|
|
77
|
-
const
|
|
94
|
+
const controllerClassNameGenerator = ce({
|
|
95
|
+
base: 'shr-sticky shr-grid shr-w-full shr-items-center shr-bg-scrim shr-py-0.5 shr-shadow-layer-1',
|
|
96
|
+
variants: {
|
|
97
|
+
mobile: {
|
|
98
|
+
true: 'shr-gap-0.5 shr-px-1',
|
|
99
|
+
false: 'shr-grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] shr-gap-1.5 shr-px-2',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const Controller = memo(({ scale, setScale, scaleSteps, onClickScaleUpButton, onClickScaleDownButton, onClickRotateButton, searchController, }) => {
|
|
104
|
+
const { mobile } = useEnvironment();
|
|
105
|
+
const className = useMemo(() => controllerClassNameGenerator({ mobile }), [mobile]);
|
|
106
|
+
const onClickScaleStep = useCallback((e) => setScale(Number(e.currentTarget.value)), [setScale]);
|
|
107
|
+
return (jsxs("div", { className: className, children: [!mobile && jsx("div", { role: "presentation", "aria-hidden": "true" }), jsxs(Cluster, { gap: 0.5, className: "shr-justify-self-center", children: [jsxs("div", { className: "shr-border-shorthand shr-flex shr-divide-x shr-divide-solid shr-overflow-hidden shr-rounded-m", children: [jsx(Button, { onClick: onClickScaleDownButton, disabled: scale <= scaleSteps[0], className: "shr-rounded-r-none shr-border-none", children: jsx(FaMagnifyingGlassMinusIcon, { alt: jsx(Localizer, { id: "smarthr-ui/FileViewer/scaleDownAlt", defaultText: "\u7E2E\u5C0F" }) }) }), jsx(DropdownMenuButton, { trigger: jsxs(Fragment, { children: [jsx(VisuallyHiddenText, { children: jsx(Localizer, { id: "smarthr-ui/FileViewer/scaleRateLabel", defaultText: "\u62E1\u5927\u7387" }) }), `${(scale * 100).toFixed(0)}%`] }), className: "shr-border-y-0 shr-border-[theme(borderColor.default)] [&_.smarthr-ui-Button]:shr-rounded-none [&_.smarthr-ui-Button]:shr-border-[transparent]", children: scaleSteps.map((step) => (jsx(Button, { value: step, onClick: onClickScaleStep, className: "shr-rounded-none shr-border-0", children: `${(step * 100).toFixed(0)}%` }, step.toString()))) }), jsx(Button, { onClick: onClickScaleUpButton, className: "shr-rounded-l-none shr-border-0", children: jsx(FaMagnifyingGlassPlusIcon, { alt: jsx(Localizer, { id: "smarthr-ui/FileViewer/scaleUpAlt", defaultText: "\u62E1\u5927" }) }) })] }), jsx(Button, { onClick: onClickRotateButton, className: "shr-p-0.75", children: jsx(FaArrowRotateLeftIcon, { alt: jsx(Localizer, { id: "smarthr-ui/FileViewer/rotateAlt", defaultText: "\u5DE6\u56DE\u8EE2" }) }) })] }), searchController ? (jsx("div", { className: "shr-min-w-0 shr-justify-self-stretch", children: searchController })) : (
|
|
108
|
+
/* PC 表示時のときに中央の操作ボタンたちを中央へ寄せるための空のスペーサー */
|
|
109
|
+
!mobile && jsx("div", { role: "presentation", "aria-hidden": "true" }))] }));
|
|
110
|
+
});
|
|
78
111
|
|
|
79
112
|
export { FileViewer };
|
|
80
113
|
//# sourceMappingURL=FileViewer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileViewer.js","sources":["../../../src/components/FileViewer/FileViewer.tsx"],"sourcesContent":["'use client'\n\nimport Decimal from 'decimal.js'\nimport {\n type ComponentProps,\n type FC,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\n\nimport { Localizer } from '../../intl'\nimport { Button } from '../Button'\nimport { DropdownMenuButton } from '../Dropdown'\nimport {\n FaArrowRotateLeftIcon,\n FaMagnifyingGlassMinusIcon,\n FaMagnifyingGlassPlusIcon,\n} from '../Icon'\nimport { Cluster } from '../Layout'\nimport { Loader } from '../Loader'\nimport { Scroller } from '../Scroller'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { ImageViewer } from './ImageViewer'\nimport { PDFViewer } from './PDFViewer'\n\nimport type { FileForViewer } from './types'\n\nconst defaultScaleStep = new Decimal(0.2)\nconst defaultScaleSteps = [0.2, 0.6, 1, 1.6, 2, 3]\n\ntype Props = {\n file: FileForViewer\n width?: number\n\n /*\n * 拡大縮小率のステップを、100%を1とした配列で指定します。\n * */\n scaleSteps?: number[]\n\n scaleStep?: number\n onPassword?: ComponentProps<typeof PDFViewer>['onPassword']\n onLoadError?: () => void\n}\n\nexport const FileViewer: FC<Props> = ({\n file,\n scaleStep,\n scaleSteps,\n width: fixedWidth,\n onPassword,\n onLoadError,\n}) => {\n const ref = useRef<HTMLDivElement>(null)\n const [scale, setScale] = useState(1)\n const [loaded, setLoaded] = useState(false)\n const [rotation, setRotation] = useState<number | undefined>(undefined)\n const [width, setWidth] = useState(fixedWidth ?? 0)\n\n const internalScaleStep = useMemo(\n () => (scaleStep ? new Decimal(scaleStep) : defaultScaleStep),\n [scaleStep],\n )\n\n const scaleUp = useCallback(() => {\n setScale((currentScale) => new Decimal(currentScale).add(internalScaleStep).toNumber())\n }, [internalScaleStep])\n\n const scaleDown = useCallback(() => {\n setScale((currentScale) => new Decimal(currentScale).sub(internalScaleStep).toNumber())\n }, [internalScaleStep])\n\n const rotate = useCallback(() => {\n // HINT: react-pdf側のAnnotationLayer.cssではマイナスの回転に対応しておらず、また0, 90, 180, 270度のみ対応しているため、-90度の場合は+270度として扱う\n const currentRotation = rotation ?? 0\n const newRotation = currentRotation === 0 ? 270 : currentRotation - 90\n setRotation(newRotation)\n }, [rotation])\n\n const handleLoaded = useCallback(() => {\n setLoaded(true)\n }, [])\n\n const handlePDFLoaded = useCallback((defaultRotation: number) => {\n setRotation(defaultRotation)\n }, [])\n\n useEffect(() => {\n if (!ref.current || fixedWidth !== undefined) {\n return\n }\n\n const resizeObserver = new ResizeObserver(() => {\n setWidth((ref.current?.clientWidth ?? 0) - 64)\n })\n\n resizeObserver.observe(ref.current)\n\n return () => {\n resizeObserver.disconnect()\n }\n }, [fixedWidth])\n\n return (\n <Scroller\n direction=\"both\"\n className=\"shr-flex shr-h-full shr-w-full shr-flex-col shr-gap-2 shr-bg-scrim shr-bg-[radial-gradient(theme(textColor.black)_1px,_transparent_0)] shr-bg-[length:16px_16px]\"\n ref={ref}\n >\n <div className=\"shr-sticky shr-start-0 shr-top-0 shr-z-[1] shr-flex shr-w-full shr-flex-shrink-0 shr-gap-0.5\">\n <Controller\n scale={scale}\n setScale={setScale}\n scaleSteps={scaleSteps || defaultScaleSteps}\n onClickScaleUpButton={scaleUp}\n onClickScaleDownButton={scaleDown}\n onClickRotateButton={rotate}\n />\n </div>\n <div className=\"shr-z-[0] shr-mx-auto shr-my-0 shr-box-border shr-flex shr-w-fit shr-flex-shrink-0 shr-grow shr-items-center shr-justify-center shr-px-2 shr-pb-2\">\n {!loaded && (\n <div className=\"shr-pointer-events-none shr-fixed shr-inset-0 shr-flex shr-h-full shr-w-full shr-items-center shr-justify-center\">\n <Loader type=\"light\" size=\"M\" />\n </div>\n )}\n <div className={!loaded ? 'shr-invisible' : ''}>\n {
|
|
1
|
+
{"version":3,"file":"FileViewer.js","sources":["../../../src/components/FileViewer/FileViewer.tsx"],"sourcesContent":["'use client'\n\nimport Decimal from 'decimal.js'\nimport {\n type ComponentProps,\n type FC,\n type MouseEvent,\n type ReactNode,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnvironment } from '../../hooks/useEnvironment'\nimport { Localizer } from '../../intl'\nimport { Button } from '../Button'\nimport { DropdownMenuButton } from '../Dropdown'\nimport {\n FaArrowRotateLeftIcon,\n FaMagnifyingGlassMinusIcon,\n FaMagnifyingGlassPlusIcon,\n} from '../Icon'\nimport { Cluster } from '../Layout'\nimport { Loader } from '../Loader'\nimport { Scroller } from '../Scroller'\nimport { VisuallyHiddenText } from '../VisuallyHiddenText'\n\nimport { ImageViewer } from './ImageViewer'\nimport { PDFViewer } from './PDFViewer'\nimport { SearchController } from './SearchController'\nimport { usePDFSearch } from './usePDFSearch'\n\nimport type { FileForViewer } from './types'\n\nconst defaultScaleStep = new Decimal(0.2)\nconst defaultScaleSteps = [0.2, 0.6, 1, 1.6, 2, 3]\n\ntype Props = {\n file: FileForViewer\n width?: number\n\n /*\n * 拡大縮小率のステップを、100%を1とした配列で指定します。\n * */\n scaleSteps?: number[]\n\n scaleStep?: number\n onPassword?: ComponentProps<typeof PDFViewer>['onPassword']\n onLoadError?: () => void\n}\n\nexport const FileViewer: FC<Props> = ({\n file,\n scaleStep,\n scaleSteps,\n width: fixedWidth,\n onPassword,\n onLoadError,\n}) => {\n const ref = useRef<HTMLDivElement>(null)\n const [scale, setScale] = useState(1)\n const [loaded, setLoaded] = useState(false)\n const [rotation, setRotation] = useState<number | undefined>(undefined)\n const [width, setWidth] = useState(fixedWidth ?? 0)\n const isPDF = file.contentType === 'application/pdf'\n\n const search = usePDFSearch(file.url)\n\n const internalScaleStep = useMemo(\n () => (scaleStep ? new Decimal(scaleStep) : defaultScaleStep),\n [scaleStep],\n )\n\n const scaleUp = useCallback(() => {\n setScale((currentScale) => new Decimal(currentScale).add(internalScaleStep).toNumber())\n }, [internalScaleStep])\n\n const scaleDown = useCallback(() => {\n setScale((currentScale) => new Decimal(currentScale).sub(internalScaleStep).toNumber())\n }, [internalScaleStep])\n\n const rotate = useCallback(() => {\n // HINT: react-pdf側のAnnotationLayer.cssではマイナスの回転に対応しておらず、また0, 90, 180, 270度のみ対応しているため、-90度の場合は+270度として扱う\n const currentRotation = rotation ?? 0\n const newRotation = currentRotation === 0 ? 270 : currentRotation - 90\n setRotation(newRotation)\n }, [rotation])\n\n const handleLoaded = useCallback(() => {\n setLoaded(true)\n }, [])\n\n const handlePDFLoaded = useCallback((defaultRotation: number) => {\n setRotation(defaultRotation)\n }, [])\n\n useEffect(() => {\n if (!ref.current || fixedWidth !== undefined) {\n return\n }\n\n const resizeObserver = new ResizeObserver(() => {\n setWidth((ref.current?.clientWidth ?? 0) - 64)\n })\n\n resizeObserver.observe(ref.current)\n\n return () => {\n resizeObserver.disconnect()\n }\n }, [fixedWidth])\n\n return (\n <Scroller\n direction=\"both\"\n className=\"shr-flex shr-h-full shr-w-full shr-flex-col shr-gap-2 shr-bg-scrim shr-bg-[radial-gradient(theme(textColor.black)_1px,_transparent_0)] shr-bg-[length:16px_16px]\"\n ref={ref}\n >\n <div className=\"shr-sticky shr-start-0 shr-top-0 shr-z-[1] shr-flex shr-w-full shr-flex-shrink-0 shr-gap-0.5\">\n <Controller\n scale={scale}\n setScale={setScale}\n scaleSteps={scaleSteps || defaultScaleSteps}\n onClickScaleUpButton={scaleUp}\n onClickScaleDownButton={scaleDown}\n onClickRotateButton={rotate}\n searchController={isPDF ? <SearchController search={search} /> : undefined}\n />\n </div>\n <div className=\"shr-z-[0] shr-mx-auto shr-my-0 shr-box-border shr-flex shr-w-fit shr-flex-shrink-0 shr-grow shr-items-center shr-justify-center shr-px-2 shr-pb-2\">\n {!loaded && (\n <div className=\"shr-pointer-events-none shr-fixed shr-inset-0 shr-flex shr-h-full shr-w-full shr-items-center shr-justify-center\">\n <Loader type=\"light\" size=\"M\" />\n </div>\n )}\n <div className={!loaded ? 'shr-invisible' : ''}>\n {isPDF ? (\n <PDFViewer\n scale={scale}\n rotation={rotation}\n file={file}\n width={width}\n onLoad={handleLoaded}\n onPDFLoaded={handlePDFLoaded}\n onPassword={onPassword}\n onLoadError={onLoadError}\n search={search}\n />\n ) : file.contentType.startsWith('image/') ? (\n <ImageViewer\n scale={scale}\n rotation={rotation}\n file={file}\n width={width}\n onLoad={handleLoaded}\n onLoadError={onLoadError}\n />\n ) : (\n <Localizer\n id=\"smarthr-ui/FileViewer/unsupportedFileText\"\n defaultText=\"サポートされていない形式のファイルです。\"\n />\n )}\n </div>\n </div>\n </Scroller>\n )\n}\n\ntype ControllerProps = {\n scale: number\n setScale: (scale: number) => void\n scaleSteps: number[]\n onClickScaleUpButton: () => void\n onClickScaleDownButton: () => void\n onClickRotateButton: () => void\n searchController?: ReactNode\n}\n\nconst controllerClassNameGenerator = tv({\n base: 'shr-sticky shr-grid shr-w-full shr-items-center shr-bg-scrim shr-py-0.5 shr-shadow-layer-1',\n variants: {\n mobile: {\n true: 'shr-gap-0.5 shr-px-1',\n false: 'shr-grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] shr-gap-1.5 shr-px-2',\n },\n },\n})\n\nconst Controller: FC<ControllerProps> = memo(\n ({\n scale,\n setScale,\n scaleSteps,\n onClickScaleUpButton,\n onClickScaleDownButton,\n onClickRotateButton,\n searchController,\n }) => {\n const { mobile } = useEnvironment()\n const className = useMemo(() => controllerClassNameGenerator({ mobile }), [mobile])\n\n const onClickScaleStep = useCallback(\n (e: MouseEvent<HTMLButtonElement>) => setScale(Number(e.currentTarget.value)),\n [setScale],\n )\n\n return (\n <div className={className}>\n {/* PC 表示時のときに中央の操作ボタンたちを中央へ寄せるための空のスペーサー */}\n {!mobile && <div role=\"presentation\" aria-hidden=\"true\" />}\n <Cluster gap={0.5} className=\"shr-justify-self-center\">\n <div className=\"shr-border-shorthand shr-flex shr-divide-x shr-divide-solid shr-overflow-hidden shr-rounded-m\">\n <Button\n onClick={onClickScaleDownButton}\n disabled={scale <= scaleSteps[0]}\n className=\"shr-rounded-r-none shr-border-none\"\n >\n <FaMagnifyingGlassMinusIcon\n alt={<Localizer id=\"smarthr-ui/FileViewer/scaleDownAlt\" defaultText=\"縮小\" />}\n />\n </Button>\n <DropdownMenuButton\n trigger={\n <>\n <VisuallyHiddenText>\n <Localizer id=\"smarthr-ui/FileViewer/scaleRateLabel\" defaultText=\"拡大率\" />\n </VisuallyHiddenText>\n {`${(scale * 100).toFixed(0)}%`}\n </>\n }\n className=\"shr-border-y-0 shr-border-[theme(borderColor.default)] [&_.smarthr-ui-Button]:shr-rounded-none [&_.smarthr-ui-Button]:shr-border-[transparent]\"\n >\n {scaleSteps.map((step) => (\n <Button\n key={step.toString()}\n value={step}\n onClick={onClickScaleStep}\n className=\"shr-rounded-none shr-border-0\"\n >\n {`${(step * 100).toFixed(0)}%`}\n </Button>\n ))}\n </DropdownMenuButton>\n <Button onClick={onClickScaleUpButton} className=\"shr-rounded-l-none shr-border-0\">\n <FaMagnifyingGlassPlusIcon\n alt={<Localizer id=\"smarthr-ui/FileViewer/scaleUpAlt\" defaultText=\"拡大\" />}\n />\n </Button>\n </div>\n <Button onClick={onClickRotateButton} className=\"shr-p-0.75\">\n <FaArrowRotateLeftIcon\n alt={<Localizer id=\"smarthr-ui/FileViewer/rotateAlt\" defaultText=\"左回転\" />}\n />\n </Button>\n </Cluster>\n {searchController ? (\n <div className=\"shr-min-w-0 shr-justify-self-stretch\">{searchController}</div>\n ) : (\n /* PC 表示時のときに中央の操作ボタンたちを中央へ寄せるための空のスペーサー */\n !mobile && <div role=\"presentation\" aria-hidden=\"true\" />\n )}\n </div>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA;AACA;;AAwBE;;;;AAIA;AACA;;;AASA;;AAEA;AAEA;;AAEA;AAEA;;AAEE;AACA;;AAEF;AAEA;;;AAIA;;;;;;;AASE;AACE;AACF;AAEA;AAEA;;AAEA;AACF;;AAyDF;AAYA;AACE;AACA;AACE;AACE;AACA;AACD;AACF;AACF;AAED;AAUI;AACA;;;;AA4DM;AAIR;;"}
|
|
@@ -5,6 +5,7 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
5
5
|
var React = require('react');
|
|
6
6
|
require('./../../vendor/.pnpm/react-pdf@9.2.1_@types_react@18.3.28_react-dom@19.2.6_react@19.2.6__react@19.2.6/vendor/react-pdf/dist/esm/index.cjs');
|
|
7
7
|
var components_Scroller_Scroller = require('../Scroller/Scroller.cjs');
|
|
8
|
+
var components_FileViewer_buildCustomTextRenderer = require('./buildCustomTextRenderer.cjs');
|
|
8
9
|
var components_FileViewer_generatedReactPDFStyle = require('./generatedReactPDFStyle.cjs');
|
|
9
10
|
var Document = require('./../../vendor/.pnpm/react-pdf@9.2.1_@types_react@18.3.28_react-dom@19.2.6_react@19.2.6__react@19.2.6/vendor/react-pdf/dist/esm/Document.cjs');
|
|
10
11
|
var Page = require('./../../vendor/.pnpm/react-pdf@9.2.1_@types_react@18.3.28_react-dom@19.2.6_react@19.2.6__react@19.2.6/vendor/react-pdf/dist/esm/Page.cjs');
|
|
@@ -42,8 +43,22 @@ const options = {
|
|
|
42
43
|
// cMapUrl: '/cmaps/',
|
|
43
44
|
cMapUrl: `//unpkg.com/pdfjs-dist@${pdf.version}/cmaps/`,
|
|
44
45
|
};
|
|
45
|
-
|
|
46
|
+
// pdfjs が用意している CSS 変数 (--highlight-bg-color / --highlight-selected-bg-color)を .textLayer スコープで上書きし、検索ハイライト色を変更している。
|
|
47
|
+
const HighlightOverrideStyle = () => (jsxRuntime.jsx("style", { children: `
|
|
48
|
+
.textLayer {
|
|
49
|
+
--highlight-bg-color: rgba(255, 235, 0, 0.5);
|
|
50
|
+
--highlight-selected-bg-color: rgba(255, 140, 0, 0.6);
|
|
51
|
+
}
|
|
52
|
+
.textLayer mark.highlight {
|
|
53
|
+
color: transparent;
|
|
54
|
+
}
|
|
55
|
+
` }));
|
|
56
|
+
const PDFViewer = React.memo(({ scale, rotation, file, width, onLoad, onPDFLoaded, onPassword, onLoadError, search }) => {
|
|
57
|
+
const matches = search?.matches;
|
|
58
|
+
const currentMatchIndex = search?.currentMatchIndex;
|
|
59
|
+
const onPageTextLoaded = search?.registerPageText;
|
|
46
60
|
const [pdfNumPages, setPdfNumPages] = React.useState(1);
|
|
61
|
+
const rootRef = React.useRef(null);
|
|
47
62
|
const onDocumentLoadSuccess = React.useCallback(({ numPages }) => {
|
|
48
63
|
setPdfNumPages(numPages);
|
|
49
64
|
}, []);
|
|
@@ -61,7 +76,49 @@ const PDFViewer = React.memo(({ scale, rotation, file, width, onLoad, onPDFLoade
|
|
|
61
76
|
}
|
|
62
77
|
};
|
|
63
78
|
}, [onLoad, onPDFLoaded, pdfNumPages, rotation]);
|
|
64
|
-
|
|
79
|
+
const customTextRenderer = React.useMemo(() => {
|
|
80
|
+
if (!matches || matches.length === 0) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return components_FileViewer_buildCustomTextRenderer.buildCustomTextRenderer(matches);
|
|
84
|
+
}, [matches]);
|
|
85
|
+
const handleGetTextSuccess = React.useCallback((pageIndex) => (textContent) => {
|
|
86
|
+
if (!onPageTextLoaded)
|
|
87
|
+
return;
|
|
88
|
+
const texts = textContent.items.reduce((acc, item) => {
|
|
89
|
+
if ('str' in item) {
|
|
90
|
+
acc.push(item.str);
|
|
91
|
+
}
|
|
92
|
+
return acc;
|
|
93
|
+
}, []);
|
|
94
|
+
onPageTextLoaded(pageIndex, texts);
|
|
95
|
+
}, [onPageTextLoaded]);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
const root = rootRef.current;
|
|
98
|
+
if (!root)
|
|
99
|
+
return;
|
|
100
|
+
root
|
|
101
|
+
.querySelectorAll(`mark.highlight.${components_FileViewer_buildCustomTextRenderer.SELECTED_MATCH_CLASS}`)
|
|
102
|
+
.forEach((el) => el.classList.remove(components_FileViewer_buildCustomTextRenderer.SELECTED_MATCH_CLASS));
|
|
103
|
+
if (currentMatchIndex === undefined || currentMatchIndex < 0)
|
|
104
|
+
return;
|
|
105
|
+
const start = performance.now();
|
|
106
|
+
let id = 0;
|
|
107
|
+
const apply = () => {
|
|
108
|
+
const els = root.querySelectorAll(components_FileViewer_buildCustomTextRenderer.matchSelector(currentMatchIndex));
|
|
109
|
+
if (els.length > 0) {
|
|
110
|
+
els.forEach((el) => el.classList.add(components_FileViewer_buildCustomTextRenderer.SELECTED_MATCH_CLASS));
|
|
111
|
+
els[0].scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (performance.now() - start < 1000) {
|
|
115
|
+
id = requestAnimationFrame(apply);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
id = requestAnimationFrame(apply);
|
|
119
|
+
return () => cancelAnimationFrame(id);
|
|
120
|
+
}, [currentMatchIndex, matches]);
|
|
121
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(components_FileViewer_generatedReactPDFStyle.ReactPDFStyle, {}), jsxRuntime.jsx(HighlightOverrideStyle, {}), jsxRuntime.jsx(components_Scroller_Scroller.Scroller, { ref: rootRef, direction: "both", className: "shr-h-full", children: jsxRuntime.jsx(Document, { options: options, file: file.url, onLoadSuccess: onDocumentLoadSuccess, onLoadError: onLoadError, rotate: rotation, className: "shr-flex shr-w-fit shr-flex-col shr-items-center shr-gap-1", externalLinkTarget: "_blank", loading: null, onPassword: onPassword, children: Array.from({ length: pdfNumPages }).map((_, i) => (jsxRuntime.jsx(Page, { pageNumber: i + 1, width: width, scale: scale, className: "shr-w-full", onLoadSuccess: onPageLoad, onGetTextSuccess: handleGetTextSuccess(i), customTextRenderer: customTextRenderer, loading: null }, `page_${i}`))) }) })] }));
|
|
65
122
|
});
|
|
66
123
|
|
|
67
124
|
exports.PDFViewer = PDFViewer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PDFViewer.cjs","sources":["../../../src/components/FileViewer/PDFViewer.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"file":"PDFViewer.cjs","sources":["../../../src/components/FileViewer/PDFViewer.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { Document, Page, pdfjs } from 'react-pdf'\n\nimport { Scroller } from '../Scroller'\n\nimport {\n SELECTED_MATCH_CLASS,\n buildCustomTextRenderer,\n matchSelector,\n} from './buildCustomTextRenderer'\nimport { ReactPDFStyle } from './generatedReactPDFStyle'\n\nimport type { ViewerProps } from './types'\nimport type { UsePDFSearch } from './usePDFSearch'\n\nif (typeof window !== 'undefined') {\n // iOS 17.3以下ではPromise.withResolversが未定義のため、polyfillを適用する\n // @ts-expect-error\n if (typeof window.Promise.withResolvers === 'undefined') {\n // @ts-expect-error\n window.Promise.withResolvers = function () {\n let resolve, reject\n const promise = new Promise((res, rej) => {\n resolve = res\n reject = rej\n })\n return { promise, resolve, reject }\n }\n // web workerもpolyfillされたものを読み込む\n pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`\n } else {\n // TODO: バンドラの関係でCDNから読み込んでいるが、smarthr-uiから配信するようにしたい\n // pdfjs.GlobalWorkerOptions.workerSrc = new URL(\n // 'pdfjs-dist/build/pdf.worker.min.mjs',\n // import.meta.url,\n // ).toString()\n pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`\n }\n}\n\nconst options = {\n // TODO: バンドラの関係でCDNから読み込んでいるが、smarthr-uiから配信するようにしたい\n // 非latin文字を読み込むためのオプション\n // 参考: https://github.com/wojtekmaj/react-pdf?tab=readme-ov-file#support-for-non-latin-characters\n // cMapUrl: '/cmaps/',\n cMapUrl: `//unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`,\n} satisfies ComponentProps<typeof Document>['options']\n\ntype CustomTextRenderer = NonNullable<ComponentProps<typeof Page>['customTextRenderer']>\ntype TextContent = Parameters<NonNullable<ComponentProps<typeof Page>['onGetTextSuccess']>>[number]\n\n// pdfjs が用意している CSS 変数 (--highlight-bg-color / --highlight-selected-bg-color)を .textLayer スコープで上書きし、検索ハイライト色を変更している。\nconst HighlightOverrideStyle = () => (\n <style>{`\n.textLayer {\n --highlight-bg-color: rgba(255, 235, 0, 0.5);\n --highlight-selected-bg-color: rgba(255, 140, 0, 0.6);\n}\n.textLayer mark.highlight {\n color: transparent;\n}\n`}</style>\n)\n\ntype Props = ViewerProps & {\n search?: UsePDFSearch\n}\n\nexport const PDFViewer: FC<Props> = memo(\n ({ scale, rotation, file, width, onLoad, onPDFLoaded, onPassword, onLoadError, search }) => {\n const matches = search?.matches\n const currentMatchIndex = search?.currentMatchIndex\n const onPageTextLoaded = search?.registerPageText\n const [pdfNumPages, setPdfNumPages] = useState(1)\n const rootRef = useRef<HTMLDivElement>(null)\n\n const onDocumentLoadSuccess = useCallback<\n NonNullable<ComponentProps<typeof Document>['onLoadSuccess']>\n >(({ numPages }) => {\n setPdfNumPages(numPages)\n }, [])\n\n const onPageLoad: ComponentProps<typeof Page>['onLoadSuccess'] = useMemo(() => {\n if (!onLoad && !onPDFLoaded) {\n return undefined\n }\n\n return (page) => {\n if (onPDFLoaded && rotation === undefined) {\n onPDFLoaded(page.rotate)\n }\n // DocumentのLoadだとページごとの読み込みが考慮されないため\n if (onLoad && page.pageNumber === pdfNumPages) {\n onLoad()\n }\n }\n }, [onLoad, onPDFLoaded, pdfNumPages, rotation])\n\n const customTextRenderer = useMemo<CustomTextRenderer | undefined>(() => {\n if (!matches || matches.length === 0) {\n return undefined\n }\n return buildCustomTextRenderer(matches)\n }, [matches])\n\n const handleGetTextSuccess = useCallback(\n (pageIndex: number) => (textContent: TextContent) => {\n if (!onPageTextLoaded) return\n const texts = textContent.items.reduce<string[]>((acc, item) => {\n if ('str' in item) {\n acc.push(item.str)\n }\n return acc\n }, [])\n onPageTextLoaded(pageIndex, texts)\n },\n [onPageTextLoaded],\n )\n\n useEffect(() => {\n const root = rootRef.current\n if (!root) return\n root\n .querySelectorAll(`mark.highlight.${SELECTED_MATCH_CLASS}`)\n .forEach((el) => el.classList.remove(SELECTED_MATCH_CLASS))\n\n if (currentMatchIndex === undefined || currentMatchIndex < 0) return\n\n const start = performance.now()\n let id = 0\n const apply = () => {\n const els = root.querySelectorAll(matchSelector(currentMatchIndex))\n if (els.length > 0) {\n els.forEach((el) => el.classList.add(SELECTED_MATCH_CLASS))\n els[0].scrollIntoView({ block: 'center', behavior: 'smooth' })\n return\n }\n if (performance.now() - start < 1000) {\n id = requestAnimationFrame(apply)\n }\n }\n id = requestAnimationFrame(apply)\n return () => cancelAnimationFrame(id)\n }, [currentMatchIndex, matches])\n\n return (\n <>\n {/* TODO: 外部CSSをsmarthr-uiから読み込んでもらえるようにする機構ができたら消す */}\n <ReactPDFStyle />\n <HighlightOverrideStyle />\n <Scroller ref={rootRef} direction=\"both\" className=\"shr-h-full\">\n <Document\n options={options}\n file={file.url}\n onLoadSuccess={onDocumentLoadSuccess}\n onLoadError={onLoadError}\n rotate={rotation}\n className=\"shr-flex shr-w-fit shr-flex-col shr-items-center shr-gap-1\"\n externalLinkTarget=\"_blank\"\n loading={null}\n onPassword={onPassword}\n >\n {Array.from({ length: pdfNumPages }).map((_, i) => (\n <Page\n key={`page_${i}`}\n pageNumber={i + 1}\n width={width}\n scale={scale}\n className=\"shr-w-full\"\n onLoadSuccess={onPageLoad}\n onGetTextSuccess={handleGetTextSuccess(i)}\n customTextRenderer={customTextRenderer}\n loading={null}\n />\n ))}\n </Document>\n </Scroller>\n </>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;;;AA0BA;;;;;AAKI;;;;;AAKE;AACA;AACF;;;;;;;;;;;;AAWJ;AAEA;;;;;AAKE;;AAMF;AACA;;;;;;;;AASC;AAOM;AAEH;AACA;AACA;;AAEA;;;;AAQA;AACE;AACE;;;AAIA;AACE;;;;AAIA;;AAEJ;;AAGF;;AAEI;;AAEF;AACF;AAEA;AAEI;;AACA;AACE;AACE;;AAEF;;AAEF;AACF;;AAKA;AACA;;;AAEG;AACA;AAEH;;AAEA;;;;AAIE;AACE;AACA;;;;AAIA;;AAEJ;AACA;AACA;AACF;AAEA;AAkCF;;"}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type FC } from 'react';
|
|
2
2
|
import type { ViewerProps } from './types';
|
|
3
|
-
|
|
3
|
+
import type { UsePDFSearch } from './usePDFSearch';
|
|
4
|
+
type Props = ViewerProps & {
|
|
5
|
+
search?: UsePDFSearch;
|
|
6
|
+
};
|
|
7
|
+
export declare const PDFViewer: FC<Props>;
|
|
8
|
+
export {};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { memo, useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { memo, useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
|
4
4
|
import './../../vendor/.pnpm/react-pdf@9.2.1_@types_react@18.3.28_react-dom@19.2.6_react@19.2.6__react@19.2.6/vendor/react-pdf/dist/esm/index.js';
|
|
5
5
|
import { Scroller } from '../Scroller/Scroller.js';
|
|
6
|
+
import { buildCustomTextRenderer, SELECTED_MATCH_CLASS, matchSelector } from './buildCustomTextRenderer.js';
|
|
6
7
|
import { ReactPDFStyle } from './generatedReactPDFStyle.js';
|
|
7
8
|
import Document from './../../vendor/.pnpm/react-pdf@9.2.1_@types_react@18.3.28_react-dom@19.2.6_react@19.2.6__react@19.2.6/vendor/react-pdf/dist/esm/Document.js';
|
|
8
9
|
import Page from './../../vendor/.pnpm/react-pdf@9.2.1_@types_react@18.3.28_react-dom@19.2.6_react@19.2.6__react@19.2.6/vendor/react-pdf/dist/esm/Page.js';
|
|
@@ -40,8 +41,22 @@ const options = {
|
|
|
40
41
|
// cMapUrl: '/cmaps/',
|
|
41
42
|
cMapUrl: `//unpkg.com/pdfjs-dist@${__webpack_exports__version}/cmaps/`,
|
|
42
43
|
};
|
|
43
|
-
|
|
44
|
+
// pdfjs が用意している CSS 変数 (--highlight-bg-color / --highlight-selected-bg-color)を .textLayer スコープで上書きし、検索ハイライト色を変更している。
|
|
45
|
+
const HighlightOverrideStyle = () => (jsx("style", { children: `
|
|
46
|
+
.textLayer {
|
|
47
|
+
--highlight-bg-color: rgba(255, 235, 0, 0.5);
|
|
48
|
+
--highlight-selected-bg-color: rgba(255, 140, 0, 0.6);
|
|
49
|
+
}
|
|
50
|
+
.textLayer mark.highlight {
|
|
51
|
+
color: transparent;
|
|
52
|
+
}
|
|
53
|
+
` }));
|
|
54
|
+
const PDFViewer = memo(({ scale, rotation, file, width, onLoad, onPDFLoaded, onPassword, onLoadError, search }) => {
|
|
55
|
+
const matches = search?.matches;
|
|
56
|
+
const currentMatchIndex = search?.currentMatchIndex;
|
|
57
|
+
const onPageTextLoaded = search?.registerPageText;
|
|
44
58
|
const [pdfNumPages, setPdfNumPages] = useState(1);
|
|
59
|
+
const rootRef = useRef(null);
|
|
45
60
|
const onDocumentLoadSuccess = useCallback(({ numPages }) => {
|
|
46
61
|
setPdfNumPages(numPages);
|
|
47
62
|
}, []);
|
|
@@ -59,7 +74,49 @@ const PDFViewer = memo(({ scale, rotation, file, width, onLoad, onPDFLoaded, onP
|
|
|
59
74
|
}
|
|
60
75
|
};
|
|
61
76
|
}, [onLoad, onPDFLoaded, pdfNumPages, rotation]);
|
|
62
|
-
|
|
77
|
+
const customTextRenderer = useMemo(() => {
|
|
78
|
+
if (!matches || matches.length === 0) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
return buildCustomTextRenderer(matches);
|
|
82
|
+
}, [matches]);
|
|
83
|
+
const handleGetTextSuccess = useCallback((pageIndex) => (textContent) => {
|
|
84
|
+
if (!onPageTextLoaded)
|
|
85
|
+
return;
|
|
86
|
+
const texts = textContent.items.reduce((acc, item) => {
|
|
87
|
+
if ('str' in item) {
|
|
88
|
+
acc.push(item.str);
|
|
89
|
+
}
|
|
90
|
+
return acc;
|
|
91
|
+
}, []);
|
|
92
|
+
onPageTextLoaded(pageIndex, texts);
|
|
93
|
+
}, [onPageTextLoaded]);
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const root = rootRef.current;
|
|
96
|
+
if (!root)
|
|
97
|
+
return;
|
|
98
|
+
root
|
|
99
|
+
.querySelectorAll(`mark.highlight.${SELECTED_MATCH_CLASS}`)
|
|
100
|
+
.forEach((el) => el.classList.remove(SELECTED_MATCH_CLASS));
|
|
101
|
+
if (currentMatchIndex === undefined || currentMatchIndex < 0)
|
|
102
|
+
return;
|
|
103
|
+
const start = performance.now();
|
|
104
|
+
let id = 0;
|
|
105
|
+
const apply = () => {
|
|
106
|
+
const els = root.querySelectorAll(matchSelector(currentMatchIndex));
|
|
107
|
+
if (els.length > 0) {
|
|
108
|
+
els.forEach((el) => el.classList.add(SELECTED_MATCH_CLASS));
|
|
109
|
+
els[0].scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (performance.now() - start < 1000) {
|
|
113
|
+
id = requestAnimationFrame(apply);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
id = requestAnimationFrame(apply);
|
|
117
|
+
return () => cancelAnimationFrame(id);
|
|
118
|
+
}, [currentMatchIndex, matches]);
|
|
119
|
+
return (jsxs(Fragment, { children: [jsx(ReactPDFStyle, {}), jsx(HighlightOverrideStyle, {}), jsx(Scroller, { ref: rootRef, direction: "both", className: "shr-h-full", children: jsx(Document, { options: options, file: file.url, onLoadSuccess: onDocumentLoadSuccess, onLoadError: onLoadError, rotate: rotation, className: "shr-flex shr-w-fit shr-flex-col shr-items-center shr-gap-1", externalLinkTarget: "_blank", loading: null, onPassword: onPassword, children: Array.from({ length: pdfNumPages }).map((_, i) => (jsx(Page, { pageNumber: i + 1, width: width, scale: scale, className: "shr-w-full", onLoadSuccess: onPageLoad, onGetTextSuccess: handleGetTextSuccess(i), customTextRenderer: customTextRenderer, loading: null }, `page_${i}`))) }) })] }));
|
|
63
120
|
});
|
|
64
121
|
|
|
65
122
|
export { PDFViewer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PDFViewer.js","sources":["../../../src/components/FileViewer/PDFViewer.tsx"],"sourcesContent":["'use client'\n\nimport {
|
|
1
|
+
{"version":3,"file":"PDFViewer.js","sources":["../../../src/components/FileViewer/PDFViewer.tsx"],"sourcesContent":["'use client'\n\nimport {\n type ComponentProps,\n type FC,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react'\nimport { Document, Page, pdfjs } from 'react-pdf'\n\nimport { Scroller } from '../Scroller'\n\nimport {\n SELECTED_MATCH_CLASS,\n buildCustomTextRenderer,\n matchSelector,\n} from './buildCustomTextRenderer'\nimport { ReactPDFStyle } from './generatedReactPDFStyle'\n\nimport type { ViewerProps } from './types'\nimport type { UsePDFSearch } from './usePDFSearch'\n\nif (typeof window !== 'undefined') {\n // iOS 17.3以下ではPromise.withResolversが未定義のため、polyfillを適用する\n // @ts-expect-error\n if (typeof window.Promise.withResolvers === 'undefined') {\n // @ts-expect-error\n window.Promise.withResolvers = function () {\n let resolve, reject\n const promise = new Promise((res, rej) => {\n resolve = res\n reject = rej\n })\n return { promise, resolve, reject }\n }\n // web workerもpolyfillされたものを読み込む\n pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`\n } else {\n // TODO: バンドラの関係でCDNから読み込んでいるが、smarthr-uiから配信するようにしたい\n // pdfjs.GlobalWorkerOptions.workerSrc = new URL(\n // 'pdfjs-dist/build/pdf.worker.min.mjs',\n // import.meta.url,\n // ).toString()\n pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`\n }\n}\n\nconst options = {\n // TODO: バンドラの関係でCDNから読み込んでいるが、smarthr-uiから配信するようにしたい\n // 非latin文字を読み込むためのオプション\n // 参考: https://github.com/wojtekmaj/react-pdf?tab=readme-ov-file#support-for-non-latin-characters\n // cMapUrl: '/cmaps/',\n cMapUrl: `//unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`,\n} satisfies ComponentProps<typeof Document>['options']\n\ntype CustomTextRenderer = NonNullable<ComponentProps<typeof Page>['customTextRenderer']>\ntype TextContent = Parameters<NonNullable<ComponentProps<typeof Page>['onGetTextSuccess']>>[number]\n\n// pdfjs が用意している CSS 変数 (--highlight-bg-color / --highlight-selected-bg-color)を .textLayer スコープで上書きし、検索ハイライト色を変更している。\nconst HighlightOverrideStyle = () => (\n <style>{`\n.textLayer {\n --highlight-bg-color: rgba(255, 235, 0, 0.5);\n --highlight-selected-bg-color: rgba(255, 140, 0, 0.6);\n}\n.textLayer mark.highlight {\n color: transparent;\n}\n`}</style>\n)\n\ntype Props = ViewerProps & {\n search?: UsePDFSearch\n}\n\nexport const PDFViewer: FC<Props> = memo(\n ({ scale, rotation, file, width, onLoad, onPDFLoaded, onPassword, onLoadError, search }) => {\n const matches = search?.matches\n const currentMatchIndex = search?.currentMatchIndex\n const onPageTextLoaded = search?.registerPageText\n const [pdfNumPages, setPdfNumPages] = useState(1)\n const rootRef = useRef<HTMLDivElement>(null)\n\n const onDocumentLoadSuccess = useCallback<\n NonNullable<ComponentProps<typeof Document>['onLoadSuccess']>\n >(({ numPages }) => {\n setPdfNumPages(numPages)\n }, [])\n\n const onPageLoad: ComponentProps<typeof Page>['onLoadSuccess'] = useMemo(() => {\n if (!onLoad && !onPDFLoaded) {\n return undefined\n }\n\n return (page) => {\n if (onPDFLoaded && rotation === undefined) {\n onPDFLoaded(page.rotate)\n }\n // DocumentのLoadだとページごとの読み込みが考慮されないため\n if (onLoad && page.pageNumber === pdfNumPages) {\n onLoad()\n }\n }\n }, [onLoad, onPDFLoaded, pdfNumPages, rotation])\n\n const customTextRenderer = useMemo<CustomTextRenderer | undefined>(() => {\n if (!matches || matches.length === 0) {\n return undefined\n }\n return buildCustomTextRenderer(matches)\n }, [matches])\n\n const handleGetTextSuccess = useCallback(\n (pageIndex: number) => (textContent: TextContent) => {\n if (!onPageTextLoaded) return\n const texts = textContent.items.reduce<string[]>((acc, item) => {\n if ('str' in item) {\n acc.push(item.str)\n }\n return acc\n }, [])\n onPageTextLoaded(pageIndex, texts)\n },\n [onPageTextLoaded],\n )\n\n useEffect(() => {\n const root = rootRef.current\n if (!root) return\n root\n .querySelectorAll(`mark.highlight.${SELECTED_MATCH_CLASS}`)\n .forEach((el) => el.classList.remove(SELECTED_MATCH_CLASS))\n\n if (currentMatchIndex === undefined || currentMatchIndex < 0) return\n\n const start = performance.now()\n let id = 0\n const apply = () => {\n const els = root.querySelectorAll(matchSelector(currentMatchIndex))\n if (els.length > 0) {\n els.forEach((el) => el.classList.add(SELECTED_MATCH_CLASS))\n els[0].scrollIntoView({ block: 'center', behavior: 'smooth' })\n return\n }\n if (performance.now() - start < 1000) {\n id = requestAnimationFrame(apply)\n }\n }\n id = requestAnimationFrame(apply)\n return () => cancelAnimationFrame(id)\n }, [currentMatchIndex, matches])\n\n return (\n <>\n {/* TODO: 外部CSSをsmarthr-uiから読み込んでもらえるようにする機構ができたら消す */}\n <ReactPDFStyle />\n <HighlightOverrideStyle />\n <Scroller ref={rootRef} direction=\"both\" className=\"shr-h-full\">\n <Document\n options={options}\n file={file.url}\n onLoadSuccess={onDocumentLoadSuccess}\n onLoadError={onLoadError}\n rotate={rotation}\n className=\"shr-flex shr-w-fit shr-flex-col shr-items-center shr-gap-1\"\n externalLinkTarget=\"_blank\"\n loading={null}\n onPassword={onPassword}\n >\n {Array.from({ length: pdfNumPages }).map((_, i) => (\n <Page\n key={`page_${i}`}\n pageNumber={i + 1}\n width={width}\n scale={scale}\n className=\"shr-w-full\"\n onLoadSuccess={onPageLoad}\n onGetTextSuccess={handleGetTextSuccess(i)}\n customTextRenderer={customTextRenderer}\n loading={null}\n />\n ))}\n </Document>\n </Scroller>\n </>\n )\n },\n)\n"],"names":[],"mappings":";;;;;;;;;;;AA0BA;;;;;AAKI;;;;;AAKE;AACA;AACF;;;;;;;;;;;;AAWJ;AAEA;;;;;AAKE;;AAMF;AACA;;;;;;;;AASC;AAOM;AAEH;AACA;AACA;;AAEA;;;;AAQA;AACE;AACE;;;AAIA;AACE;;;;AAIA;;AAEJ;;AAGF;;AAEI;;AAEF;AACF;AAEA;AAEI;;AACA;AACE;AACE;;AAEF;;AAEF;AACF;;AAKA;AACA;;;AAEG;AACA;AAEH;;AAEA;;;;AAIE;AACE;AACA;;;;AAIA;;AAEJ;AACA;AACA;AACF;AAEA;AAkCF;;"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var index = require('./../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.cjs');
|
|
7
|
+
var hooks_useEnvironment_useEnvironment = require('../../hooks/useEnvironment/useEnvironment.cjs');
|
|
8
|
+
require('../../themes/createSpacing.cjs');
|
|
9
|
+
require('../../_virtual/index.cjs');
|
|
10
|
+
require('../../_virtual/index2.cjs');
|
|
11
|
+
require('../../themes/createColor.cjs');
|
|
12
|
+
require('../../themes/createTextColor.cjs');
|
|
13
|
+
require('styled-components');
|
|
14
|
+
require('../../themes/createShadow/defaultShadow.cjs');
|
|
15
|
+
require('../../themes/createWidth.cjs');
|
|
16
|
+
require('../../themes/createBackgroundColor.cjs');
|
|
17
|
+
require('../../themes/createBorder.cjs');
|
|
18
|
+
require('../../hooks/useTheme.cjs');
|
|
19
|
+
require('../../intl/IntlProvider.cjs');
|
|
20
|
+
var intl_useIntl = require('../../intl/useIntl.cjs');
|
|
21
|
+
require('../../intl/Localizer.cjs');
|
|
22
|
+
require('../../_virtual/dayjs.min.cjs');
|
|
23
|
+
require('../../intl/TimeFormatter.cjs');
|
|
24
|
+
var components_Button_Button = require('../Button/Button.cjs');
|
|
25
|
+
require('../Button/AnchorButton.cjs');
|
|
26
|
+
require('../Button/UnstyledButton.cjs');
|
|
27
|
+
var components_Icon_FaIcon = require('../Icon/FaIcon.cjs');
|
|
28
|
+
require('../Icon/generateIcon.cjs');
|
|
29
|
+
require('../Icon/OpenInNewTabIcon.cjs');
|
|
30
|
+
require('../Input/Input.cjs');
|
|
31
|
+
require('../Input/CurrencyInput/CurrencyInput.cjs');
|
|
32
|
+
var components_Input_SearchInput_SearchInput = require('../Input/SearchInput/SearchInput.cjs');
|
|
33
|
+
var components_Text_Text = require('../Text/Text.cjs');
|
|
34
|
+
|
|
35
|
+
const classNameGenerator = index.tv({
|
|
36
|
+
slots: {
|
|
37
|
+
wrapper: 'shr-flex shr-w-full',
|
|
38
|
+
inputArea: 'shr-min-w-0 shr-grow',
|
|
39
|
+
},
|
|
40
|
+
variants: {
|
|
41
|
+
mobile: {
|
|
42
|
+
true: {},
|
|
43
|
+
false: {
|
|
44
|
+
wrapper: 'shr-justify-end',
|
|
45
|
+
inputArea: 'shr-max-w-[15em]',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
const SearchController = React.memo(({ search }) => {
|
|
51
|
+
const { query, setQuery, matchCount, currentMatchIndex, goNext: onClickNext, goPrev: onClickPrev, clear: onClickClear, } = search;
|
|
52
|
+
const { localize } = intl_useIntl.useIntl();
|
|
53
|
+
const { mobile } = hooks_useEnvironment_useEnvironment.useEnvironment();
|
|
54
|
+
const classNames = React.useMemo(() => {
|
|
55
|
+
const { wrapper, inputArea } = classNameGenerator({ mobile });
|
|
56
|
+
return { wrapper: wrapper(), inputArea: inputArea() };
|
|
57
|
+
}, [mobile]);
|
|
58
|
+
const translated = React.useMemo(() => ({
|
|
59
|
+
searchInputTooltipMessage: localize({
|
|
60
|
+
id: 'smarthr-ui/FileViewer/searchInputTooltipMessage',
|
|
61
|
+
defaultText: 'PDF内のテキストを検索',
|
|
62
|
+
}),
|
|
63
|
+
previousMatchAlt: localize({
|
|
64
|
+
id: 'smarthr-ui/FileViewer/previousMatchAlt',
|
|
65
|
+
defaultText: '前の検索結果へ',
|
|
66
|
+
}),
|
|
67
|
+
nextMatchAlt: localize({
|
|
68
|
+
id: 'smarthr-ui/FileViewer/nextMatchAlt',
|
|
69
|
+
defaultText: '次の検索結果へ',
|
|
70
|
+
}),
|
|
71
|
+
}), [localize]);
|
|
72
|
+
const hasMatches = matchCount > 0;
|
|
73
|
+
const displayedCurrent = hasMatches ? currentMatchIndex + 1 : 0;
|
|
74
|
+
const handleChange = React.useCallback((e) => {
|
|
75
|
+
setQuery(e.target.value);
|
|
76
|
+
}, [setQuery]);
|
|
77
|
+
const handleKeyDown = React.useCallback((e) => {
|
|
78
|
+
if (e.nativeEvent.isComposing) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
switch (e.key) {
|
|
82
|
+
case 'Enter': {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
if (e.shiftKey) {
|
|
85
|
+
onClickPrev();
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
onClickNext();
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'Escape': {
|
|
93
|
+
if (query !== '') {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
onClickClear();
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}, [onClickNext, onClickPrev, onClickClear, query]);
|
|
101
|
+
return (jsxRuntime.jsxs("div", { className: classNames.wrapper, children: [jsxRuntime.jsx("div", { className: classNames.inputArea, children: jsxRuntime.jsx(components_Input_SearchInput_SearchInput.SearchInput, { name: "file_viewer_search", tooltipMessage: translated.searchInputTooltipMessage, value: query, onChange: handleChange, onKeyDown: handleKeyDown, width: "100%", suffix: query !== '' ? (jsxRuntime.jsx(components_Text_Text.Text, { size: "S", "aria-live": "polite", className: "shr-tabular-nums", children: `${displayedCurrent}/${matchCount}` })) : undefined, className: "[&_.smarthr-ui-Input]:shr-rounded-e-none" }) }), jsxRuntime.jsx(components_Button_Button.Button, { onClick: onClickPrev, disabled: !hasMatches, className: "shr-rounded-none shr-border-s-0 shr-p-0.75 aria-disabled:!shr-border-default", children: jsxRuntime.jsx(components_Icon_FaIcon.FaAngleUpIcon, { alt: translated.previousMatchAlt }) }), jsxRuntime.jsx(components_Button_Button.Button, { onClick: onClickNext, disabled: !hasMatches, className: "shr-rounded-s-none shr-border-s-0 shr-p-0.75 aria-disabled:!shr-border-default", children: jsxRuntime.jsx(components_Icon_FaIcon.FaAngleDownIcon, { alt: translated.nextMatchAlt }) })] }));
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
exports.SearchController = SearchController;
|
|
105
|
+
//# sourceMappingURL=SearchController.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchController.cjs","sources":["../../../src/components/FileViewer/SearchController.tsx"],"sourcesContent":["'use client'\n\nimport { type ChangeEvent, type FC, type KeyboardEvent, memo, useCallback, useMemo } from 'react'\nimport { tv } from 'tailwind-variants'\n\nimport { useEnvironment } from '../../hooks/useEnvironment'\nimport { useIntl } from '../../intl'\nimport { Button } from '../Button'\nimport { FaAngleDownIcon, FaAngleUpIcon } from '../Icon'\nimport { SearchInput } from '../Input'\nimport { Text } from '../Text'\n\nimport type { UsePDFSearch } from './usePDFSearch'\n\ntype Props = {\n search: UsePDFSearch\n}\n\nconst classNameGenerator = tv({\n slots: {\n wrapper: 'shr-flex shr-w-full',\n inputArea: 'shr-min-w-0 shr-grow',\n },\n variants: {\n mobile: {\n true: {},\n false: {\n wrapper: 'shr-justify-end',\n inputArea: 'shr-max-w-[15em]',\n },\n },\n },\n})\n\nexport const SearchController: FC<Props> = memo(({ search }) => {\n const {\n query,\n setQuery,\n matchCount,\n currentMatchIndex,\n goNext: onClickNext,\n goPrev: onClickPrev,\n clear: onClickClear,\n } = search\n const { localize } = useIntl()\n const { mobile } = useEnvironment()\n const classNames = useMemo(() => {\n const { wrapper, inputArea } = classNameGenerator({ mobile })\n return { wrapper: wrapper(), inputArea: inputArea() }\n }, [mobile])\n const translated = useMemo(\n () => ({\n searchInputTooltipMessage: localize({\n id: 'smarthr-ui/FileViewer/searchInputTooltipMessage',\n defaultText: 'PDF内のテキストを検索',\n }),\n previousMatchAlt: localize({\n id: 'smarthr-ui/FileViewer/previousMatchAlt',\n defaultText: '前の検索結果へ',\n }),\n nextMatchAlt: localize({\n id: 'smarthr-ui/FileViewer/nextMatchAlt',\n defaultText: '次の検索結果へ',\n }),\n }),\n [localize],\n )\n\n const hasMatches = matchCount > 0\n const displayedCurrent = hasMatches ? currentMatchIndex + 1 : 0\n\n const handleChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n setQuery(e.target.value)\n },\n [setQuery],\n )\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.nativeEvent.isComposing) {\n return\n }\n switch (e.key) {\n case 'Enter': {\n e.preventDefault()\n if (e.shiftKey) {\n onClickPrev()\n } else {\n onClickNext()\n }\n break\n }\n case 'Escape': {\n if (query !== '') {\n e.preventDefault()\n onClickClear()\n }\n break\n }\n }\n },\n [onClickNext, onClickPrev, onClickClear, query],\n )\n\n return (\n <div className={classNames.wrapper}>\n <div className={classNames.inputArea}>\n <SearchInput\n name=\"file_viewer_search\"\n tooltipMessage={translated.searchInputTooltipMessage}\n value={query}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n width=\"100%\"\n suffix={\n query !== '' ? (\n <Text size=\"S\" aria-live=\"polite\" className=\"shr-tabular-nums\">\n {`${displayedCurrent}/${matchCount}`}\n </Text>\n ) : undefined\n }\n className=\"[&_.smarthr-ui-Input]:shr-rounded-e-none\"\n />\n </div>\n <Button\n onClick={onClickPrev}\n disabled={!hasMatches}\n className=\"shr-rounded-none shr-border-s-0 shr-p-0.75 aria-disabled:!shr-border-default\"\n >\n <FaAngleUpIcon alt={translated.previousMatchAlt} />\n </Button>\n <Button\n onClick={onClickNext}\n disabled={!hasMatches}\n className=\"shr-rounded-s-none shr-border-s-0 shr-p-0.75 aria-disabled:!shr-border-default\"\n >\n <FaAngleDownIcon alt={translated.nextMatchAlt} />\n </Button>\n </div>\n )\n})\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBA;AACE;AACE;AACA;AACD;AACD;AACE;AACE;AACA;AACE;AACA;AACD;AACF;AACF;AACF;AAEM;;AAUL;AACA;AACA;AACE;;AAEF;AACA;;AAGM;AACA;;;AAGA;AACA;;;AAGA;AACA;;AAEH;AAIH;AACA;AAEA;AAEI;AACF;AAIF;AAEI;;;AAGA;;;AAGI;AACE;;;AAEA;;;;;AAKF;;AAEE;;;;;;AASV;AAoCF;;"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { memo, useMemo, useCallback } from 'react';
|
|
4
|
+
import { tv as ce } from './../../vendor/.pnpm/tailwind-variants@0.3.1_tailwindcss@3.4.19_ts-node@10.9.2_@swc_core@1.15.17_@types_node@20.19.35_typescript@5.9.3__/vendor/tailwind-variants/dist/index.js';
|
|
5
|
+
import { useEnvironment } from '../../hooks/useEnvironment/useEnvironment.js';
|
|
6
|
+
import '../../themes/createSpacing.js';
|
|
7
|
+
import '../../_virtual/index.js';
|
|
8
|
+
import '../../_virtual/index2.js';
|
|
9
|
+
import '../../themes/createColor.js';
|
|
10
|
+
import '../../themes/createTextColor.js';
|
|
11
|
+
import 'styled-components';
|
|
12
|
+
import '../../themes/createShadow/defaultShadow.js';
|
|
13
|
+
import '../../themes/createWidth.js';
|
|
14
|
+
import '../../themes/createBackgroundColor.js';
|
|
15
|
+
import '../../themes/createBorder.js';
|
|
16
|
+
import '../../hooks/useTheme.js';
|
|
17
|
+
import '../../intl/IntlProvider.js';
|
|
18
|
+
import { useIntl } from '../../intl/useIntl.js';
|
|
19
|
+
import '../../intl/Localizer.js';
|
|
20
|
+
import '../../_virtual/dayjs.min.js';
|
|
21
|
+
import '../../intl/TimeFormatter.js';
|
|
22
|
+
import { Button } from '../Button/Button.js';
|
|
23
|
+
import '../Button/AnchorButton.js';
|
|
24
|
+
import '../Button/UnstyledButton.js';
|
|
25
|
+
import { FaAngleUpIcon, FaAngleDownIcon } from '../Icon/FaIcon.js';
|
|
26
|
+
import '../Icon/generateIcon.js';
|
|
27
|
+
import '../Icon/OpenInNewTabIcon.js';
|
|
28
|
+
import '../Input/Input.js';
|
|
29
|
+
import '../Input/CurrencyInput/CurrencyInput.js';
|
|
30
|
+
import { SearchInput } from '../Input/SearchInput/SearchInput.js';
|
|
31
|
+
import { Text } from '../Text/Text.js';
|
|
32
|
+
|
|
33
|
+
const classNameGenerator = ce({
|
|
34
|
+
slots: {
|
|
35
|
+
wrapper: 'shr-flex shr-w-full',
|
|
36
|
+
inputArea: 'shr-min-w-0 shr-grow',
|
|
37
|
+
},
|
|
38
|
+
variants: {
|
|
39
|
+
mobile: {
|
|
40
|
+
true: {},
|
|
41
|
+
false: {
|
|
42
|
+
wrapper: 'shr-justify-end',
|
|
43
|
+
inputArea: 'shr-max-w-[15em]',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const SearchController = memo(({ search }) => {
|
|
49
|
+
const { query, setQuery, matchCount, currentMatchIndex, goNext: onClickNext, goPrev: onClickPrev, clear: onClickClear, } = search;
|
|
50
|
+
const { localize } = useIntl();
|
|
51
|
+
const { mobile } = useEnvironment();
|
|
52
|
+
const classNames = useMemo(() => {
|
|
53
|
+
const { wrapper, inputArea } = classNameGenerator({ mobile });
|
|
54
|
+
return { wrapper: wrapper(), inputArea: inputArea() };
|
|
55
|
+
}, [mobile]);
|
|
56
|
+
const translated = useMemo(() => ({
|
|
57
|
+
searchInputTooltipMessage: localize({
|
|
58
|
+
id: 'smarthr-ui/FileViewer/searchInputTooltipMessage',
|
|
59
|
+
defaultText: 'PDF内のテキストを検索',
|
|
60
|
+
}),
|
|
61
|
+
previousMatchAlt: localize({
|
|
62
|
+
id: 'smarthr-ui/FileViewer/previousMatchAlt',
|
|
63
|
+
defaultText: '前の検索結果へ',
|
|
64
|
+
}),
|
|
65
|
+
nextMatchAlt: localize({
|
|
66
|
+
id: 'smarthr-ui/FileViewer/nextMatchAlt',
|
|
67
|
+
defaultText: '次の検索結果へ',
|
|
68
|
+
}),
|
|
69
|
+
}), [localize]);
|
|
70
|
+
const hasMatches = matchCount > 0;
|
|
71
|
+
const displayedCurrent = hasMatches ? currentMatchIndex + 1 : 0;
|
|
72
|
+
const handleChange = useCallback((e) => {
|
|
73
|
+
setQuery(e.target.value);
|
|
74
|
+
}, [setQuery]);
|
|
75
|
+
const handleKeyDown = useCallback((e) => {
|
|
76
|
+
if (e.nativeEvent.isComposing) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
switch (e.key) {
|
|
80
|
+
case 'Enter': {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
if (e.shiftKey) {
|
|
83
|
+
onClickPrev();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
onClickNext();
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'Escape': {
|
|
91
|
+
if (query !== '') {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
onClickClear();
|
|
94
|
+
}
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}, [onClickNext, onClickPrev, onClickClear, query]);
|
|
99
|
+
return (jsxs("div", { className: classNames.wrapper, children: [jsx("div", { className: classNames.inputArea, children: jsx(SearchInput, { name: "file_viewer_search", tooltipMessage: translated.searchInputTooltipMessage, value: query, onChange: handleChange, onKeyDown: handleKeyDown, width: "100%", suffix: query !== '' ? (jsx(Text, { size: "S", "aria-live": "polite", className: "shr-tabular-nums", children: `${displayedCurrent}/${matchCount}` })) : undefined, className: "[&_.smarthr-ui-Input]:shr-rounded-e-none" }) }), jsx(Button, { onClick: onClickPrev, disabled: !hasMatches, className: "shr-rounded-none shr-border-s-0 shr-p-0.75 aria-disabled:!shr-border-default", children: jsx(FaAngleUpIcon, { alt: translated.previousMatchAlt }) }), jsx(Button, { onClick: onClickNext, disabled: !hasMatches, className: "shr-rounded-s-none shr-border-s-0 shr-p-0.75 aria-disabled:!shr-border-default", children: jsx(FaAngleDownIcon, { alt: translated.nextMatchAlt }) })] }));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export { SearchController };
|
|
103
|
+
//# sourceMappingURL=SearchController.js.map
|