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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchController.js","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,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MATCH_INDEX_ATTR = 'data-shr-match-index';
|
|
4
|
+
const SELECTED_MATCH_CLASS = 'selected';
|
|
5
|
+
const matchSelector = (globalIndex) => `mark.highlight[${MATCH_INDEX_ATTR}="${globalIndex}"]`;
|
|
6
|
+
const HTML_ESCAPE_MAP = {
|
|
7
|
+
'&': '&',
|
|
8
|
+
'<': '<',
|
|
9
|
+
'>': '>',
|
|
10
|
+
'"': '"',
|
|
11
|
+
"'": ''',
|
|
12
|
+
};
|
|
13
|
+
const replaceHtmlChar = (c) => HTML_ESCAPE_MAP[c];
|
|
14
|
+
const escapeHtml = (s) => s.replace(/[&<>"']/g, replaceHtmlChar);
|
|
15
|
+
const buildMarkHtml = (text, globalIndex) => `<mark class="highlight" ${MATCH_INDEX_ATTR}="${globalIndex}">${escapeHtml(text)}</mark>`;
|
|
16
|
+
const renderHighlightedFragment = (str, itemMatches) => {
|
|
17
|
+
let result = '';
|
|
18
|
+
let cursor = 0;
|
|
19
|
+
for (const { matchStart, matchLength, globalIndex } of itemMatches) {
|
|
20
|
+
result += escapeHtml(str.slice(cursor, matchStart));
|
|
21
|
+
result += buildMarkHtml(str.slice(matchStart, matchStart + matchLength), globalIndex);
|
|
22
|
+
cursor = matchStart + matchLength;
|
|
23
|
+
}
|
|
24
|
+
result += escapeHtml(str.slice(cursor));
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
const groupMatchesByItem = (matches) => {
|
|
28
|
+
const groups = new Map();
|
|
29
|
+
for (const m of matches) {
|
|
30
|
+
const key = `${m.pageIndex}:${m.itemIndex}`;
|
|
31
|
+
const list = groups.get(key);
|
|
32
|
+
if (list) {
|
|
33
|
+
list.push(m);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
groups.set(key, [m]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return groups;
|
|
40
|
+
};
|
|
41
|
+
const buildCustomTextRenderer = (matches) => {
|
|
42
|
+
const groups = groupMatchesByItem(matches);
|
|
43
|
+
return ({ pageIndex, itemIndex, str }) => {
|
|
44
|
+
const itemMatches = groups.get(`${pageIndex}:${itemIndex}`);
|
|
45
|
+
if (!itemMatches)
|
|
46
|
+
return escapeHtml(str);
|
|
47
|
+
return renderHighlightedFragment(str, itemMatches);
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
exports.MATCH_INDEX_ATTR = MATCH_INDEX_ATTR;
|
|
52
|
+
exports.SELECTED_MATCH_CLASS = SELECTED_MATCH_CLASS;
|
|
53
|
+
exports.buildCustomTextRenderer = buildCustomTextRenderer;
|
|
54
|
+
exports.matchSelector = matchSelector;
|
|
55
|
+
//# sourceMappingURL=buildCustomTextRenderer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildCustomTextRenderer.cjs","sources":["../../../src/components/FileViewer/buildCustomTextRenderer.ts"],"sourcesContent":["import type { PDFSearchMatch } from './types'\nimport type { ComponentProps } from 'react'\nimport type { Page } from 'react-pdf'\n\ntype CustomTextRenderer = NonNullable<ComponentProps<typeof Page>['customTextRenderer']>\n\nexport const MATCH_INDEX_ATTR = 'data-shr-match-index'\n\nexport const SELECTED_MATCH_CLASS = 'selected'\n\nexport const matchSelector = (globalIndex: number): string =>\n `mark.highlight[${MATCH_INDEX_ATTR}=\"${globalIndex}\"]`\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n}\nconst replaceHtmlChar = (c: string): string => HTML_ESCAPE_MAP[c]!\nconst escapeHtml = (s: string): string => s.replace(/[&<>\"']/g, replaceHtmlChar)\n\nconst buildMarkHtml = (text: string, globalIndex: number): string =>\n `<mark class=\"highlight\" ${MATCH_INDEX_ATTR}=\"${globalIndex}\">${escapeHtml(text)}</mark>`\n\nconst renderHighlightedFragment = (str: string, itemMatches: PDFSearchMatch[]): string => {\n let result = ''\n let cursor = 0\n for (const { matchStart, matchLength, globalIndex } of itemMatches) {\n result += escapeHtml(str.slice(cursor, matchStart))\n result += buildMarkHtml(str.slice(matchStart, matchStart + matchLength), globalIndex)\n cursor = matchStart + matchLength\n }\n result += escapeHtml(str.slice(cursor))\n return result\n}\n\nconst groupMatchesByItem = (matches: PDFSearchMatch[]): Map<string, PDFSearchMatch[]> => {\n const groups = new Map<string, PDFSearchMatch[]>()\n for (const m of matches) {\n const key = `${m.pageIndex}:${m.itemIndex}`\n const list = groups.get(key)\n if (list) {\n list.push(m)\n } else {\n groups.set(key, [m])\n }\n }\n return groups\n}\n\nexport const buildCustomTextRenderer = (matches: PDFSearchMatch[]): CustomTextRenderer => {\n const groups = groupMatchesByItem(matches)\n return ({ pageIndex, itemIndex, str }) => {\n const itemMatches = groups.get(`${pageIndex}:${itemIndex}`)\n if (!itemMatches) return escapeHtml(str)\n return renderHighlightedFragment(str, itemMatches)\n }\n}\n"],"names":[],"mappings":";;AAMO,MAAM,gBAAgB,GAAG;AAEzB,MAAM,oBAAoB,GAAG;AAE7B,MAAM,aAAa,GAAG,CAAC,WAAmB,KAC/C,CAAA,eAAA,EAAkB,gBAAgB,CAAA,EAAA,EAAK,WAAW;AAEpD,MAAM,eAAe,GAA2B;AAC9C,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,QAAQ;AACb,IAAA,GAAG,EAAE,OAAO;CACb;AACD,MAAM,eAAe,GAAG,CAAC,CAAS,KAAa,eAAe,CAAC,CAAC,CAAE;AAClE,MAAM,UAAU,GAAG,CAAC,CAAS,KAAa,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,eAAe,CAAC;AAEhF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,WAAmB,KACtD,2BAA2B,gBAAgB,CAAA,EAAA,EAAK,WAAW,CAAA,EAAA,EAAK,UAAU,CAAC,IAAI,CAAC,SAAS;AAE3F,MAAM,yBAAyB,GAAG,CAAC,GAAW,EAAE,WAA6B,KAAY;IACvF,IAAI,MAAM,GAAG,EAAE;IACf,IAAI,MAAM,GAAG,CAAC;IACd,KAAK,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,WAAW,EAAE;AAClE,QAAA,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACnD,QAAA,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,CAAC,EAAE,WAAW,CAAC;AACrF,QAAA,MAAM,GAAG,UAAU,GAAG,WAAW;IACnC;IACA,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACvC,IAAA,OAAO,MAAM;AACf,CAAC;AAED,MAAM,kBAAkB,GAAG,CAAC,OAAyB,KAAmC;AACtF,IAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B;AAClD,IAAA,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE;QACvB,MAAM,GAAG,GAAG,CAAA,EAAG,CAAC,CAAC,SAAS,CAAA,CAAA,EAAI,CAAC,CAAC,SAAS,CAAA,CAAE;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QAC5B,IAAI,IAAI,EAAE;AACR,YAAA,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACd;aAAO;YACL,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACtB;IACF;AACA,IAAA,OAAO,MAAM;AACf,CAAC;AAEM,MAAM,uBAAuB,GAAG,CAAC,OAAyB,KAAwB;AACvF,IAAA,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC;IAC1C,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,KAAI;AACvC,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAC;AAC3D,QAAA,IAAI,CAAC,WAAW;AAAE,YAAA,OAAO,UAAU,CAAC,GAAG,CAAC;AACxC,QAAA,OAAO,yBAAyB,CAAC,GAAG,EAAE,WAAW,CAAC;AACpD,IAAA,CAAC;AACH;;;;;;;"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PDFSearchMatch } from './types';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
import type { Page } from 'react-pdf';
|
|
4
|
+
type CustomTextRenderer = NonNullable<ComponentProps<typeof Page>['customTextRenderer']>;
|
|
5
|
+
export declare const MATCH_INDEX_ATTR = "data-shr-match-index";
|
|
6
|
+
export declare const SELECTED_MATCH_CLASS = "selected";
|
|
7
|
+
export declare const matchSelector: (globalIndex: number) => string;
|
|
8
|
+
export declare const buildCustomTextRenderer: (matches: PDFSearchMatch[]) => CustomTextRenderer;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const MATCH_INDEX_ATTR = 'data-shr-match-index';
|
|
2
|
+
const SELECTED_MATCH_CLASS = 'selected';
|
|
3
|
+
const matchSelector = (globalIndex) => `mark.highlight[${MATCH_INDEX_ATTR}="${globalIndex}"]`;
|
|
4
|
+
const HTML_ESCAPE_MAP = {
|
|
5
|
+
'&': '&',
|
|
6
|
+
'<': '<',
|
|
7
|
+
'>': '>',
|
|
8
|
+
'"': '"',
|
|
9
|
+
"'": ''',
|
|
10
|
+
};
|
|
11
|
+
const replaceHtmlChar = (c) => HTML_ESCAPE_MAP[c];
|
|
12
|
+
const escapeHtml = (s) => s.replace(/[&<>"']/g, replaceHtmlChar);
|
|
13
|
+
const buildMarkHtml = (text, globalIndex) => `<mark class="highlight" ${MATCH_INDEX_ATTR}="${globalIndex}">${escapeHtml(text)}</mark>`;
|
|
14
|
+
const renderHighlightedFragment = (str, itemMatches) => {
|
|
15
|
+
let result = '';
|
|
16
|
+
let cursor = 0;
|
|
17
|
+
for (const { matchStart, matchLength, globalIndex } of itemMatches) {
|
|
18
|
+
result += escapeHtml(str.slice(cursor, matchStart));
|
|
19
|
+
result += buildMarkHtml(str.slice(matchStart, matchStart + matchLength), globalIndex);
|
|
20
|
+
cursor = matchStart + matchLength;
|
|
21
|
+
}
|
|
22
|
+
result += escapeHtml(str.slice(cursor));
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
const groupMatchesByItem = (matches) => {
|
|
26
|
+
const groups = new Map();
|
|
27
|
+
for (const m of matches) {
|
|
28
|
+
const key = `${m.pageIndex}:${m.itemIndex}`;
|
|
29
|
+
const list = groups.get(key);
|
|
30
|
+
if (list) {
|
|
31
|
+
list.push(m);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
groups.set(key, [m]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return groups;
|
|
38
|
+
};
|
|
39
|
+
const buildCustomTextRenderer = (matches) => {
|
|
40
|
+
const groups = groupMatchesByItem(matches);
|
|
41
|
+
return ({ pageIndex, itemIndex, str }) => {
|
|
42
|
+
const itemMatches = groups.get(`${pageIndex}:${itemIndex}`);
|
|
43
|
+
if (!itemMatches)
|
|
44
|
+
return escapeHtml(str);
|
|
45
|
+
return renderHighlightedFragment(str, itemMatches);
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export { MATCH_INDEX_ATTR, SELECTED_MATCH_CLASS, buildCustomTextRenderer, matchSelector };
|
|
50
|
+
//# sourceMappingURL=buildCustomTextRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildCustomTextRenderer.js","sources":["../../../src/components/FileViewer/buildCustomTextRenderer.ts"],"sourcesContent":["import type { PDFSearchMatch } from './types'\nimport type { ComponentProps } from 'react'\nimport type { Page } from 'react-pdf'\n\ntype CustomTextRenderer = NonNullable<ComponentProps<typeof Page>['customTextRenderer']>\n\nexport const MATCH_INDEX_ATTR = 'data-shr-match-index'\n\nexport const SELECTED_MATCH_CLASS = 'selected'\n\nexport const matchSelector = (globalIndex: number): string =>\n `mark.highlight[${MATCH_INDEX_ATTR}=\"${globalIndex}\"]`\n\nconst HTML_ESCAPE_MAP: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n}\nconst replaceHtmlChar = (c: string): string => HTML_ESCAPE_MAP[c]!\nconst escapeHtml = (s: string): string => s.replace(/[&<>\"']/g, replaceHtmlChar)\n\nconst buildMarkHtml = (text: string, globalIndex: number): string =>\n `<mark class=\"highlight\" ${MATCH_INDEX_ATTR}=\"${globalIndex}\">${escapeHtml(text)}</mark>`\n\nconst renderHighlightedFragment = (str: string, itemMatches: PDFSearchMatch[]): string => {\n let result = ''\n let cursor = 0\n for (const { matchStart, matchLength, globalIndex } of itemMatches) {\n result += escapeHtml(str.slice(cursor, matchStart))\n result += buildMarkHtml(str.slice(matchStart, matchStart + matchLength), globalIndex)\n cursor = matchStart + matchLength\n }\n result += escapeHtml(str.slice(cursor))\n return result\n}\n\nconst groupMatchesByItem = (matches: PDFSearchMatch[]): Map<string, PDFSearchMatch[]> => {\n const groups = new Map<string, PDFSearchMatch[]>()\n for (const m of matches) {\n const key = `${m.pageIndex}:${m.itemIndex}`\n const list = groups.get(key)\n if (list) {\n list.push(m)\n } else {\n groups.set(key, [m])\n }\n }\n return groups\n}\n\nexport const buildCustomTextRenderer = (matches: PDFSearchMatch[]): CustomTextRenderer => {\n const groups = groupMatchesByItem(matches)\n return ({ pageIndex, itemIndex, str }) => {\n const itemMatches = groups.get(`${pageIndex}:${itemIndex}`)\n if (!itemMatches) return escapeHtml(str)\n return renderHighlightedFragment(str, itemMatches)\n }\n}\n"],"names":[],"mappings":"AAMO,MAAM,gBAAgB,GAAG;AAEzB,MAAM,oBAAoB,GAAG;AAE7B,MAAM,aAAa,GAAG,CAAC,WAAmB,KAC/C,CAAA,eAAA,EAAkB,gBAAgB,CAAA,EAAA,EAAK,WAAW;AAEpD,MAAM,eAAe,GAA2B;AAC9C,IAAA,GAAG,EAAE,OAAO;AACZ,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,MAAM;AACX,IAAA,GAAG,EAAE,QAAQ;AACb,IAAA,GAAG,EAAE,OAAO;CACb;AACD,MAAM,eAAe,GAAG,CAAC,CAAS,KAAa,eAAe,CAAC,CAAC,CAAE;AAClE,MAAM,UAAU,GAAG,CAAC,CAAS,KAAa,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,eAAe,CAAC;AAEhF,MAAM,aAAa,GAAG,CAAC,IAAY,EAAE,WAAmB,KACtD,2BAA2B,gBAAgB,CAAA,EAAA,EAAK,WAAW,CAAA,EAAA,EAAK,UAAU,CAAC,IAAI,CAAC,SAAS;AAE3F,MAAM,yBAAyB,GAAG,CAAC,GAAW,EAAE,WAA6B,KAAY;IACvF,IAAI,MAAM,GAAG,EAAE;IACf,IAAI,MAAM,GAAG,CAAC;IACd,KAAK,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,WAAW,EAAE;AAClE,QAAA,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACnD,QAAA,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,CAAC,EAAE,WAAW,CAAC;AACrF,QAAA,MAAM,GAAG,UAAU,GAAG,WAAW;IACnC;IACA,MAAM,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACvC,IAAA,OAAO,MAAM;AACf,CAAC;AAED,MAAM,kBAAkB,GAAG,CAAC,OAAyB,KAAmC;AACtF,IAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B;AAClD,IAAA,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE;QACvB,MAAM,GAAG,GAAG,CAAA,EAAG,CAAC,CAAC,SAAS,CAAA,CAAA,EAAI,CAAC,CAAC,SAAS,CAAA,CAAE;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QAC5B,IAAI,IAAI,EAAE;AACR,YAAA,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACd;aAAO;YACL,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACtB;IACF;AACA,IAAA,OAAO,MAAM;AACf,CAAC;AAEM,MAAM,uBAAuB,GAAG,CAAC,OAAyB,KAAwB;AACvF,IAAA,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC;IAC1C,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,KAAI;AACvC,QAAA,MAAM,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,SAAS,CAAA,CAAE,CAAC;AAC3D,QAAA,IAAI,CAAC,WAAW;AAAE,YAAA,OAAO,UAAU,CAAC,GAAG,CAAC;AACxC,QAAA,OAAO,yBAAyB,CAAC,GAAG,EAAE,WAAW,CAAC;AACpD,IAAA,CAAC;AACH;;;;"}
|
|
@@ -5,6 +5,13 @@ export type FileForViewer = {
|
|
|
5
5
|
contentType: string;
|
|
6
6
|
alt?: string;
|
|
7
7
|
};
|
|
8
|
+
export type PDFSearchMatch = {
|
|
9
|
+
pageIndex: number;
|
|
10
|
+
itemIndex: number;
|
|
11
|
+
matchStart: number;
|
|
12
|
+
matchLength: number;
|
|
13
|
+
globalIndex: number;
|
|
14
|
+
};
|
|
8
15
|
export type ViewerProps = {
|
|
9
16
|
file: FileForViewer;
|
|
10
17
|
scale: number;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
6
|
+
const normalize = (s) => s.normalize('NFKC');
|
|
7
|
+
// 縦書きは未対応(pdf.js が縦書きの textItem を読み順で返さず、連結すると検索文字列がずれるため)
|
|
8
|
+
const computeMatchesForPage = (pageIndex, normalizedTextItems, escapedQuery, startGlobalIndex) => {
|
|
9
|
+
const joinedPageText = normalizedTextItems.join('');
|
|
10
|
+
if (joinedPageText.length === 0)
|
|
11
|
+
return { matches: [], nextGlobalIndex: startGlobalIndex };
|
|
12
|
+
const matches = [];
|
|
13
|
+
let globalIndex = startGlobalIndex;
|
|
14
|
+
for (const m of joinedPageText.matchAll(new RegExp(escapedQuery, 'gi'))) {
|
|
15
|
+
const matchStart = m.index;
|
|
16
|
+
const matchEnd = matchStart + m[0].length;
|
|
17
|
+
let itemStart = 0;
|
|
18
|
+
for (let i = 0; i < normalizedTextItems.length; i++) {
|
|
19
|
+
if (itemStart >= matchEnd)
|
|
20
|
+
break;
|
|
21
|
+
const itemEnd = itemStart + normalizedTextItems[i].length;
|
|
22
|
+
const overlapStart = Math.max(matchStart, itemStart);
|
|
23
|
+
const overlapEnd = Math.min(matchEnd, itemEnd);
|
|
24
|
+
if (overlapEnd > overlapStart) {
|
|
25
|
+
matches.push({
|
|
26
|
+
pageIndex,
|
|
27
|
+
itemIndex: i,
|
|
28
|
+
matchStart: overlapStart - itemStart,
|
|
29
|
+
matchLength: overlapEnd - overlapStart,
|
|
30
|
+
globalIndex,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
itemStart = itemEnd;
|
|
34
|
+
}
|
|
35
|
+
globalIndex++;
|
|
36
|
+
}
|
|
37
|
+
return { matches, nextGlobalIndex: globalIndex };
|
|
38
|
+
};
|
|
39
|
+
const usePDFSearch = (fileUrl) => {
|
|
40
|
+
const [query, setQueryState] = React.useState('');
|
|
41
|
+
const [matches, setMatches] = React.useState([]);
|
|
42
|
+
const [currentMatchIndex, setCurrentMatchIndex] = React.useState(-1);
|
|
43
|
+
const pageTextsRef = React.useRef(new Map());
|
|
44
|
+
const queryRef = React.useRef('');
|
|
45
|
+
const matchCount = matches.length === 0 ? 0 : matches[matches.length - 1].globalIndex + 1;
|
|
46
|
+
const resetMatchState = React.useCallback(() => {
|
|
47
|
+
setMatches([]);
|
|
48
|
+
setCurrentMatchIndex(-1);
|
|
49
|
+
}, []);
|
|
50
|
+
const recalculate = React.useCallback((nextQuery, options) => {
|
|
51
|
+
if (nextQuery === '') {
|
|
52
|
+
resetMatchState();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const escapedQuery = escapeRegExp(normalize(nextQuery));
|
|
56
|
+
const pageIndices = Array.from(pageTextsRef.current.keys()).sort((a, b) => a - b);
|
|
57
|
+
const collected = [];
|
|
58
|
+
let globalIndex = 0;
|
|
59
|
+
for (const pageIndex of pageIndices) {
|
|
60
|
+
const texts = pageTextsRef.current.get(pageIndex);
|
|
61
|
+
if (!texts)
|
|
62
|
+
continue;
|
|
63
|
+
const { matches: pageMatches, nextGlobalIndex } = computeMatchesForPage(pageIndex, texts, escapedQuery, globalIndex);
|
|
64
|
+
collected.push(...pageMatches);
|
|
65
|
+
globalIndex = nextGlobalIndex;
|
|
66
|
+
}
|
|
67
|
+
setMatches(collected);
|
|
68
|
+
if (options?.resetSelection) {
|
|
69
|
+
setCurrentMatchIndex(-1);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setCurrentMatchIndex((prev) => {
|
|
73
|
+
if (globalIndex === 0)
|
|
74
|
+
return -1;
|
|
75
|
+
if (prev >= globalIndex)
|
|
76
|
+
return globalIndex - 1;
|
|
77
|
+
return prev;
|
|
78
|
+
});
|
|
79
|
+
}, [resetMatchState]);
|
|
80
|
+
const setQuery = React.useCallback((nextQuery) => {
|
|
81
|
+
queryRef.current = nextQuery;
|
|
82
|
+
setQueryState(nextQuery);
|
|
83
|
+
recalculate(nextQuery, { resetSelection: true });
|
|
84
|
+
}, [recalculate]);
|
|
85
|
+
const registerPageText = React.useCallback((pageIndex, texts) => {
|
|
86
|
+
pageTextsRef.current.set(pageIndex, texts.map(normalize));
|
|
87
|
+
// 全ページ読み込み前に検索が始まっても、後から読んだページがヒットするよう再計算する。
|
|
88
|
+
if (queryRef.current !== '') {
|
|
89
|
+
recalculate(queryRef.current);
|
|
90
|
+
}
|
|
91
|
+
}, [recalculate]);
|
|
92
|
+
const clear = React.useCallback(() => {
|
|
93
|
+
queryRef.current = '';
|
|
94
|
+
setQueryState('');
|
|
95
|
+
resetMatchState();
|
|
96
|
+
}, [resetMatchState]);
|
|
97
|
+
const goNext = React.useCallback(() => {
|
|
98
|
+
setCurrentMatchIndex((prev) => {
|
|
99
|
+
if (matchCount === 0)
|
|
100
|
+
return -1;
|
|
101
|
+
if (prev < 0)
|
|
102
|
+
return 0;
|
|
103
|
+
return (prev + 1) % matchCount;
|
|
104
|
+
});
|
|
105
|
+
}, [matchCount]);
|
|
106
|
+
const goPrev = React.useCallback(() => {
|
|
107
|
+
setCurrentMatchIndex((prev) => {
|
|
108
|
+
if (matchCount === 0)
|
|
109
|
+
return -1;
|
|
110
|
+
if (prev < 0)
|
|
111
|
+
return matchCount - 1;
|
|
112
|
+
return (prev - 1 + matchCount) % matchCount;
|
|
113
|
+
});
|
|
114
|
+
}, [matchCount]);
|
|
115
|
+
React.useEffect(() => {
|
|
116
|
+
pageTextsRef.current.clear();
|
|
117
|
+
queryRef.current = '';
|
|
118
|
+
setQueryState('');
|
|
119
|
+
resetMatchState();
|
|
120
|
+
}, [fileUrl, resetMatchState]);
|
|
121
|
+
return React.useMemo(() => ({
|
|
122
|
+
query,
|
|
123
|
+
setQuery,
|
|
124
|
+
matches,
|
|
125
|
+
matchCount,
|
|
126
|
+
currentMatchIndex,
|
|
127
|
+
goNext,
|
|
128
|
+
goPrev,
|
|
129
|
+
clear,
|
|
130
|
+
registerPageText,
|
|
131
|
+
}), [
|
|
132
|
+
query,
|
|
133
|
+
setQuery,
|
|
134
|
+
matches,
|
|
135
|
+
matchCount,
|
|
136
|
+
currentMatchIndex,
|
|
137
|
+
goNext,
|
|
138
|
+
goPrev,
|
|
139
|
+
clear,
|
|
140
|
+
registerPageText,
|
|
141
|
+
]);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
exports.computeMatchesForPage = computeMatchesForPage;
|
|
145
|
+
exports.escapeRegExp = escapeRegExp;
|
|
146
|
+
exports.normalize = normalize;
|
|
147
|
+
exports.usePDFSearch = usePDFSearch;
|
|
148
|
+
//# sourceMappingURL=usePDFSearch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePDFSearch.cjs","sources":["../../../src/components/FileViewer/usePDFSearch.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { PDFSearchMatch } from './types'\n\nexport const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\nexport const normalize = (s: string) => s.normalize('NFKC')\n\n// 縦書きは未対応(pdf.js が縦書きの textItem を読み順で返さず、連結すると検索文字列がずれるため)\nexport const computeMatchesForPage = (\n pageIndex: number,\n normalizedTextItems: string[],\n escapedQuery: string,\n startGlobalIndex: number,\n): { matches: PDFSearchMatch[]; nextGlobalIndex: number } => {\n const joinedPageText = normalizedTextItems.join('')\n if (joinedPageText.length === 0) return { matches: [], nextGlobalIndex: startGlobalIndex }\n\n const matches: PDFSearchMatch[] = []\n let globalIndex = startGlobalIndex\n\n for (const m of joinedPageText.matchAll(new RegExp(escapedQuery, 'gi'))) {\n const matchStart = m.index\n const matchEnd = matchStart + m[0].length\n\n let itemStart = 0\n for (let i = 0; i < normalizedTextItems.length; i++) {\n if (itemStart >= matchEnd) break\n\n const itemEnd = itemStart + normalizedTextItems[i].length\n\n const overlapStart = Math.max(matchStart, itemStart)\n const overlapEnd = Math.min(matchEnd, itemEnd)\n if (overlapEnd > overlapStart) {\n matches.push({\n pageIndex,\n itemIndex: i,\n matchStart: overlapStart - itemStart,\n matchLength: overlapEnd - overlapStart,\n globalIndex,\n })\n }\n itemStart = itemEnd\n }\n globalIndex++\n }\n return { matches, nextGlobalIndex: globalIndex }\n}\n\nexport const usePDFSearch = (fileUrl: string) => {\n const [query, setQueryState] = useState('')\n const [matches, setMatches] = useState<PDFSearchMatch[]>([])\n const [currentMatchIndex, setCurrentMatchIndex] = useState(-1)\n const pageTextsRef = useRef<Map<number, string[]>>(new Map())\n const queryRef = useRef('')\n\n const matchCount = matches.length === 0 ? 0 : matches[matches.length - 1].globalIndex + 1\n\n const resetMatchState = useCallback(() => {\n setMatches([])\n setCurrentMatchIndex(-1)\n }, [])\n\n const recalculate = useCallback(\n (nextQuery: string, options?: { resetSelection?: boolean }) => {\n if (nextQuery === '') {\n resetMatchState()\n return\n }\n const escapedQuery = escapeRegExp(normalize(nextQuery))\n const pageIndices = Array.from(pageTextsRef.current.keys()).sort((a, b) => a - b)\n const collected: PDFSearchMatch[] = []\n let globalIndex = 0\n for (const pageIndex of pageIndices) {\n const texts = pageTextsRef.current.get(pageIndex)\n if (!texts) continue\n const { matches: pageMatches, nextGlobalIndex } = computeMatchesForPage(\n pageIndex,\n texts,\n escapedQuery,\n globalIndex,\n )\n collected.push(...pageMatches)\n globalIndex = nextGlobalIndex\n }\n setMatches(collected)\n if (options?.resetSelection) {\n setCurrentMatchIndex(-1)\n return\n }\n setCurrentMatchIndex((prev) => {\n if (globalIndex === 0) return -1\n if (prev >= globalIndex) return globalIndex - 1\n return prev\n })\n },\n [resetMatchState],\n )\n\n const setQuery = useCallback(\n (nextQuery: string) => {\n queryRef.current = nextQuery\n setQueryState(nextQuery)\n recalculate(nextQuery, { resetSelection: true })\n },\n [recalculate],\n )\n\n const registerPageText = useCallback(\n (pageIndex: number, texts: string[]) => {\n pageTextsRef.current.set(pageIndex, texts.map(normalize))\n // 全ページ読み込み前に検索が始まっても、後から読んだページがヒットするよう再計算する。\n if (queryRef.current !== '') {\n recalculate(queryRef.current)\n }\n },\n [recalculate],\n )\n\n const clear = useCallback(() => {\n queryRef.current = ''\n setQueryState('')\n resetMatchState()\n }, [resetMatchState])\n\n const goNext = useCallback(() => {\n setCurrentMatchIndex((prev) => {\n if (matchCount === 0) return -1\n if (prev < 0) return 0\n return (prev + 1) % matchCount\n })\n }, [matchCount])\n\n const goPrev = useCallback(() => {\n setCurrentMatchIndex((prev) => {\n if (matchCount === 0) return -1\n if (prev < 0) return matchCount - 1\n return (prev - 1 + matchCount) % matchCount\n })\n }, [matchCount])\n\n useEffect(() => {\n pageTextsRef.current.clear()\n queryRef.current = ''\n setQueryState('')\n resetMatchState()\n }, [fileUrl, resetMatchState])\n\n return useMemo(\n () => ({\n query,\n setQuery,\n matches,\n matchCount,\n currentMatchIndex,\n goNext,\n goPrev,\n clear,\n registerPageText,\n }),\n [\n query,\n setQuery,\n matches,\n matchCount,\n currentMatchIndex,\n goNext,\n goPrev,\n clear,\n registerPageText,\n ],\n )\n}\n\nexport type UsePDFSearch = ReturnType<typeof usePDFSearch>\n"],"names":["useState","useRef","useCallback","useEffect","useMemo"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PDFSearchMatch } from './types';
|
|
2
|
+
export declare const escapeRegExp: (s: string) => string;
|
|
3
|
+
export declare const normalize: (s: string) => string;
|
|
4
|
+
export declare const computeMatchesForPage: (pageIndex: number, normalizedTextItems: string[], escapedQuery: string, startGlobalIndex: number) => {
|
|
5
|
+
matches: PDFSearchMatch[];
|
|
6
|
+
nextGlobalIndex: number;
|
|
7
|
+
};
|
|
8
|
+
export declare const usePDFSearch: (fileUrl: string) => {
|
|
9
|
+
query: string;
|
|
10
|
+
setQuery: (nextQuery: string) => void;
|
|
11
|
+
matches: PDFSearchMatch[];
|
|
12
|
+
matchCount: number;
|
|
13
|
+
currentMatchIndex: number;
|
|
14
|
+
goNext: () => void;
|
|
15
|
+
goPrev: () => void;
|
|
16
|
+
clear: () => void;
|
|
17
|
+
registerPageText: (pageIndex: number, texts: string[]) => void;
|
|
18
|
+
};
|
|
19
|
+
export type UsePDFSearch = ReturnType<typeof usePDFSearch>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
4
|
+
const normalize = (s) => s.normalize('NFKC');
|
|
5
|
+
// 縦書きは未対応(pdf.js が縦書きの textItem を読み順で返さず、連結すると検索文字列がずれるため)
|
|
6
|
+
const computeMatchesForPage = (pageIndex, normalizedTextItems, escapedQuery, startGlobalIndex) => {
|
|
7
|
+
const joinedPageText = normalizedTextItems.join('');
|
|
8
|
+
if (joinedPageText.length === 0)
|
|
9
|
+
return { matches: [], nextGlobalIndex: startGlobalIndex };
|
|
10
|
+
const matches = [];
|
|
11
|
+
let globalIndex = startGlobalIndex;
|
|
12
|
+
for (const m of joinedPageText.matchAll(new RegExp(escapedQuery, 'gi'))) {
|
|
13
|
+
const matchStart = m.index;
|
|
14
|
+
const matchEnd = matchStart + m[0].length;
|
|
15
|
+
let itemStart = 0;
|
|
16
|
+
for (let i = 0; i < normalizedTextItems.length; i++) {
|
|
17
|
+
if (itemStart >= matchEnd)
|
|
18
|
+
break;
|
|
19
|
+
const itemEnd = itemStart + normalizedTextItems[i].length;
|
|
20
|
+
const overlapStart = Math.max(matchStart, itemStart);
|
|
21
|
+
const overlapEnd = Math.min(matchEnd, itemEnd);
|
|
22
|
+
if (overlapEnd > overlapStart) {
|
|
23
|
+
matches.push({
|
|
24
|
+
pageIndex,
|
|
25
|
+
itemIndex: i,
|
|
26
|
+
matchStart: overlapStart - itemStart,
|
|
27
|
+
matchLength: overlapEnd - overlapStart,
|
|
28
|
+
globalIndex,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
itemStart = itemEnd;
|
|
32
|
+
}
|
|
33
|
+
globalIndex++;
|
|
34
|
+
}
|
|
35
|
+
return { matches, nextGlobalIndex: globalIndex };
|
|
36
|
+
};
|
|
37
|
+
const usePDFSearch = (fileUrl) => {
|
|
38
|
+
const [query, setQueryState] = useState('');
|
|
39
|
+
const [matches, setMatches] = useState([]);
|
|
40
|
+
const [currentMatchIndex, setCurrentMatchIndex] = useState(-1);
|
|
41
|
+
const pageTextsRef = useRef(new Map());
|
|
42
|
+
const queryRef = useRef('');
|
|
43
|
+
const matchCount = matches.length === 0 ? 0 : matches[matches.length - 1].globalIndex + 1;
|
|
44
|
+
const resetMatchState = useCallback(() => {
|
|
45
|
+
setMatches([]);
|
|
46
|
+
setCurrentMatchIndex(-1);
|
|
47
|
+
}, []);
|
|
48
|
+
const recalculate = useCallback((nextQuery, options) => {
|
|
49
|
+
if (nextQuery === '') {
|
|
50
|
+
resetMatchState();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const escapedQuery = escapeRegExp(normalize(nextQuery));
|
|
54
|
+
const pageIndices = Array.from(pageTextsRef.current.keys()).sort((a, b) => a - b);
|
|
55
|
+
const collected = [];
|
|
56
|
+
let globalIndex = 0;
|
|
57
|
+
for (const pageIndex of pageIndices) {
|
|
58
|
+
const texts = pageTextsRef.current.get(pageIndex);
|
|
59
|
+
if (!texts)
|
|
60
|
+
continue;
|
|
61
|
+
const { matches: pageMatches, nextGlobalIndex } = computeMatchesForPage(pageIndex, texts, escapedQuery, globalIndex);
|
|
62
|
+
collected.push(...pageMatches);
|
|
63
|
+
globalIndex = nextGlobalIndex;
|
|
64
|
+
}
|
|
65
|
+
setMatches(collected);
|
|
66
|
+
if (options?.resetSelection) {
|
|
67
|
+
setCurrentMatchIndex(-1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setCurrentMatchIndex((prev) => {
|
|
71
|
+
if (globalIndex === 0)
|
|
72
|
+
return -1;
|
|
73
|
+
if (prev >= globalIndex)
|
|
74
|
+
return globalIndex - 1;
|
|
75
|
+
return prev;
|
|
76
|
+
});
|
|
77
|
+
}, [resetMatchState]);
|
|
78
|
+
const setQuery = useCallback((nextQuery) => {
|
|
79
|
+
queryRef.current = nextQuery;
|
|
80
|
+
setQueryState(nextQuery);
|
|
81
|
+
recalculate(nextQuery, { resetSelection: true });
|
|
82
|
+
}, [recalculate]);
|
|
83
|
+
const registerPageText = useCallback((pageIndex, texts) => {
|
|
84
|
+
pageTextsRef.current.set(pageIndex, texts.map(normalize));
|
|
85
|
+
// 全ページ読み込み前に検索が始まっても、後から読んだページがヒットするよう再計算する。
|
|
86
|
+
if (queryRef.current !== '') {
|
|
87
|
+
recalculate(queryRef.current);
|
|
88
|
+
}
|
|
89
|
+
}, [recalculate]);
|
|
90
|
+
const clear = useCallback(() => {
|
|
91
|
+
queryRef.current = '';
|
|
92
|
+
setQueryState('');
|
|
93
|
+
resetMatchState();
|
|
94
|
+
}, [resetMatchState]);
|
|
95
|
+
const goNext = useCallback(() => {
|
|
96
|
+
setCurrentMatchIndex((prev) => {
|
|
97
|
+
if (matchCount === 0)
|
|
98
|
+
return -1;
|
|
99
|
+
if (prev < 0)
|
|
100
|
+
return 0;
|
|
101
|
+
return (prev + 1) % matchCount;
|
|
102
|
+
});
|
|
103
|
+
}, [matchCount]);
|
|
104
|
+
const goPrev = useCallback(() => {
|
|
105
|
+
setCurrentMatchIndex((prev) => {
|
|
106
|
+
if (matchCount === 0)
|
|
107
|
+
return -1;
|
|
108
|
+
if (prev < 0)
|
|
109
|
+
return matchCount - 1;
|
|
110
|
+
return (prev - 1 + matchCount) % matchCount;
|
|
111
|
+
});
|
|
112
|
+
}, [matchCount]);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
pageTextsRef.current.clear();
|
|
115
|
+
queryRef.current = '';
|
|
116
|
+
setQueryState('');
|
|
117
|
+
resetMatchState();
|
|
118
|
+
}, [fileUrl, resetMatchState]);
|
|
119
|
+
return useMemo(() => ({
|
|
120
|
+
query,
|
|
121
|
+
setQuery,
|
|
122
|
+
matches,
|
|
123
|
+
matchCount,
|
|
124
|
+
currentMatchIndex,
|
|
125
|
+
goNext,
|
|
126
|
+
goPrev,
|
|
127
|
+
clear,
|
|
128
|
+
registerPageText,
|
|
129
|
+
}), [
|
|
130
|
+
query,
|
|
131
|
+
setQuery,
|
|
132
|
+
matches,
|
|
133
|
+
matchCount,
|
|
134
|
+
currentMatchIndex,
|
|
135
|
+
goNext,
|
|
136
|
+
goPrev,
|
|
137
|
+
clear,
|
|
138
|
+
registerPageText,
|
|
139
|
+
]);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export { computeMatchesForPage, escapeRegExp, normalize, usePDFSearch };
|
|
143
|
+
//# sourceMappingURL=usePDFSearch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePDFSearch.js","sources":["../../../src/components/FileViewer/usePDFSearch.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { PDFSearchMatch } from './types'\n\nexport const escapeRegExp = (s: string) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\nexport const normalize = (s: string) => s.normalize('NFKC')\n\n// 縦書きは未対応(pdf.js が縦書きの textItem を読み順で返さず、連結すると検索文字列がずれるため)\nexport const computeMatchesForPage = (\n pageIndex: number,\n normalizedTextItems: string[],\n escapedQuery: string,\n startGlobalIndex: number,\n): { matches: PDFSearchMatch[]; nextGlobalIndex: number } => {\n const joinedPageText = normalizedTextItems.join('')\n if (joinedPageText.length === 0) return { matches: [], nextGlobalIndex: startGlobalIndex }\n\n const matches: PDFSearchMatch[] = []\n let globalIndex = startGlobalIndex\n\n for (const m of joinedPageText.matchAll(new RegExp(escapedQuery, 'gi'))) {\n const matchStart = m.index\n const matchEnd = matchStart + m[0].length\n\n let itemStart = 0\n for (let i = 0; i < normalizedTextItems.length; i++) {\n if (itemStart >= matchEnd) break\n\n const itemEnd = itemStart + normalizedTextItems[i].length\n\n const overlapStart = Math.max(matchStart, itemStart)\n const overlapEnd = Math.min(matchEnd, itemEnd)\n if (overlapEnd > overlapStart) {\n matches.push({\n pageIndex,\n itemIndex: i,\n matchStart: overlapStart - itemStart,\n matchLength: overlapEnd - overlapStart,\n globalIndex,\n })\n }\n itemStart = itemEnd\n }\n globalIndex++\n }\n return { matches, nextGlobalIndex: globalIndex }\n}\n\nexport const usePDFSearch = (fileUrl: string) => {\n const [query, setQueryState] = useState('')\n const [matches, setMatches] = useState<PDFSearchMatch[]>([])\n const [currentMatchIndex, setCurrentMatchIndex] = useState(-1)\n const pageTextsRef = useRef<Map<number, string[]>>(new Map())\n const queryRef = useRef('')\n\n const matchCount = matches.length === 0 ? 0 : matches[matches.length - 1].globalIndex + 1\n\n const resetMatchState = useCallback(() => {\n setMatches([])\n setCurrentMatchIndex(-1)\n }, [])\n\n const recalculate = useCallback(\n (nextQuery: string, options?: { resetSelection?: boolean }) => {\n if (nextQuery === '') {\n resetMatchState()\n return\n }\n const escapedQuery = escapeRegExp(normalize(nextQuery))\n const pageIndices = Array.from(pageTextsRef.current.keys()).sort((a, b) => a - b)\n const collected: PDFSearchMatch[] = []\n let globalIndex = 0\n for (const pageIndex of pageIndices) {\n const texts = pageTextsRef.current.get(pageIndex)\n if (!texts) continue\n const { matches: pageMatches, nextGlobalIndex } = computeMatchesForPage(\n pageIndex,\n texts,\n escapedQuery,\n globalIndex,\n )\n collected.push(...pageMatches)\n globalIndex = nextGlobalIndex\n }\n setMatches(collected)\n if (options?.resetSelection) {\n setCurrentMatchIndex(-1)\n return\n }\n setCurrentMatchIndex((prev) => {\n if (globalIndex === 0) return -1\n if (prev >= globalIndex) return globalIndex - 1\n return prev\n })\n },\n [resetMatchState],\n )\n\n const setQuery = useCallback(\n (nextQuery: string) => {\n queryRef.current = nextQuery\n setQueryState(nextQuery)\n recalculate(nextQuery, { resetSelection: true })\n },\n [recalculate],\n )\n\n const registerPageText = useCallback(\n (pageIndex: number, texts: string[]) => {\n pageTextsRef.current.set(pageIndex, texts.map(normalize))\n // 全ページ読み込み前に検索が始まっても、後から読んだページがヒットするよう再計算する。\n if (queryRef.current !== '') {\n recalculate(queryRef.current)\n }\n },\n [recalculate],\n )\n\n const clear = useCallback(() => {\n queryRef.current = ''\n setQueryState('')\n resetMatchState()\n }, [resetMatchState])\n\n const goNext = useCallback(() => {\n setCurrentMatchIndex((prev) => {\n if (matchCount === 0) return -1\n if (prev < 0) return 0\n return (prev + 1) % matchCount\n })\n }, [matchCount])\n\n const goPrev = useCallback(() => {\n setCurrentMatchIndex((prev) => {\n if (matchCount === 0) return -1\n if (prev < 0) return matchCount - 1\n return (prev - 1 + matchCount) % matchCount\n })\n }, [matchCount])\n\n useEffect(() => {\n pageTextsRef.current.clear()\n queryRef.current = ''\n setQueryState('')\n resetMatchState()\n }, [fileUrl, resetMatchState])\n\n return useMemo(\n () => ({\n query,\n setQuery,\n matches,\n matchCount,\n currentMatchIndex,\n goNext,\n goPrev,\n clear,\n registerPageText,\n }),\n [\n query,\n setQuery,\n matches,\n matchCount,\n currentMatchIndex,\n goNext,\n goPrev,\n clear,\n registerPageText,\n ],\n )\n}\n\nexport type UsePDFSearch = ReturnType<typeof usePDFSearch>\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FaMinus, FaCheck, FaCircleInfo, FaCaretDown, FaEllipsis, FaCircleExclamation, FaAngleDown, FaMagnifyingGlassMinus, FaMagnifyingGlassPlus, FaArrowRotateLeft,
|
|
1
|
+
import { FaMinus, FaCheck, FaCircleInfo, FaCaretDown, FaEllipsis, FaCircleExclamation, FaAngleDown, FaMagnifyingGlass, FaAngleUp, FaMagnifyingGlassMinus, FaMagnifyingGlassPlus, FaArrowRotateLeft, FaTrashCan, FaFolderOpen, FaCircleQuestion, FaArrowLeft, FaGrip, FaXmark, FaAnglesRight, FaChevronRight, FaAnglesLeft, FaChevronLeft, FaTriangleExclamation, FaSortUp, FaSortDown, FaCaretRight, FaRotate, FaCircleCheck, FaCalendarDays, FaCirclePlus, FaCircleXmark, FaToolbox, FaGlobe, FaAngleRight, FaArrowRight, FaStar, FaUser, FaGear, FaGraduationCap, FaCaretUp, FaBars, FaUserLarge, FaAddressBook, FaAddressCard, FaAngleLeft, FaArrowDown, FaArrowDownWideShort, FaArrowRightArrowLeft, FaArrowRotateRight, FaArrowUp19, FaArrowUp, FaArrowUpWideShort, FaArrowsRotate, FaAsterisk, FaAt, FaBabyCarriage, FaBan, FaBarsStaggered, FaBell, FaBellSlash, FaBolt, FaBook, FaBookOpen, FaBookmark, FaBoxArchive, FaBuilding, FaBullhorn, FaCakeCandles, FaCalculator, FaCalendarCheck, FaCalendarXmark, FaCamera, FaCaretLeft, FaChartArea, FaChartBar, FaChartColumn, FaChartLine, FaChartPie, FaChevronDown, FaCircleDot, FaCircle, FaCircleMinus, FaCirclePlay, FaCircleUser, FaClipboardCheck, FaClipboard, FaClipboardList, FaClipboardQuestion, FaClock, FaClockRotateLeft, FaCloudArrowDown, FaCloudArrowUp, FaCode, FaCommentDots, FaComment, FaComments, FaCompress, FaCopy, FaCube, FaCubes, FaDatabase, FaDoorOpen, FaDownLeftAndUpRightToCenter, FaEllipsisVertical, FaEnvelope, FaEnvelopeOpen, FaExpand, FaEye, FaEyeSlash, FaFileArrowDown, FaFileCirclePlus, FaFileCode, FaFileCsv, FaFileExcel, FaFileExport, FaFile, FaFileLines, FaFilePdf, FaFilePowerpoint, FaFileWord, FaFilter, FaFloppyDisk, FaFolder, FaFolderPlus, FaFont, FaGauge, FaGears, FaGripLines, FaGripLinesVertical, FaGripVertical, FaHourglassHalf, FaHouse, FaIdBadge, FaIdCard, FaImage, FaImages, FaInbox, FaIntercom, FaKey, FaLeftLong, FaLeftRight, FaLightbulb, FaLink, FaLinkSlash, FaList, FaListOl, FaListUl, FaLocationDot, FaLock, FaLockOpen, FaMessage, FaMicrosoft, FaMoneyCheckDollar, FaMugHot, FaPaintbrush, FaPaperPlane, FaPaperclip, FaPen, FaPenToSquare, FaPencil, FaPersonWalkingArrowLoopLeft, FaPiggyBank, FaPlus, FaPowerOff, FaPrint, FaRegCircleQuestion, FaRegLightbulb, FaReply, FaRightFromBracket, FaRightLeft, FaRightLong, FaRightToBracket, FaRobot, FaRocket, FaRotateLeft, FaShareNodes, FaShieldHalved, FaSliders, FaSort, FaSquareCheck, FaSquareMinus, FaSquarePlus, FaSquarePollVertical, FaSuitcaseMedical, FaTableColumns, FaTable, FaTableList, FaTag, FaTags, FaThumbtack, FaTruck, FaTruckRampBox, FaUpDown, FaUpRightAndDownLeftFromCenter, FaUserCheck, FaUserClock, FaUserPen, FaUserPlus, FaUsers, FaWindowMaximize, FaWindowMinimize, FaWindowRestore, FaYenSign } from './../../vendor/.pnpm/react-icons@5.6.0_react@19.2.6/vendor/react-icons/fa6/index.js';
|
|
2
2
|
import { generateIcon } from './generateIcon.js';
|
|
3
3
|
|
|
4
4
|
const FaAddressBookIcon = /*#__PURE__*/ generateIcon(FaAddressBook);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 常に最新の値を参照するためのフック。
|
|
7
|
+
* useCallbackやuseMemoの依存配列を減らし、不要な再実行を防ぐために使用する。
|
|
8
|
+
*
|
|
9
|
+
* 返されるオブジェクトのプロパティは読み取り専用で、
|
|
10
|
+
* 常に最新の値を参照する。
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const latest = useLatest({
|
|
14
|
+
* onChange,
|
|
15
|
+
* onSelect,
|
|
16
|
+
* selectedItem,
|
|
17
|
+
* })
|
|
18
|
+
*
|
|
19
|
+
* // 最新の値に常にアクセス可能
|
|
20
|
+
* latest.onChange?.(e)
|
|
21
|
+
* if (latest.selectedItem) { ... }
|
|
22
|
+
*/
|
|
23
|
+
function useLatest(values) {
|
|
24
|
+
const ref = React.useRef(values);
|
|
25
|
+
ref.current = values;
|
|
26
|
+
const proxy = React.useMemo(() => new Proxy({}, {
|
|
27
|
+
get: (_target, prop) => ref.current[prop],
|
|
28
|
+
}), []);
|
|
29
|
+
return proxy;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
exports.useLatest = useLatest;
|
|
33
|
+
//# sourceMappingURL=useLatest.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLatest.cjs","sources":["../../src/hooks/useLatest.ts"],"sourcesContent":["import { useMemo, useRef } from 'react'\n\n/**\n * 常に最新の値を参照するためのフック。\n * useCallbackやuseMemoの依存配列を減らし、不要な再実行を防ぐために使用する。\n *\n * 返されるオブジェクトのプロパティは読み取り専用で、\n * 常に最新の値を参照する。\n *\n * @example\n * const latest = useLatest({\n * onChange,\n * onSelect,\n * selectedItem,\n * })\n *\n * // 最新の値に常にアクセス可能\n * latest.onChange?.(e)\n * if (latest.selectedItem) { ... }\n */\nexport function useLatest<T extends object>(values: T): Readonly<T> {\n const ref = useRef<T>(values)\n ref.current = values\n\n const proxy = useMemo(\n () =>\n new Proxy({} as T, {\n get: (_target, prop) => ref.current[prop as keyof T],\n }) as Readonly<T>,\n [],\n )\n\n return proxy\n}\n"],"names":["useRef","useMemo"],"mappings":";;;;AAEA;;;;;;;;;;;;;;;;;AAiBG;AACG,SAAU,SAAS,CAAmB,MAAS,EAAA;AACnD,IAAA,MAAM,GAAG,GAAGA,YAAM,CAAI,MAAM,CAAC;AAC7B,IAAA,GAAG,CAAC,OAAO,GAAG,MAAM;IAEpB,MAAM,KAAK,GAAGC,aAAO,CACnB,MACE,IAAI,KAAK,CAAC,EAAO,EAAE;AACjB,QAAA,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,OAAO,CAAC,IAAe,CAAC;KACrD,CAAgB,EACnB,EAAE,CACH;AAED,IAAA,OAAO,KAAK;AACd;;;;"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 常に最新の値を参照するためのフック。
|
|
3
|
+
* useCallbackやuseMemoの依存配列を減らし、不要な再実行を防ぐために使用する。
|
|
4
|
+
*
|
|
5
|
+
* 返されるオブジェクトのプロパティは読み取り専用で、
|
|
6
|
+
* 常に最新の値を参照する。
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const latest = useLatest({
|
|
10
|
+
* onChange,
|
|
11
|
+
* onSelect,
|
|
12
|
+
* selectedItem,
|
|
13
|
+
* })
|
|
14
|
+
*
|
|
15
|
+
* // 最新の値に常にアクセス可能
|
|
16
|
+
* latest.onChange?.(e)
|
|
17
|
+
* if (latest.selectedItem) { ... }
|
|
18
|
+
*/
|
|
19
|
+
export declare function useLatest<T extends object>(values: T): Readonly<T>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useRef, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 常に最新の値を参照するためのフック。
|
|
5
|
+
* useCallbackやuseMemoの依存配列を減らし、不要な再実行を防ぐために使用する。
|
|
6
|
+
*
|
|
7
|
+
* 返されるオブジェクトのプロパティは読み取り専用で、
|
|
8
|
+
* 常に最新の値を参照する。
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const latest = useLatest({
|
|
12
|
+
* onChange,
|
|
13
|
+
* onSelect,
|
|
14
|
+
* selectedItem,
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // 最新の値に常にアクセス可能
|
|
18
|
+
* latest.onChange?.(e)
|
|
19
|
+
* if (latest.selectedItem) { ... }
|
|
20
|
+
*/
|
|
21
|
+
function useLatest(values) {
|
|
22
|
+
const ref = useRef(values);
|
|
23
|
+
ref.current = values;
|
|
24
|
+
const proxy = useMemo(() => new Proxy({}, {
|
|
25
|
+
get: (_target, prop) => ref.current[prop],
|
|
26
|
+
}), []);
|
|
27
|
+
return proxy;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { useLatest };
|
|
31
|
+
//# sourceMappingURL=useLatest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLatest.js","sources":["../../src/hooks/useLatest.ts"],"sourcesContent":["import { useMemo, useRef } from 'react'\n\n/**\n * 常に最新の値を参照するためのフック。\n * useCallbackやuseMemoの依存配列を減らし、不要な再実行を防ぐために使用する。\n *\n * 返されるオブジェクトのプロパティは読み取り専用で、\n * 常に最新の値を参照する。\n *\n * @example\n * const latest = useLatest({\n * onChange,\n * onSelect,\n * selectedItem,\n * })\n *\n * // 最新の値に常にアクセス可能\n * latest.onChange?.(e)\n * if (latest.selectedItem) { ... }\n */\nexport function useLatest<T extends object>(values: T): Readonly<T> {\n const ref = useRef<T>(values)\n ref.current = values\n\n const proxy = useMemo(\n () =>\n new Proxy({} as T, {\n get: (_target, prop) => ref.current[prop as keyof T],\n }) as Readonly<T>,\n [],\n )\n\n return proxy\n}\n"],"names":[],"mappings":";;AAEA;;;;;;;;;;;;;;;;;AAiBG;AACG,SAAU,SAAS,CAAmB,MAAS,EAAA;AACnD,IAAA,MAAM,GAAG,GAAG,MAAM,CAAI,MAAM,CAAC;AAC7B,IAAA,GAAG,CAAC,OAAO,GAAG,MAAM;IAEpB,MAAM,KAAK,GAAG,OAAO,CACnB,MACE,IAAI,KAAK,CAAC,EAAO,EAAE;AACjB,QAAA,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,KAAK,GAAG,CAAC,OAAO,CAAC,IAAe,CAAC;KACrD,CAAgB,EACnB,EAAE,CACH;AAED,IAAA,OAAO,KAAK;AACd;;;;"}
|