@semiont/react-ui 0.5.26 → 0.5.28
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/README.md +28 -4
- package/dist/{PdfAnnotationCanvas.client-6ZSF6ZTJ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +33 -6
- package/dist/{PdfAnnotationCanvas.client-JCJMXWWJ.css → PdfAnnotationCanvas.client-TGDPPGXJ.css} +6 -1
- package/dist/{ar-YTUUGK2O.js → ar-FABPTJVB.js} +88 -26
- package/dist/{bn-ASGNGPMD.js → bn-LKP6BDMH.js} +88 -26
- package/dist/{chunk-L3WKRWP3.js → chunk-2GOHTTHW.js} +88 -26
- package/dist/{chunk-OBQOZCJE.js → chunk-2YHEE63S.js} +31 -31
- package/dist/{cs-WMYVAOZY.js → cs-YQYNWHRG.js} +88 -26
- package/dist/{da-FSHHN4D6.js → da-QHZOYKRY.js} +88 -26
- package/dist/{de-FJFO3TPT.js → de-GKA7RD7J.js} +88 -26
- package/dist/{el-XOH4QIAG.js → el-45T257AL.js} +88 -26
- package/dist/{en-3FCBW3YD.js → en-RVW5EQ66.js} +4 -2
- package/dist/{es-HDX4QI7E.js → es-AVER7RU4.js} +88 -26
- package/dist/{fa-DJ6WTFTJ.js → fa-K3VPK3O2.js} +88 -26
- package/dist/{fi-4S7H7W7U.js → fi-5NEKF7EB.js} +88 -26
- package/dist/{fr-6UJSYHXC.js → fr-FUMNDLBZ.js} +88 -26
- package/dist/{he-ZZJOHJX2.js → he-3JGNA3PR.js} +88 -26
- package/dist/{hi-HZJTAW7U.js → hi-IJY375DF.js} +88 -26
- package/dist/{id-E3I6ILEM.js → id-LWHKJ7ZD.js} +88 -26
- package/dist/index.css +380 -524
- package/dist/index.d.ts +318 -192
- package/dist/index.js +2960 -2421
- package/dist/integrations/css-modules-helper.js +1 -3
- package/dist/{it-ULAPAD7U.js → it-EBVOV4RW.js} +88 -26
- package/dist/{ja-SAHZQ4CZ.js → ja-KWRIX2NF.js} +88 -26
- package/dist/{ko-DFU2ADQ4.js → ko-L5CJMFDS.js} +88 -26
- package/dist/{ms-NWL76XNT.js → ms-LSNMJDR2.js} +88 -26
- package/dist/{nl-ATCM76WN.js → nl-ZB32MB7U.js} +88 -26
- package/dist/{no-UIAQ5RJF.js → no-4GTNY7M5.js} +88 -26
- package/dist/{pl-NOECQDXD.js → pl-LUHBLTHB.js} +88 -26
- package/dist/{pt-SVWIIUPC.js → pt-C4H3UNQ6.js} +88 -26
- package/dist/{ro-NR2DRTZG.js → ro-GGITS5V4.js} +88 -26
- package/dist/{sv-STVJ37JP.js → sv-T56RTLBP.js} +88 -26
- package/dist/test-utils.css +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.js +2 -2
- package/dist/{th-UWQR4K54.js → th-22X2IWCB.js} +88 -26
- package/dist/{tr-WNXZSD27.js → tr-77TFG62H.js} +88 -26
- package/dist/{uk-SJRMB47Y.js → uk-MIIL6NXT.js} +88 -26
- package/dist/{vi-TMRQFES2.js → vi-7JYOIGNV.js} +88 -26
- package/dist/{zh-QOL3LYEM.js → zh-ID6DRYU4.js} +88 -26
- package/package.json +11 -10
- package/src/components/AssistProgress.tsx +178 -134
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
- package/src/components/ProtectedErrorBoundary.css +3 -1
- package/src/components/StatusDisplay.css +1 -1
- package/src/components/Toast.css +3 -1
- package/src/components/__tests__/AssistProgress.test.tsx +268 -141
- package/src/components/annotation/annotation-entries.css +0 -4
- package/src/components/annotation/references.css +0 -4
- package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
- package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
- package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
- package/src/components/layout/Header.css +0 -4
- package/src/components/layout/LeftSidebar.css +0 -4
- package/src/components/modals/ComposeStep.tsx +218 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +131 -54
- package/src/components/modals/ConfigureSearchStep.tsx +42 -40
- package/src/components/modals/ContextSummary.tsx +185 -72
- package/src/components/modals/CorpusPane.tsx +75 -0
- package/src/components/modals/DiscardPrompt.tsx +38 -0
- package/src/components/modals/GatherContextStep.tsx +237 -107
- package/src/components/modals/ReferenceWizardModal.tsx +287 -60
- package/src/components/modals/ResourceGenerateModal.tsx +233 -109
- package/src/components/modals/SearchModal.css +335 -111
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/modals/SearchResultsStep.tsx +63 -88
- package/src/components/modals/WizardFooter.tsx +69 -0
- package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
- package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +290 -0
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +95 -0
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +635 -0
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +277 -85
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +344 -0
- package/src/components/modals/modals.css +16 -42
- package/src/components/navigation/NavigationTabs.css +0 -20
- package/src/components/pdf-annotation/PdfAnnotationCanvas.css +21 -2
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +36 -3
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +41 -6
- package/src/components/resource/AnnotateView.tsx +19 -8
- package/src/components/resource/AnnotationHistory.tsx +2 -0
- package/src/components/resource/BrowseView.tsx +20 -9
- package/src/components/resource/ResourceViewer.tsx +27 -28
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +38 -12
- package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/event-formatting.ts +0 -3
- package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
- package/src/components/resource/panels/AssessmentPanel.css +5 -6
- package/src/components/resource/panels/AssistSection.css +95 -263
- package/src/components/resource/panels/AssistSection.tsx +4 -4
- package/src/components/resource/panels/AssistShell.tsx +12 -4
- package/src/components/resource/panels/CollaborationPanel.css +47 -22
- package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
- package/src/components/resource/panels/CommentEntry.tsx +1 -1
- package/src/components/resource/panels/CommentsPanel.css +0 -16
- package/src/components/resource/panels/HighlightEntry.tsx +1 -1
- package/src/components/resource/panels/HighlightPanel.css +0 -7
- package/src/components/resource/panels/JsonLdPanel.css +0 -4
- package/src/components/resource/panels/ReferenceEntry.tsx +10 -3
- package/src/components/resource/panels/ReferencesPanel.css +0 -12
- package/src/components/resource/panels/ReferencesPanel.tsx +16 -20
- package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/StatisticsPanel.css +0 -20
- package/src/components/resource/panels/TagEntry.tsx +1 -1
- package/src/components/resource/panels/TaggingPanel.css +0 -4
- package/src/components/resource/panels/TaggingPanel.tsx +2 -10
- package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
- package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +67 -54
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +185 -7
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
- package/src/components/settings/SettingsPanel.css +0 -4
- package/src/features/auth/auth.css +2 -26
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -76
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -48
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
- package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +166 -71
- package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
- package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
- package/src/styles/core/buttons.css +3 -1
- package/src/styles/core/forms.css +35 -29
- package/src/styles/core/indicators.css +0 -4
- package/src/styles/core/inputs.css +0 -4
- package/src/styles/core/progress.css +2 -34
- package/src/styles/core/sliders.css +0 -4
- package/src/styles/core/toggles.css +0 -4
- package/src/styles/features/resource-discovery.css +13 -0
- package/src/styles/features/resource-viewer.css +0 -42
- package/src/styles/features/resource.css +0 -4
- package/src/styles/motivations/motivation-assessment.css +2 -2
- package/src/styles/panels/history-panel.css +0 -40
- package/src/styles/panels/user-panel.css +0 -3
- package/src/styles/patterns/panel-helpers.css +0 -27
- package/src/styles/patterns/panel-sections.css +0 -59
- package/src/styles/patterns/panels-base.css +0 -32
- package/translations/ar.json +84 -24
- package/translations/bn.json +84 -24
- package/translations/cs.json +84 -24
- package/translations/da.json +84 -24
- package/translations/de.json +84 -24
- package/translations/el.json +84 -24
- package/translations/en.json +84 -24
- package/translations/es.json +84 -24
- package/translations/fa.json +84 -24
- package/translations/fi.json +84 -24
- package/translations/fr.json +84 -24
- package/translations/he.json +84 -24
- package/translations/hi.json +84 -24
- package/translations/id.json +84 -24
- package/translations/it.json +84 -24
- package/translations/ja.json +84 -24
- package/translations/ko.json +84 -24
- package/translations/ms.json +84 -24
- package/translations/nl.json +84 -24
- package/translations/no.json +84 -24
- package/translations/pl.json +84 -24
- package/translations/pt.json +84 -24
- package/translations/ro.json +84 -24
- package/translations/sv.json +84 -24
- package/translations/th.json +84 -24
- package/translations/tr.json +84 -24
- package/translations/uk.json +84 -24
- package/translations/vi.json +84 -24
- package/translations/zh.json +84 -24
- package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +0 -1
- package/dist/ar-YTUUGK2O.js.map +0 -1
- package/dist/bn-ASGNGPMD.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-L3WKRWP3.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-OBQOZCJE.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/chunk-XUDKYAVC.js +0 -21
- package/dist/chunk-XUDKYAVC.js.map +0 -1
- package/dist/cs-WMYVAOZY.js.map +0 -1
- package/dist/da-FSHHN4D6.js.map +0 -1
- package/dist/de-FJFO3TPT.js.map +0 -1
- package/dist/el-XOH4QIAG.js.map +0 -1
- package/dist/en-3FCBW3YD.js.map +0 -1
- package/dist/es-HDX4QI7E.js.map +0 -1
- package/dist/fa-DJ6WTFTJ.js.map +0 -1
- package/dist/fi-4S7H7W7U.js.map +0 -1
- package/dist/fr-6UJSYHXC.js.map +0 -1
- package/dist/he-ZZJOHJX2.js.map +0 -1
- package/dist/hi-HZJTAW7U.js.map +0 -1
- package/dist/id-E3I6ILEM.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/css-modules-helper.js.map +0 -1
- package/dist/integrations/styled-components-theme.js.map +0 -1
- package/dist/it-ULAPAD7U.js.map +0 -1
- package/dist/ja-SAHZQ4CZ.js.map +0 -1
- package/dist/ko-DFU2ADQ4.js.map +0 -1
- package/dist/ms-NWL76XNT.js.map +0 -1
- package/dist/nl-ATCM76WN.js.map +0 -1
- package/dist/no-UIAQ5RJF.js.map +0 -1
- package/dist/pl-NOECQDXD.js.map +0 -1
- package/dist/pt-SVWIIUPC.js.map +0 -1
- package/dist/ro-NR2DRTZG.js.map +0 -1
- package/dist/sv-STVJ37JP.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-UWQR4K54.js.map +0 -1
- package/dist/tr-WNXZSD27.js.map +0 -1
- package/dist/uk-SJRMB47Y.js.map +0 -1
- package/dist/vi-TMRQFES2.js.map +0 -1
- package/dist/zh-QOL3LYEM.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -159
- package/src/integrations/tailwind-plugin.cjs +0 -224
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WIZARD-NAVIGATION P1 — one dismissal, one retreat, one advance.
|
|
3
|
+
*
|
|
4
|
+
* The wizard used to offer four ways out of one step (corner ✕, Esc, backdrop, and a
|
|
5
|
+
* footer `✕ Cancel`) and none at all on the step that starts the flow. D1 settled it:
|
|
6
|
+
* the corner control plus Esc and backdrop are the dismissal, and the footer is purely
|
|
7
|
+
* flow — where you came from, where you are going.
|
|
8
|
+
*
|
|
9
|
+
* These assert STRUCTURE, never copy: labels arrive as props and will be revised.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
12
|
+
import { render, screen } from '@testing-library/react';
|
|
13
|
+
import userEvent from '@testing-library/user-event';
|
|
14
|
+
import '@testing-library/jest-dom';
|
|
15
|
+
import { WizardFooter } from '../WizardFooter';
|
|
16
|
+
import { ConfigureSearchStep } from '../ConfigureSearchStep';
|
|
17
|
+
import { ConfigureGenerationStep } from '../ConfigureGenerationStep';
|
|
18
|
+
import { SearchResultsStep } from '../SearchResultsStep';
|
|
19
|
+
import { ConfigureGatherStep } from '../ConfigureGatherStep';
|
|
20
|
+
import { GatherContextStep } from '../GatherContextStep';
|
|
21
|
+
import type { GatheredContext } from '@semiont/core';
|
|
22
|
+
|
|
23
|
+
/** Anything that reads as "get me out of here" belongs in the corner, not the footer. */
|
|
24
|
+
const DISMISSAL = /cancel|close|dismiss|✕|✖|×/i;
|
|
25
|
+
|
|
26
|
+
const footerOf = (container: HTMLElement) =>
|
|
27
|
+
container.querySelector('.semiont-modal__actions') as HTMLElement | null;
|
|
28
|
+
|
|
29
|
+
const buttonsIn = (footer: HTMLElement) => Array.from(footer.querySelectorAll('button'));
|
|
30
|
+
|
|
31
|
+
describe('WizardFooter', () => {
|
|
32
|
+
it('renders retreat and advance, and nothing that dismisses', () => {
|
|
33
|
+
const { container } = render(
|
|
34
|
+
<WizardFooter
|
|
35
|
+
backLabel="Back"
|
|
36
|
+
onBack={vi.fn()}
|
|
37
|
+
primary={{ label: 'Search', type: 'submit' }}
|
|
38
|
+
/>,
|
|
39
|
+
);
|
|
40
|
+
const footer = footerOf(container)!;
|
|
41
|
+
const labels = buttonsIn(footer).map((b) => b.textContent ?? '');
|
|
42
|
+
expect(labels).toHaveLength(2);
|
|
43
|
+
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('omits Back entirely when there is nowhere to go back to', () => {
|
|
47
|
+
const { container } = render(
|
|
48
|
+
<WizardFooter primary={{ label: 'Search', type: 'submit' }} />,
|
|
49
|
+
);
|
|
50
|
+
expect(buttonsIn(footerOf(container)!)).toHaveLength(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders Back alone when the step has no footer-level action', () => {
|
|
54
|
+
// Search results: the action is per row, so the footer carries retreat only.
|
|
55
|
+
const { container } = render(<WizardFooter backLabel="Back" onBack={vi.fn()} />);
|
|
56
|
+
const labels = buttonsIn(footerOf(container)!).map((b) => b.textContent);
|
|
57
|
+
expect(labels).toEqual(['◀ Back']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('does not stretch its buttons to equal width', () => {
|
|
61
|
+
// `--flex` made retreat and advance read as peers. Retreat is chrome.
|
|
62
|
+
const { container } = render(
|
|
63
|
+
<WizardFooter backLabel="Back" onBack={vi.fn()} primary={{ label: 'Go', type: 'button', onClick: vi.fn() }} />,
|
|
64
|
+
);
|
|
65
|
+
for (const button of buttonsIn(footerOf(container)!)) {
|
|
66
|
+
expect(button.className).not.toContain('semiont-button--flex');
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('a pending primary disables itself and Back, and says so', async () => {
|
|
71
|
+
const onBack = vi.fn();
|
|
72
|
+
render(
|
|
73
|
+
<WizardFooter
|
|
74
|
+
backLabel="Back"
|
|
75
|
+
onBack={onBack}
|
|
76
|
+
primary={{ label: 'Search', pendingLabel: 'Searching…', pending: true, type: 'submit' }}
|
|
77
|
+
/>,
|
|
78
|
+
);
|
|
79
|
+
expect(screen.getByText(/Searching…/)).toBeInTheDocument();
|
|
80
|
+
await userEvent.click(screen.getByText(/Back/));
|
|
81
|
+
expect(onBack).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('a disabled primary is not clickable, but Back stays live', async () => {
|
|
85
|
+
// `disabled` is the action's PRECONDITION loading (review step waiting on
|
|
86
|
+
// gather) — distinct from `pending`, which is the action itself running and
|
|
87
|
+
// rightly freezes retreat too. While a precondition loads, retreat must work.
|
|
88
|
+
const onBack = vi.fn();
|
|
89
|
+
const onAdvance = vi.fn();
|
|
90
|
+
render(
|
|
91
|
+
<WizardFooter
|
|
92
|
+
backLabel="Back"
|
|
93
|
+
onBack={onBack}
|
|
94
|
+
primary={{ label: 'Next', type: 'button', onClick: onAdvance, disabled: true }}
|
|
95
|
+
/>,
|
|
96
|
+
);
|
|
97
|
+
await userEvent.click(screen.getByText(/Next/));
|
|
98
|
+
expect(onAdvance).not.toHaveBeenCalled();
|
|
99
|
+
await userEvent.click(screen.getByText(/Back/));
|
|
100
|
+
expect(onBack).toHaveBeenCalledTimes(1);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
105
|
+
// The same rule, asserted against the real steps — a footer component is
|
|
106
|
+
// only worth having if every step actually uses it.
|
|
107
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
108
|
+
const SEARCH_T = {
|
|
109
|
+
maxResults: 'Max Results', semanticScoring: 'Semantic Scoring',
|
|
110
|
+
semanticScoringHelp: 'help', back: 'Back', search: 'Search', searching: 'Searching…',
|
|
111
|
+
searchFailed: 'Search failed',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const GEN_T = {
|
|
115
|
+
resourceTitle: 'Title', resourceTitlePlaceholder: '', saveLocation: 'Save location', additionalInstructions: 'Instructions',
|
|
116
|
+
additionalInstructionsPlaceholder: '', language: 'Language', languageHelp: '',
|
|
117
|
+
creativity: 'Creativity', creativityFocused: 'Focused', creativityCreative: 'Creative',
|
|
118
|
+
maxLength: 'Max Length', maxLengthHelp: '', maxLengthCeiling: 'Limited to {{max}} by {{model}}.',
|
|
119
|
+
back: 'Back', generate: 'Generate',
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const CONTEXT_T = {
|
|
123
|
+
sourceContextLabel: 'Source', connectionsLabel: 'Connections', citedByLabel: 'Cited by',
|
|
124
|
+
graphPaneTitle: 'In the graph', graphEmpty: 'No links yet.', resourceLinkLabel: 'Resource link',
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const GATHER_T = {
|
|
128
|
+
intro: 'Choose what to include.', includeContent: 'Content', includeSummary: 'Summary',
|
|
129
|
+
depth: 'Depth', maxResources: 'Max', gather: 'Gather',
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const RESULTS_T = { noResults: 'None', score: 'Score', link: 'Link', back: 'Back', ...CONTEXT_T };
|
|
133
|
+
|
|
134
|
+
const CONTEXT = {
|
|
135
|
+
focus: { kind: 'annotation', annotation: { id: 'a1' }, sourceResource: { '@id': 'r1', name: 'Src' } },
|
|
136
|
+
// `graph` is not optional in practice: ContextSummary hands it straight to
|
|
137
|
+
// `deriveViews`, which dereferences `.nodes`.
|
|
138
|
+
graph: { nodes: [], edges: [] },
|
|
139
|
+
} as unknown as GatheredContext;
|
|
140
|
+
|
|
141
|
+
describe('every step footer follows the grammar (A1, A2)', () => {
|
|
142
|
+
it('ConfigureSearchStep', () => {
|
|
143
|
+
const { container } = render(
|
|
144
|
+
<ConfigureSearchStep
|
|
145
|
+
config={{ limit: 10, useSemanticScoring: true }}
|
|
146
|
+
onConfigChange={vi.fn()}
|
|
147
|
+
onBack={vi.fn()}
|
|
148
|
+
onSearch={vi.fn()}
|
|
149
|
+
translations={SEARCH_T}
|
|
150
|
+
/>,
|
|
151
|
+
);
|
|
152
|
+
const labels = buttonsIn(footerOf(container)!).map((b) => b.textContent ?? '');
|
|
153
|
+
expect(labels).toHaveLength(2);
|
|
154
|
+
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('SearchResultsStep — footer is retreat only; Link lives on the rows', () => {
|
|
158
|
+
const { container } = render(
|
|
159
|
+
<SearchResultsStep
|
|
160
|
+
results={[{ id: 'r1', name: 'Black Sea', score: 54.22 } as never]}
|
|
161
|
+
onBack={vi.fn()}
|
|
162
|
+
onLink={vi.fn()}
|
|
163
|
+
translations={RESULTS_T}
|
|
164
|
+
/>,
|
|
165
|
+
);
|
|
166
|
+
const labels = buttonsIn(footerOf(container)!).map((b) => b.textContent ?? '');
|
|
167
|
+
expect(labels).toHaveLength(1);
|
|
168
|
+
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
169
|
+
expect(screen.getByText(/Link/)).toBeInTheDocument();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('ConfigureGenerationStep', () => {
|
|
173
|
+
const { container } = render(
|
|
174
|
+
<ConfigureGenerationStep
|
|
175
|
+
context={CONTEXT}
|
|
176
|
+
config={{
|
|
177
|
+
title: 'Caspian Sea', storagePath: '', prompt: '', language: 'en',
|
|
178
|
+
temperature: 0.7, maxTokensText: '500',
|
|
179
|
+
}}
|
|
180
|
+
onConfigChange={vi.fn()}
|
|
181
|
+
onBack={vi.fn()}
|
|
182
|
+
onGenerate={vi.fn()}
|
|
183
|
+
translations={GEN_T}
|
|
184
|
+
/>,
|
|
185
|
+
);
|
|
186
|
+
const labels = buttonsIn(footerOf(container)!).map((b) => b.textContent ?? '');
|
|
187
|
+
expect(labels).toHaveLength(2);
|
|
188
|
+
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('ConfigureGatherStep — first step of the resource-generate flow: advance only (GFR A5)', () => {
|
|
192
|
+
const { container } = render(
|
|
193
|
+
<ConfigureGatherStep onGather={vi.fn()} translations={GATHER_T} />,
|
|
194
|
+
);
|
|
195
|
+
const labels = buttonsIn(footerOf(container)!).map((b) => b.textContent ?? '');
|
|
196
|
+
expect(labels).toHaveLength(1); // nowhere to go back to, and dismissal is the corner's job
|
|
197
|
+
expect(labels.filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
202
|
+
// The THIRD footer species: the resolution CHOOSER. The one deliberate
|
|
203
|
+
// exception to the WizardFooter grammar — a three-way fork the
|
|
204
|
+
// one-retreat-plus-one-advance component cannot express. Its grammar is
|
|
205
|
+
// its own, and this pin is what keeps it a species instead of drift.
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
207
|
+
describe('the resolution chooser (GatherContextStep) — the named exception', () => {
|
|
208
|
+
const ANNOTATE = {
|
|
209
|
+
userHint: '',
|
|
210
|
+
onUserHintChange: vi.fn(),
|
|
211
|
+
onBind: vi.fn(),
|
|
212
|
+
onGenerate: vi.fn(),
|
|
213
|
+
onCompose: vi.fn(),
|
|
214
|
+
translations: {
|
|
215
|
+
search: 'Search', generate: 'Generate', compose: 'Compose',
|
|
216
|
+
resolutionStrategyLabel: 'Strategy', userHintLabel: 'Hint',
|
|
217
|
+
userHintEffect: 'steers Search and Generate', userHintPlaceholder: '',
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
const DISPLAY_T = {
|
|
221
|
+
loadingContext: 'Loading…', failedContext: 'Failed', ...CONTEXT_T,
|
|
222
|
+
corpusPaneTitle: 'In the corpus', corpusEmpty: 'Nothing similar.',
|
|
223
|
+
excludedReceipt: '{{types}} excluded', machineRead: 'OCR', score: 'Score',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const chooserButtons = (container: HTMLElement) =>
|
|
227
|
+
Array.from(container.querySelectorAll('.semiont-gather__actions button'));
|
|
228
|
+
|
|
229
|
+
it('three equal-width peers, one of them quiet, none of them dismissal', () => {
|
|
230
|
+
const { container } = render(
|
|
231
|
+
<GatherContextStep
|
|
232
|
+
context={CONTEXT} contextLoading={false} contextError={null}
|
|
233
|
+
annotate={ANNOTATE} translations={DISPLAY_T}
|
|
234
|
+
/>,
|
|
235
|
+
);
|
|
236
|
+
const buttons = chooserButtons(container);
|
|
237
|
+
expect(buttons).toHaveLength(3);
|
|
238
|
+
// Equal width is HONEST here — these are actual peers. This is the same
|
|
239
|
+
// `--flex` the shared footer bans for making retreat read as advance's peer.
|
|
240
|
+
for (const b of buttons) expect(b.className).toContain('semiont-button--flex');
|
|
241
|
+
// The AI paths are primary; the manual path's demotion to secondary is the
|
|
242
|
+
// recorded convention, not drift.
|
|
243
|
+
const secondary = buttons.filter((b) => b.className.includes('semiont-button--secondary'));
|
|
244
|
+
expect(secondary).toHaveLength(1);
|
|
245
|
+
expect(secondary[0]!.textContent).toContain('Compose');
|
|
246
|
+
// Dismissal stays in the corner, as on every footer.
|
|
247
|
+
expect(buttons.map((b) => b.textContent ?? '').filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('ellipses mark step-vs-act: the step-leading choices carry one, the acting one does not', () => {
|
|
251
|
+
// The "…" is component-owned markup, not translated copy: Search and
|
|
252
|
+
// Generate lead to another step; Compose acts immediately (navigates away).
|
|
253
|
+
const { container } = render(
|
|
254
|
+
<GatherContextStep
|
|
255
|
+
context={CONTEXT} contextLoading={false} contextError={null}
|
|
256
|
+
annotate={ANNOTATE} translations={DISPLAY_T}
|
|
257
|
+
/>,
|
|
258
|
+
);
|
|
259
|
+
const labels = chooserButtons(container).map((b) => (b.textContent ?? '').trim());
|
|
260
|
+
expect(labels.filter((l) => l.endsWith('…'))).toHaveLength(2);
|
|
261
|
+
expect(labels.find((l) => l.includes('Compose'))!.endsWith('…')).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('every choice waits for context', () => {
|
|
265
|
+
const { container } = render(
|
|
266
|
+
<GatherContextStep
|
|
267
|
+
context={CONTEXT} contextLoading contextError={null}
|
|
268
|
+
annotate={ANNOTATE} translations={DISPLAY_T}
|
|
269
|
+
/>,
|
|
270
|
+
);
|
|
271
|
+
for (const b of chooserButtons(container)) expect(b).toBeDisabled();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
276
|
+
// WIZARD-NAVIGATION P3 — the exits, pinned.
|
|
277
|
+
//
|
|
278
|
+
// HeadlessUI already wires Esc and the backdrop to `onClose`; these are the
|
|
279
|
+
// guard, not the fix. What they prevent is a later change that "improves" Esc
|
|
280
|
+
// into a step-back, or that disables the way out while a request is in flight.
|
|
281
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
282
|
+
describe('the way out (A4, A5)', () => {
|
|
283
|
+
it('a pending step never disables the corner control — only the footer', () => {
|
|
284
|
+
// D5: dismissal must survive a search that never returns. The footer's own
|
|
285
|
+
// buttons DO disable (pressing Search twice is not a feature); the corner is
|
|
286
|
+
// not the footer's to disable, which is why it does not live there.
|
|
287
|
+
const { container } = render(
|
|
288
|
+
<ConfigureSearchStep
|
|
289
|
+
config={{ limit: 10, useSemanticScoring: true }}
|
|
290
|
+
onConfigChange={vi.fn()}
|
|
291
|
+
isSearching
|
|
292
|
+
onBack={vi.fn()}
|
|
293
|
+
onSearch={vi.fn()}
|
|
294
|
+
translations={SEARCH_T}
|
|
295
|
+
/>,
|
|
296
|
+
);
|
|
297
|
+
const footer = footerOf(container)!;
|
|
298
|
+
for (const button of buttonsIn(footer)) expect(button).toBeDisabled();
|
|
299
|
+
// …and the step renders no dismissal of its own to be disabled with them.
|
|
300
|
+
expect(buttonsIn(footer).map((b) => b.textContent ?? '').filter((l) => DISMISSAL.test(l))).toEqual([]);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('Back does not fire while the step is pending', async () => {
|
|
304
|
+
const onBack = vi.fn();
|
|
305
|
+
render(
|
|
306
|
+
<ConfigureSearchStep
|
|
307
|
+
config={{ limit: 10, useSemanticScoring: true }}
|
|
308
|
+
onConfigChange={vi.fn()}
|
|
309
|
+
isSearching
|
|
310
|
+
onBack={onBack}
|
|
311
|
+
onSearch={vi.fn()}
|
|
312
|
+
translations={SEARCH_T}
|
|
313
|
+
/>,
|
|
314
|
+
);
|
|
315
|
+
await userEvent.click(screen.getByText(/Back/));
|
|
316
|
+
expect(onBack).not.toHaveBeenCalled();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('Back is lossless (A3)', () => {
|
|
321
|
+
it('the step reflects the wizard-owned draft, so remounting restores it', () => {
|
|
322
|
+
// The regression this replaces: `useState` inside the step meant stepping
|
|
323
|
+
// back unmounted it and every choice reverted to the default.
|
|
324
|
+
const config = { limit: 20, useSemanticScoring: false };
|
|
325
|
+
const { unmount } = render(
|
|
326
|
+
<ConfigureSearchStep
|
|
327
|
+
config={config} onConfigChange={vi.fn()} onBack={vi.fn()} onSearch={vi.fn()}
|
|
328
|
+
translations={SEARCH_T}
|
|
329
|
+
/>,
|
|
330
|
+
);
|
|
331
|
+
expect((screen.getByLabelText('Max Results') as HTMLSelectElement).value).toBe('20');
|
|
332
|
+
unmount();
|
|
333
|
+
|
|
334
|
+
// Same draft, fresh mount — what "Back then forward" does.
|
|
335
|
+
render(
|
|
336
|
+
<ConfigureSearchStep
|
|
337
|
+
config={config} onConfigChange={vi.fn()} onBack={vi.fn()} onSearch={vi.fn()}
|
|
338
|
+
translations={SEARCH_T}
|
|
339
|
+
/>,
|
|
340
|
+
);
|
|
341
|
+
expect((screen.getByLabelText('Max Results') as HTMLSelectElement).value).toBe('20');
|
|
342
|
+
expect((screen.getByLabelText(/Semantic Scoring/) as HTMLInputElement).checked).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
@@ -111,11 +111,6 @@
|
|
|
111
111
|
background-color: var(--semiont-bg-tertiary);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
[data-theme="dark"] .semiont-modal__close:hover {
|
|
115
|
-
color: var(--semiont-text-primary);
|
|
116
|
-
background-color: var(--semiont-bg-tertiary);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
114
|
.semiont-modal__close-icon {
|
|
120
115
|
width: 1.25rem;
|
|
121
116
|
height: 1.25rem;
|
|
@@ -256,6 +251,22 @@
|
|
|
256
251
|
gap: 0.5rem;
|
|
257
252
|
}
|
|
258
253
|
|
|
254
|
+
/*
|
|
255
|
+
* The wizard footer (WIZARD-NAVIGATION D2). Retreat sits at the leading edge and
|
|
256
|
+
* advance at the trailing one, and neither stretches — `--flex` on all three
|
|
257
|
+
* buttons is what made "Cancel", "Back" and "Search" read as three peers.
|
|
258
|
+
* `margin-inline-end: auto` rather than `space-between` so a footer with only
|
|
259
|
+
* one child still lands on the correct side.
|
|
260
|
+
*/
|
|
261
|
+
.semiont-modal__actions--wizard {
|
|
262
|
+
justify-content: flex-end;
|
|
263
|
+
padding-top: 0.5rem;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.semiont-wizard-footer__back {
|
|
267
|
+
margin-inline-end: auto;
|
|
268
|
+
}
|
|
269
|
+
|
|
259
270
|
/* Chip Styles */
|
|
260
271
|
.semiont-chip {
|
|
261
272
|
padding: 0.25rem 0.75rem;
|
|
@@ -352,12 +363,6 @@
|
|
|
352
363
|
cursor: not-allowed;
|
|
353
364
|
}
|
|
354
365
|
|
|
355
|
-
[data-theme="dark"] .semiont-button--disabled,
|
|
356
|
-
[data-theme="dark"] .semiont-button[data-disabled="true"] {
|
|
357
|
-
background-color: var(--semiont-bg-tertiary);
|
|
358
|
-
color: var(--semiont-text-tertiary);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
366
|
/* Keyboard Shortcuts Styles */
|
|
362
367
|
.semiont-shortcuts {
|
|
363
368
|
display: flex;
|
|
@@ -544,10 +549,6 @@
|
|
|
544
549
|
margin: 0;
|
|
545
550
|
}
|
|
546
551
|
|
|
547
|
-
[data-theme="dark"] .semiont-jsonld-view__title {
|
|
548
|
-
color: var(--semiont-text-primary);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
552
|
.semiont-jsonld-view__back-button,
|
|
552
553
|
.semiont-jsonld-view__copy-button {
|
|
553
554
|
padding: 0.5rem 1rem;
|
|
@@ -572,12 +573,6 @@
|
|
|
572
573
|
border-color: var(--semiont-border-primary);
|
|
573
574
|
}
|
|
574
575
|
|
|
575
|
-
[data-theme="dark"] .semiont-jsonld-view__back-button:hover,
|
|
576
|
-
[data-theme="dark"] .semiont-jsonld-view__copy-button:hover {
|
|
577
|
-
background-color: var(--semiont-bg-secondary);
|
|
578
|
-
border-color: var(--semiont-border-secondary);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
576
|
.semiont-jsonld-view__editor {
|
|
582
577
|
flex: 1;
|
|
583
578
|
overflow: auto;
|
|
@@ -585,10 +580,6 @@
|
|
|
585
580
|
background-color: var(--semiont-bg-primary);
|
|
586
581
|
}
|
|
587
582
|
|
|
588
|
-
[data-theme="dark"] .semiont-jsonld-view__editor {
|
|
589
|
-
background-color: var(--semiont-bg-primary);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
583
|
/* Ensure CodeMirror takes full height */
|
|
593
584
|
.semiont-jsonld-view__editor .cm-editor {
|
|
594
585
|
height: 100%;
|
|
@@ -620,10 +611,6 @@
|
|
|
620
611
|
margin: 0;
|
|
621
612
|
}
|
|
622
613
|
|
|
623
|
-
[data-theme="dark"] .semiont-popup-header__title {
|
|
624
|
-
color: var(--semiont-text-primary);
|
|
625
|
-
}
|
|
626
|
-
|
|
627
614
|
.semiont-popup-header__subtitle {
|
|
628
615
|
font-size: 0.875rem;
|
|
629
616
|
font-weight: normal;
|
|
@@ -631,10 +618,6 @@
|
|
|
631
618
|
margin-left: 0.5rem;
|
|
632
619
|
}
|
|
633
620
|
|
|
634
|
-
[data-theme="dark"] .semiont-popup-header__subtitle {
|
|
635
|
-
color: var(--semiont-text-secondary);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
621
|
.semiont-popup-header__close-button {
|
|
639
622
|
padding: 0.25rem;
|
|
640
623
|
border-radius: 0.25rem;
|
|
@@ -657,15 +640,6 @@
|
|
|
657
640
|
outline-offset: 2px;
|
|
658
641
|
}
|
|
659
642
|
|
|
660
|
-
[data-theme="dark"] .semiont-popup-header__close-button {
|
|
661
|
-
color: var(--semiont-text-secondary);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
[data-theme="dark"] .semiont-popup-header__close-button:hover {
|
|
665
|
-
background-color: var(--semiont-bg-tertiary);
|
|
666
|
-
color: var(--semiont-text-primary);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
643
|
[data-theme="dark"] .semiont-popup-header__close-button:focus-visible {
|
|
670
644
|
outline-color: var(--semiont-color-primary-400);
|
|
671
645
|
}
|
|
@@ -36,10 +36,6 @@
|
|
|
36
36
|
align-items: center;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
[data-theme="dark"] .semiont-nav-section__header-button {
|
|
40
|
-
color: inherit;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
39
|
.semiont-nav-section__header-button:hover {
|
|
44
40
|
opacity: 0.8;
|
|
45
41
|
}
|
|
@@ -68,10 +64,6 @@
|
|
|
68
64
|
text-align: left;
|
|
69
65
|
}
|
|
70
66
|
|
|
71
|
-
[data-theme="dark"] .semiont-nav-section__header-text {
|
|
72
|
-
color: inherit;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
67
|
.semiont-nav-section__header-icon {
|
|
76
68
|
display: flex;
|
|
77
69
|
align-items: center;
|
|
@@ -99,14 +91,6 @@
|
|
|
99
91
|
outline-offset: 2px;
|
|
100
92
|
}
|
|
101
93
|
|
|
102
|
-
[data-theme="dark"] .semiont-nav-section__header-icon {
|
|
103
|
-
color: var(--semiont-text-secondary);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
[data-theme="dark"] .semiont-nav-section__header-icon:hover {
|
|
107
|
-
background: var(--semiont-bg-tertiary);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
94
|
[data-theme="dark"] .semiont-nav-section__header-icon:focus-visible {
|
|
111
95
|
outline-color: var(--semiont-color-primary-400);
|
|
112
96
|
}
|
|
@@ -361,10 +345,6 @@
|
|
|
361
345
|
outline-color: var(--semiont-color-primary-400);
|
|
362
346
|
}
|
|
363
347
|
|
|
364
|
-
[data-theme="dark"] .semiont-resource-tab__link:visited {
|
|
365
|
-
color: inherit !important;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
348
|
/* Resource tab icon (emoji) */
|
|
369
349
|
.semiont-resource-tab__icon {
|
|
370
350
|
flex-shrink: 0;
|
|
@@ -101,9 +101,16 @@
|
|
|
101
101
|
scrollbar-width: none; /* standard; Firefox + recent Chromium */
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/*
|
|
104
|
+
/*
|
|
105
|
+
* WebKit/Safari, which has no `scrollbar-width` support to rely on.
|
|
106
|
+
*
|
|
107
|
+
* This hides a SCROLLBAR, not content: `scrollbar-width: none` above already
|
|
108
|
+
* does the same for every other engine, the rail still scrolls by wheel and
|
|
109
|
+
* drag, and each page is a focusable button — so tabbing through the rail
|
|
110
|
+
* scrolls it, which is the keyboard path the rule is protecting.
|
|
111
|
+
*/
|
|
105
112
|
.semiont-pdf-annotation-canvas__strip::-webkit-scrollbar {
|
|
106
|
-
display: none;
|
|
113
|
+
display: none; /* stylelint-disable-line semiont/accessibility */
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
/*
|
|
@@ -160,6 +167,18 @@
|
|
|
160
167
|
font-weight: var(--semiont-font-semibold);
|
|
161
168
|
}
|
|
162
169
|
|
|
170
|
+
/*
|
|
171
|
+
* Palette tokens are one fixed hue in both themes, so this marker needs a real
|
|
172
|
+
* variant — the accent steps lighter against a dark surface (the treatment
|
|
173
|
+
* `.semiont-checkbox:checked` uses), and the number flips to dark type, which
|
|
174
|
+
* at this size reads far better on that lighter blue than white does.
|
|
175
|
+
*/
|
|
176
|
+
[data-theme="dark"] .semiont-pdf-annotation-canvas__strip-page[aria-current="page"] {
|
|
177
|
+
border-color: var(--semiont-color-primary-400);
|
|
178
|
+
background: var(--semiont-color-primary-400);
|
|
179
|
+
color: var(--semiont-color-neutral-900);
|
|
180
|
+
}
|
|
181
|
+
|
|
163
182
|
.semiont-pdf-annotation-canvas__strip-page:focus-visible {
|
|
164
183
|
outline: 2px solid var(--semiont-focus-ring);
|
|
165
184
|
outline-offset: 1px;
|
|
@@ -353,7 +353,7 @@ function PdfPageView({
|
|
|
353
353
|
});
|
|
354
354
|
|
|
355
355
|
if (hit) {
|
|
356
|
-
session?.client.browse.click(hit.annId,
|
|
356
|
+
session?.client.browse.click(hit.annId, hitRect);
|
|
357
357
|
setIsDrawing(false);
|
|
358
358
|
setSelection(null);
|
|
359
359
|
return;
|
|
@@ -515,7 +515,7 @@ function PdfPageView({
|
|
|
515
515
|
cursor: 'pointer',
|
|
516
516
|
opacity: isSelected ? 1 : isHovered ? 0.9 : 0.7
|
|
517
517
|
}}
|
|
518
|
-
onClick={(e) => session?.client.browse.click(r.annId,
|
|
518
|
+
onClick={(e) => session?.client.browse.click(r.annId, e.currentTarget.getBoundingClientRect())}
|
|
519
519
|
onMouseEnter={() => handleMouseEnter(r.annId)}
|
|
520
520
|
onMouseLeave={handleMouseLeave}
|
|
521
521
|
/>
|
|
@@ -685,7 +685,7 @@ export function PdfAnnotationCanvas({
|
|
|
685
685
|
|
|
686
686
|
const uri = resourceUri;
|
|
687
687
|
const outcome = session.client.browse.resourceAnchoredText(toResourceId(uri)).then(
|
|
688
|
-
(served) => (served &&
|
|
688
|
+
(served) => (served && served.kind === 'extracted' ? served : null),
|
|
689
689
|
() => {
|
|
690
690
|
if (resourceAnchoredRef.current?.uri === uri) resourceAnchoredRef.current = null;
|
|
691
691
|
return null;
|
|
@@ -861,6 +861,39 @@ export function PdfAnnotationCanvas({
|
|
|
861
861
|
|
|
862
862
|
const stripRef = useRef<HTMLDivElement>(null);
|
|
863
863
|
|
|
864
|
+
// The strip's CSS scrollport ceiling is 100vh — right when the nearest
|
|
865
|
+
// scroller is the window, a lie inside an inner-scrolled panel: the sticky
|
|
866
|
+
// strip then extends below the panel's clip, and `nearest` (below) considers
|
|
867
|
+
// a tick in that hidden band already in view, so the active page vanished
|
|
868
|
+
// whenever it crossed the strip's bottom (never the top, whose edges
|
|
869
|
+
// coincide). Size the scrollport to the scroller that actually clips it;
|
|
870
|
+
// with no such scroller the CSS ceiling stands.
|
|
871
|
+
useEffect(() => {
|
|
872
|
+
const strip = stripRef.current;
|
|
873
|
+
if (pageLayout !== 'scroll' || !strip) return;
|
|
874
|
+
let scroller: HTMLElement | null = strip.parentElement;
|
|
875
|
+
while (scroller) {
|
|
876
|
+
const overflowY = getComputedStyle(scroller).overflowY;
|
|
877
|
+
if (overflowY === 'auto' || overflowY === 'scroll') break;
|
|
878
|
+
scroller = scroller.parentElement;
|
|
879
|
+
}
|
|
880
|
+
if (!scroller) return;
|
|
881
|
+
const clip = scroller;
|
|
882
|
+
const size = () => { strip.style.maxHeight = `${clip.clientHeight}px`; };
|
|
883
|
+
size();
|
|
884
|
+
let observer: ResizeObserver | null = null;
|
|
885
|
+
try {
|
|
886
|
+
observer = new ResizeObserver(size);
|
|
887
|
+
observer.observe(clip);
|
|
888
|
+
} catch {
|
|
889
|
+
window.addEventListener('resize', size);
|
|
890
|
+
}
|
|
891
|
+
return () => {
|
|
892
|
+
if (observer) observer.disconnect();
|
|
893
|
+
else window.removeEventListener('resize', size);
|
|
894
|
+
};
|
|
895
|
+
}, [pageLayout, numPages, pageShape]);
|
|
896
|
+
|
|
864
897
|
useEffect(() => {
|
|
865
898
|
// `nearest` so following the reader never yanks the strip around; it
|
|
866
899
|
// scrolls only when the current rectangle would otherwise leave view.
|