@puredesktop/puredesktop-ui-bridge 1.0.0-beta.1
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/package.json +96 -0
- package/src/agents/runtime/agentLiveReducer.test.ts +72 -0
- package/src/agents/runtime/agentLiveReducer.ts +139 -0
- package/src/agents/runtime/agentRunEventTypes.ts +115 -0
- package/src/agents/runtime/contextWindowUsage.test.ts +38 -0
- package/src/agents/runtime/contextWindowUsage.ts +25 -0
- package/src/agents/runtime/index.ts +24 -0
- package/src/agents/runtime/mapAgentMessages.ts +54 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +71 -0
- package/src/agents/runtime/mergeUsageTurn.ts +23 -0
- package/src/agents/runtime/pendingToolCalls.test.ts +85 -0
- package/src/agents/runtime/pendingToolCalls.ts +14 -0
- package/src/agents/runtime/toolRequiresApproval.ts +20 -0
- package/src/agents/runtime/unresolvedToolCalls.ts +42 -0
- package/src/bridge/agentModelTiers.ts +50 -0
- package/src/bridge/agentToolHandlers.ts +23 -0
- package/src/bridge/agentToolHelpers.test.ts +27 -0
- package/src/bridge/agentToolHelpers.ts +20 -0
- package/src/bridge/agentTypes.ts +294 -0
- package/src/bridge/agents.d.mts +113 -0
- package/src/bridge/agents.mjs +233 -0
- package/src/bridge/agents.ts +62 -0
- package/src/bridge/appSettings.d.mts +9 -0
- package/src/bridge/appSettings.mjs +18 -0
- package/src/bridge/appSettings.ts +4 -0
- package/src/bridge/applyThemeFromHost.d.mts +7 -0
- package/src/bridge/applyThemeFromHost.mjs +33 -0
- package/src/bridge/assets.d.mts +66 -0
- package/src/bridge/assets.mjs +121 -0
- package/src/bridge/catalog.d.mts +5 -0
- package/src/bridge/catalog.mjs +24 -0
- package/src/bridge/catalog.ts +1 -0
- package/src/bridge/client.d.mts +19 -0
- package/src/bridge/client.mjs +147 -0
- package/src/bridge/collectionAssetAdapter.ts +63 -0
- package/src/bridge/collectionAssets.d.mts +1 -0
- package/src/bridge/collectionAssets.mjs +2 -0
- package/src/bridge/collectionAssets.ts +1 -0
- package/src/bridge/collectionDocumentHtml.test.ts +71 -0
- package/src/bridge/collectionDocumentHtml.ts +118 -0
- package/src/bridge/collectionImagePaste.test.ts +32 -0
- package/src/bridge/collectionImagePaste.ts +130 -0
- package/src/bridge/context.mjs +12 -0
- package/src/bridge/credentials.mjs +22 -0
- package/src/bridge/dialog.d.mts +5 -0
- package/src/bridge/dialog.mjs +13 -0
- package/src/bridge/events.d.mts +14 -0
- package/src/bridge/events.mjs +12 -0
- package/src/bridge/filePreviewKind.test.ts +43 -0
- package/src/bridge/filePreviewKind.ts +76 -0
- package/src/bridge/fs.d.mts +41 -0
- package/src/bridge/fs.mjs +82 -0
- package/src/bridge/fs.test.ts +16 -0
- package/src/bridge/fs.ts +10 -0
- package/src/bridge/index.ts +12 -0
- package/src/bridge/inlinePreview.d.mts +35 -0
- package/src/bridge/inlinePreview.mjs +99 -0
- package/src/bridge/inlinePreview.test.ts +56 -0
- package/src/bridge/inlinePreview.ts +13 -0
- package/src/bridge/methods.d.mts +115 -0
- package/src/bridge/methods.mjs +132 -0
- package/src/bridge/os.d.mts +11 -0
- package/src/bridge/os.mjs +26 -0
- package/src/bridge/plugins.d.mts +9 -0
- package/src/bridge/plugins.mjs +31 -0
- package/src/bridge/plugins.ts +5 -0
- package/src/bridge/preferences.d.mts +7 -0
- package/src/bridge/preferences.mjs +17 -0
- package/src/bridge/preferences.ts +13 -0
- package/src/bridge/printDocument.test.ts +47 -0
- package/src/bridge/printDocument.ts +75 -0
- package/src/bridge/pureRender/adapters.test.ts +144 -0
- package/src/bridge/pureRender/adapters.ts +175 -0
- package/src/bridge/pureRender/assets.test.ts +44 -0
- package/src/bridge/pureRender/base.css +76 -0
- package/src/bridge/pureRender/compiler.test.ts +279 -0
- package/src/bridge/pureRender/compiler.ts +660 -0
- package/src/bridge/pureRender/document.test.ts +283 -0
- package/src/bridge/pureRender/document.ts +35 -0
- package/src/bridge/pureRender/extraction.test.ts +432 -0
- package/src/bridge/pureRender/extraction.ts +1023 -0
- package/src/bridge/pureRender/flow.css +3 -0
- package/src/bridge/pureRender/index.ts +66 -0
- package/src/bridge/pureRender/normalize.test.ts +442 -0
- package/src/bridge/pureRender/normalize.ts +682 -0
- package/src/bridge/pureRender/presets.ts +365 -0
- package/src/bridge/pureRender/profiles/book.css +19 -0
- package/src/bridge/pureRender/profiles/manuscript.css +11 -0
- package/src/bridge/pureRender/profiles/writer.css +10 -0
- package/src/bridge/pureRender/reset.css +5 -0
- package/src/bridge/pureRender/samples.ts +94 -0
- package/src/bridge/pureRender/styles.ts +164 -0
- package/src/bridge/pureRender/theme.test.ts +53 -0
- package/src/bridge/pureRender/theme.ts +258 -0
- package/src/bridge/pureRender/tokens/default.css +36 -0
- package/src/bridge/pureRender/tokens/default.json +32 -0
- package/src/bridge/pureRender/types.ts +264 -0
- package/src/bridge/pureRender/visualLayoutInspection.test.ts +57 -0
- package/src/bridge/pureRender/visualLayoutInspection.ts +161 -0
- package/src/bridge/react/useDebouncedEffect.ts +21 -0
- package/src/bridge/react/useIpc.tsx +75 -0
- package/src/bridge/react/usePlatformAgentRunLiveState.tsx +78 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +729 -0
- package/src/bridge/react/usePlatformAgentSessions.tsx +60 -0
- package/src/bridge/react/usePlatformAgentTools.tsx +82 -0
- package/src/bridge/react/usePlatformAppManifest.tsx +32 -0
- package/src/bridge/react/usePlatformBridge.tsx +56 -0
- package/src/bridge/react/usePlatformDeepLink.tsx +57 -0
- package/src/bridge/react/usePlatformFileBinaryDataUrl.tsx +54 -0
- package/src/bridge/react/usePlatformFilePreview.tsx +92 -0
- package/src/bridge/react/usePlatformPreferences.tsx +77 -0
- package/src/bridge/react/usePlatformThemeDocumentAttrs.ts +35 -0
- package/src/bridge/react/usePlatformThemeFromBridge.tsx +16 -0
- package/src/bridge/react/usePlatformViewportResource.test.tsx +106 -0
- package/src/bridge/react/usePlatformViewportResource.tsx +72 -0
- package/src/bridge/react/useScheduledTask.tsx +59 -0
- package/src/bridge/render.d.mts +31 -0
- package/src/bridge/render.mjs +62 -0
- package/src/bridge/render.ts +8 -0
- package/src/bridge/screenAnnotation.test.ts +103 -0
- package/src/bridge/screenAnnotation.ts +269 -0
- package/src/bridge/theme.d.mts +3 -0
- package/src/bridge/theme.mjs +9 -0
- package/src/bridge/types.ts +439 -0
- package/src/bridge/watch.d.mts +9 -0
- package/src/bridge/watch.mjs +34 -0
- package/src/bridge/watch.ts +1 -0
- package/src/bridge/window.d.mts +44 -0
- package/src/bridge/window.mjs +33 -0
- package/src/chrome/index.ts +1 -0
- package/src/components/accessibility/AccessibilityReviewDialog.test.tsx +104 -0
- package/src/components/accessibility/AccessibilityReviewDialog.tsx +350 -0
- package/src/components/accessibility/accessibilityReview.test.ts +101 -0
- package/src/components/accessibility/accessibilityReview.ts +639 -0
- package/src/components/accessibility/index.ts +16 -0
- package/src/components/agent-feedback/ScreenAnnotationAgentDialog.tsx +509 -0
- package/src/components/agent-feedback/index.ts +5 -0
- package/src/components/agents/AgentComposer.tsx +34 -0
- package/src/components/agents/AgentContextUsageBar.tsx +127 -0
- package/src/components/agents/AgentDrawerPanel.tsx +82 -0
- package/src/components/agents/AgentMarkdown.tsx +12 -0
- package/src/components/agents/AgentMessageBubble.tsx +82 -0
- package/src/components/agents/AgentMessageList.tsx +47 -0
- package/src/components/agents/AgentModelMenu.tsx +55 -0
- package/src/components/agents/AgentThinkingBlock.tsx +66 -0
- package/src/components/agents/AgentToolApprovalActions.tsx +65 -0
- package/src/components/agents/AgentToolCallCard.tsx +26 -0
- package/src/components/agents/AgentToolPendingList.tsx +39 -0
- package/src/components/agents/AgentToolPendingReview.tsx +118 -0
- package/src/components/agents/agentPanelStyles.ts +79 -0
- package/src/components/agents/agentTypes.ts +96 -0
- package/src/components/agents/formatToolCallArguments.test.ts +16 -0
- package/src/components/agents/formatToolCallArguments.ts +9 -0
- package/src/components/agents/index.ts +40 -0
- package/src/components/assets/AssetLibraryDialog.tsx +5 -0
- package/src/components/assets/asset-library/AssetLibraryDialog.tsx +43 -0
- package/src/components/assets/asset-library/AssetLibraryWorkspace.tsx +54 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailActions.tsx +68 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.tsx +56 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailPane.tsx +90 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailPreview.tsx +67 -0
- package/src/components/assets/asset-library/detail/AssetLibraryInlinePreview.tsx +87 -0
- package/src/components/assets/asset-library/detail/AssetLibraryInlineSourceField.tsx +29 -0
- package/src/components/assets/asset-library/detail/AssetLibraryMetadataFields.tsx +34 -0
- package/src/components/assets/asset-library/detail/AssetLibraryUsagePanel.tsx +46 -0
- package/src/components/assets/asset-library/detail/AssetLibraryVersionsPanel.tsx +145 -0
- package/src/components/assets/asset-library/helpers/assetKind.ts +74 -0
- package/src/components/assets/asset-library/helpers.ts +41 -0
- package/src/components/assets/asset-library/hooks/useAssetDetailDraft.ts +81 -0
- package/src/components/assets/asset-library/hooks/useAssetLibraryDialog.ts +233 -0
- package/src/components/assets/asset-library/hooks/useAssetLibraryMutations.ts +266 -0
- package/src/components/assets/asset-library/hooks/useAssetLibrarySelectionSync.ts +24 -0
- package/src/components/assets/asset-library/hooks/useAssetLibraryVersions.ts +145 -0
- package/src/components/assets/asset-library/hooks/useInlineAssetPreview.ts +44 -0
- package/src/components/assets/asset-library/index.ts +19 -0
- package/src/components/assets/asset-library/layout/assetLibraryLayout.ts +201 -0
- package/src/components/assets/asset-library/shared/fieldStyles.ts +62 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryFilterPills.tsx +89 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +91 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryListRow.tsx +99 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryListStatus.tsx +24 -0
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebar.tsx +48 -0
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarActions.tsx +38 -0
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarToolbar.tsx +78 -0
- package/src/components/assets/asset-library/sidebar/AssetListThumbnail.tsx +86 -0
- package/src/components/assets/asset-library/types.ts +128 -0
- package/src/components/assets/asset-library/utils/assetLibraryUtils.ts +181 -0
- package/src/components/assets/asset-library/utils/pathBasename.ts +5 -0
- package/src/components/assets/asset-library/utils/sanitizeUploadFileName.ts +7 -0
- package/src/components/assets/asset-library/utils/uploadCollectionAsset.ts +52 -0
- package/src/components/chrome/OverlayActionCapsule.test.tsx +82 -0
- package/src/components/chrome/OverlayActionCapsule.tsx +90 -0
- package/src/components/chrome/PanelTabStrip.tsx +113 -0
- package/src/components/chrome/PlatformIcon.tsx +27 -0
- package/src/components/chrome/PureAppWordmark.tsx +89 -0
- package/src/components/chrome/ShellWindowControls.tsx +51 -0
- package/src/components/chrome/ThemeModeButton.tsx +16 -0
- package/src/components/chrome/Titlebar.tsx +79 -0
- package/src/components/chrome/WorkspaceTabStrip.tsx +300 -0
- package/src/components/chrome/index.ts +31 -0
- package/src/components/chrome/workspaceTabTypes.ts +29 -0
- package/src/components/common/buttons/Button.tsx +241 -0
- package/src/components/common/buttons/ChoiceChip.tsx +73 -0
- package/src/components/common/buttons/CleanButton.tsx +34 -0
- package/src/components/common/buttons/IconButton.tsx +89 -0
- package/src/components/common/buttons/QuietAction.tsx +29 -0
- package/src/components/common/buttons/SegmentedControl.tsx +67 -0
- package/src/components/common/buttons/TextActionButton.tsx +55 -0
- package/src/components/common/buttons/TextLink.tsx +38 -0
- package/src/components/common/canvas/CanvasElementFrame.test.tsx +79 -0
- package/src/components/common/canvas/CanvasElementFrame.tsx +182 -0
- package/src/components/common/canvas/canvasGeometry.test.ts +66 -0
- package/src/components/common/canvas/canvasGeometry.ts +133 -0
- package/src/components/common/canvas/index.ts +27 -0
- package/src/components/common/canvas/useCanvasDragResize.test.tsx +152 -0
- package/src/components/common/canvas/useCanvasDragResize.ts +161 -0
- package/src/components/common/chat/ChatAssistantMessage.tsx +81 -0
- package/src/components/common/chat/ChatBox.tsx +189 -0
- package/src/components/common/chat/ChatMarkdown.tsx +109 -0
- package/src/components/common/chat/ChatModelMenu.tsx +258 -0
- package/src/components/common/chat/ChatStatusRow.tsx +24 -0
- package/src/components/common/chat/ChatThinkingIndicator.tsx +54 -0
- package/src/components/common/chat/ChatThread.tsx +59 -0
- package/src/components/common/chat/ChatToolRow.tsx +141 -0
- package/src/components/common/chat/ChatUserBubble.tsx +45 -0
- package/src/components/common/chat/chatUtils.ts +7 -0
- package/src/components/common/chat/index.ts +24 -0
- package/src/components/common/connections/ConnectionSetup.tsx +529 -0
- package/src/components/common/connections/index.ts +26 -0
- package/src/components/common/containers/AppFrame.test.tsx +47 -0
- package/src/components/common/containers/AppFrame.tsx +65 -0
- package/src/components/common/containers/AppHeader.tsx +60 -0
- package/src/components/common/containers/AutoFillGrid.tsx +37 -0
- package/src/components/common/containers/Collapsible.tsx +43 -0
- package/src/components/common/containers/CollapsibleGrid.tsx +174 -0
- package/src/components/common/containers/MotionScope.tsx +32 -0
- package/src/components/common/containers/PageHeader.tsx +35 -0
- package/src/components/common/containers/PanelToolbarHeader.tsx +113 -0
- package/src/components/common/containers/QuietDisclosure.tsx +91 -0
- package/src/components/common/containers/QuietSurface.tsx +41 -0
- package/src/components/common/containers/ScrollPanel.tsx +33 -0
- package/src/components/common/containers/StatusBar.tsx +42 -0
- package/src/components/common/containers/SurfacePanel.tsx +66 -0
- package/src/components/common/containers/ToolbarStrip.tsx +80 -0
- package/src/components/common/containers/flex.ts +117 -0
- package/src/components/common/containers/useOpenTransition.ts +33 -0
- package/src/components/common/context/ContextSelector.tsx +269 -0
- package/src/components/common/context/index.ts +7 -0
- package/src/components/common/desk/ActionReader.tsx +270 -0
- package/src/components/common/desk/ActivityLedger.tsx +143 -0
- package/src/components/common/desk/CreationDesk.tsx +139 -0
- package/src/components/common/desk/DailyBrief.tsx +160 -0
- package/src/components/common/desk/DeskCard.tsx +157 -0
- package/src/components/common/desk/index.ts +30 -0
- package/src/components/common/dropdown/ContextMenu.tsx +87 -0
- package/src/components/common/dropdown/MenuButtonDropdown.tsx +121 -0
- package/src/components/common/dropdown/MenuDropdown.tsx +63 -0
- package/src/components/common/dropdown/MenuDropdownEntry.tsx +51 -0
- package/src/components/common/dropdown/MenuDropdownItem.tsx +82 -0
- package/src/components/common/dropdown/MenuDropdownSubmenu.tsx +145 -0
- package/src/components/common/dropdown/MenuPanel.tsx +69 -0
- package/src/components/common/dropdown/MenuPortal.tsx +162 -0
- package/src/components/common/dropdown/MenuPortalLayer.tsx +6 -0
- package/src/components/common/dropdown/SelectMenu.tsx +243 -0
- package/src/components/common/dropdown/SelectMenuControl.tsx +23 -0
- package/src/components/common/dropdown/SelectMenuOptions.tsx +38 -0
- package/src/components/common/dropdown/menuPortalConstants.ts +11 -0
- package/src/components/common/dropdown/menuTypes.ts +71 -0
- package/src/components/common/dropdown/menuViewportPosition.test.ts +139 -0
- package/src/components/common/dropdown/menuViewportPosition.ts +122 -0
- package/src/components/common/dropdown/selectMenuLayout.test.ts +42 -0
- package/src/components/common/dropdown/selectMenuLayout.ts +123 -0
- package/src/components/common/dropdown/useMenuAnchorPosition.ts +109 -0
- package/src/components/common/dropdown/useSelectMenuAnchor.ts +30 -0
- package/src/components/common/evidence/StructuredEvidenceView.test.tsx +77 -0
- package/src/components/common/evidence/StructuredEvidenceView.tsx +312 -0
- package/src/components/common/evidence/index.ts +13 -0
- package/src/components/common/feedback/Badge.tsx +33 -0
- package/src/components/common/feedback/CapabilityPills.tsx +36 -0
- package/src/components/common/feedback/EmptyState.tsx +132 -0
- package/src/components/common/feedback/InfoHint.tsx +67 -0
- package/src/components/common/feedback/InlineBanner.tsx +82 -0
- package/src/components/common/feedback/PlatformChip.tsx +104 -0
- package/src/components/common/feedback/QuietPill.tsx +75 -0
- package/src/components/common/feedback/WorkProgressPanel.tsx +156 -0
- package/src/components/common/feedback/index.ts +33 -0
- package/src/components/common/inputs/BareInput.tsx +31 -0
- package/src/components/common/inputs/BareTextarea.tsx +10 -0
- package/src/components/common/inputs/Checkbox.tsx +13 -0
- package/src/components/common/inputs/FieldControlShell.tsx +49 -0
- package/src/components/common/inputs/FormField.tsx +40 -0
- package/src/components/common/inputs/HybridSearchField.tsx +280 -0
- package/src/components/common/inputs/InlineComposer.tsx +197 -0
- package/src/components/common/inputs/PaletteCirclePicker.tsx +26 -0
- package/src/components/common/inputs/PaletteDropdown.tsx +413 -0
- package/src/components/common/inputs/Radio.tsx +19 -0
- package/src/components/common/inputs/SearchField.tsx +128 -0
- package/src/components/common/inputs/SelectField.tsx +169 -0
- package/src/components/common/inputs/SurfacePalettePicker.tsx +54 -0
- package/src/components/common/inputs/TextAreaField.tsx +22 -0
- package/src/components/common/inputs/TextField.tsx +51 -0
- package/src/components/common/inputs/ToggleSwitch.tsx +110 -0
- package/src/components/common/inputs/WorkspacePicker.tsx +349 -0
- package/src/components/common/inputs/fieldStyles.ts +137 -0
- package/src/components/common/layout/PanelGroup.tsx +479 -0
- package/src/components/common/layout/index.ts +31 -0
- package/src/components/common/layout/panelLayoutConstants.ts +7 -0
- package/src/components/common/layout/panelLayoutPersistence.ts +35 -0
- package/src/components/common/layout/panelLayoutState.test.ts +38 -0
- package/src/components/common/layout/panelLayoutState.ts +174 -0
- package/src/components/common/layout/panelLayoutTypes.ts +19 -0
- package/src/components/common/layout/tabs/Tabs.tsx +136 -0
- package/src/components/common/lists/listRowStyles.ts +32 -0
- package/src/components/common/lists/quiet-ledger/QuietActivityList.tsx +64 -0
- package/src/components/common/lists/quiet-ledger/QuietLedgerRow.tsx +119 -0
- package/src/components/common/lists/quiet-ledger/index.ts +9 -0
- package/src/components/common/lists/reorderable/DragHandle.tsx +65 -0
- package/src/components/common/lists/reorderable/ReorderableList.tsx +280 -0
- package/src/components/common/lists/reorderable/ReorderableListRow.tsx +102 -0
- package/src/components/common/lists/reorderable/ReorderableSections.tsx +344 -0
- package/src/components/common/lists/reorderable/ReorderableSectionsDemo.tsx +94 -0
- package/src/components/common/lists/reorderable/index.ts +15 -0
- package/src/components/common/lists/reorderable/reorderableUtils.test.ts +28 -0
- package/src/components/common/lists/reorderable/reorderableUtils.ts +23 -0
- package/src/components/common/lists/reorderable/types.ts +40 -0
- package/src/components/common/lists/reorderable/useReorderableDragState.ts +49 -0
- package/src/components/common/media/ImageReferencePicker.tsx +242 -0
- package/src/components/common/menubar/MenuBar.tsx +78 -0
- package/src/components/common/menubar/MenuBarTrigger.tsx +70 -0
- package/src/components/common/narrative/NarrativeMention.tsx +74 -0
- package/src/components/common/navigation/PathBreadcrumbs.tsx +78 -0
- package/src/components/common/navigation/UnderlineTabs.tsx +83 -0
- package/src/components/common/overlays/InstallAppPromptDialog.tsx +59 -0
- package/src/components/common/overlays/Modal.tsx +285 -0
- package/src/components/common/overlays/ModalChromeHeader.tsx +105 -0
- package/src/components/common/overlays/ObjectActionStrip.tsx +80 -0
- package/src/components/common/people/AutoUpdateReviewList.tsx +182 -0
- package/src/components/common/people/EntityList.tsx +122 -0
- package/src/components/common/people/EntityPortrait.tsx +64 -0
- package/src/components/common/people/LinkedDossierList.tsx +99 -0
- package/src/components/common/people/OpportunityList.tsx +94 -0
- package/src/components/common/people/PeopleSourceList.tsx +145 -0
- package/src/components/common/people/RelationshipDossier.tsx +168 -0
- package/src/components/common/people/RelationshipMap.tsx +362 -0
- package/src/components/common/people/index.ts +40 -0
- package/src/components/common/preview/FileAudioPreview.tsx +89 -0
- package/src/components/common/preview/FileImagePreview.tsx +88 -0
- package/src/components/common/preview/FilePdfPreview.tsx +87 -0
- package/src/components/common/preview/FileTextPreview.tsx +96 -0
- package/src/components/common/preview/FileVideoPreview.tsx +87 -0
- package/src/components/common/preview/PlatformFilePreview.tsx +166 -0
- package/src/components/common/preview/previewLayout.ts +27 -0
- package/src/components/common/render/Each.tsx +42 -0
- package/src/components/common/research/EvidenceDossier.tsx +894 -0
- package/src/components/common/research/index.ts +7 -0
- package/src/components/common/tiles/CatalogCard.tsx +116 -0
- package/src/components/common/tiles/Tile.tsx +45 -0
- package/src/components/common/tiles/TileGrid.tsx +13 -0
- package/src/components/common/typography/Heading.tsx +27 -0
- package/src/components/common/typography/Kicker.tsx +30 -0
- package/src/components/common/typography/Text.tsx +80 -0
- package/src/components/credentials/CredentialsPanel.tsx +169 -0
- package/src/components/credentials/credentialsTypes.ts +14 -0
- package/src/components/data/DataChart.tsx +913 -0
- package/src/components/data/dataTable.ts +99 -0
- package/src/components/editor/EditorCommentPromptDialog.tsx +124 -0
- package/src/components/editor/EditorLinkPromptDialog.tsx +65 -0
- package/src/components/print-design/PrintDesignPanel.test.tsx +1446 -0
- package/src/components/print-design/PrintDesignPanel.tsx +3043 -0
- package/src/components/print-design/index.ts +9 -0
- package/src/components/proofing/PagedProofDialog.tsx +453 -0
- package/src/components/proofing/index.ts +4 -0
- package/src/editor/DocumentEditor.tsx +28 -0
- package/src/editor/EditorViewModeToggle.tsx +35 -0
- package/src/editor/index.ts +97 -0
- package/src/editor/toolbarOverlays.ts +8 -0
- package/src/index.ts +209 -0
- package/src/theme/ThemeProvider.tsx +113 -0
- package/src/theme/appAccents.ts +87 -0
- package/src/theme/buildThemeBridgePayload.ts +23 -0
- package/src/theme/composePlatformTheme.test.ts +30 -0
- package/src/theme/composePlatformTheme.ts +23 -0
- package/src/theme/platformFont.d.mts +7 -0
- package/src/theme/platformFont.mjs +20 -0
- package/src/theme/platformFont.ts +6 -0
- package/src/theme/reportsShellChromeSelectors.ts +136 -0
- package/src/theme/styled.d.ts +5 -0
- package/src/theme/surfacePalettes.ts +178 -0
- package/src/theme/surfaceRoleSelectors.ts +113 -0
- package/src/theme/themeVars.ts +13 -0
- package/src/theme/themes/dark.ts +59 -0
- package/src/theme/themes/light.ts +60 -0
- package/src/theme/themes/palette.ts +44 -0
- package/src/theme/themes/shared.ts +68 -0
- package/src/theme/tokens.ts +114 -0
- package/src/theme/utils/GlobalStyles.ts +88 -0
- package/src/theme/utils/appFrameDocumentStyles.d.mts +5 -0
- package/src/theme/utils/appFrameDocumentStyles.mjs +89 -0
- package/src/theme/utils/appFrameDocumentStyles.ts +5 -0
- package/src/theme/utils/applyTheme.ts +50 -0
- package/src/theme/utils/cssVariables.ts +49 -0
- package/src/theme/utils/useInjectThemeIntoIframe.ts +40 -0
- package/src/utils/extensionIconMap.ts +123 -0
- package/src/utils/generalUtils.ts +157 -0
- package/src/writing/DraftAssistantDialog.tsx +279 -0
- package/src/writing/WritingStyleManager.tsx +416 -0
- package/src/writing/condenseWritingStyle.ts +103 -0
- package/src/writing/draftTextWithAgent.ts +134 -0
- package/src/writing/index.ts +39 -0
- package/src/writing/styleStore.ts +32 -0
- package/src/writing/writingStyle.test.ts +122 -0
- package/src/writing/writingStyle.ts +217 -0
|
@@ -0,0 +1,639 @@
|
|
|
1
|
+
export type AccessibilityIssueSeverity = 'error' | 'warning' | 'notice'
|
|
2
|
+
|
|
3
|
+
export type AccessibilityIssueCategory =
|
|
4
|
+
| 'content'
|
|
5
|
+
| 'headings'
|
|
6
|
+
| 'images'
|
|
7
|
+
| 'captions'
|
|
8
|
+
| 'tables'
|
|
9
|
+
| 'links'
|
|
10
|
+
| 'media'
|
|
11
|
+
| 'structure'
|
|
12
|
+
|
|
13
|
+
export interface AccessibilityIssueFix {
|
|
14
|
+
label: string
|
|
15
|
+
description: string
|
|
16
|
+
action:
|
|
17
|
+
| 'set-empty-alt'
|
|
18
|
+
| 'use-caption-as-alt'
|
|
19
|
+
| 'add-figure-caption'
|
|
20
|
+
| 'add-table-caption'
|
|
21
|
+
| 'promote-first-row-headers'
|
|
22
|
+
| 'add-video-captions-track'
|
|
23
|
+
| 'remove-empty-heading'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface AccessibilityIssue {
|
|
27
|
+
id: string
|
|
28
|
+
ruleId: string
|
|
29
|
+
category: AccessibilityIssueCategory
|
|
30
|
+
severity: AccessibilityIssueSeverity
|
|
31
|
+
title: string
|
|
32
|
+
detail: string
|
|
33
|
+
target: string
|
|
34
|
+
targetLabel: string
|
|
35
|
+
wcag: string[]
|
|
36
|
+
standard: string
|
|
37
|
+
fix?: AccessibilityIssueFix
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface AccessibilityIssueDraft {
|
|
41
|
+
label: string
|
|
42
|
+
description: string
|
|
43
|
+
value: string
|
|
44
|
+
applyLabel: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ISSUE_AUTHOR = 'Pure accessibility review'
|
|
48
|
+
|
|
49
|
+
function parseHtml(html: string): Document {
|
|
50
|
+
return new DOMParser().parseFromString(`<main>${html}</main>`, 'text/html')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function serializeHtml(document: Document): string {
|
|
54
|
+
return document.body.querySelector('main')?.innerHTML ?? ''
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function textOf(element: Element | null | undefined): string {
|
|
58
|
+
return (element?.textContent ?? '').replace(/\s+/g, ' ').trim()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function elementLabel(element: Element): string {
|
|
62
|
+
const tag = element.tagName.toLowerCase()
|
|
63
|
+
if (tag === 'img') {
|
|
64
|
+
return element.getAttribute('alt')?.trim() || 'image'
|
|
65
|
+
}
|
|
66
|
+
if (tag === 'table') {
|
|
67
|
+
return textOf(element.querySelector('caption')) || 'table'
|
|
68
|
+
}
|
|
69
|
+
return textOf(element).slice(0, 80) || tag
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function nthOfType(element: Element): number {
|
|
73
|
+
let index = 1
|
|
74
|
+
let sibling = element.previousElementSibling
|
|
75
|
+
while (sibling) {
|
|
76
|
+
if (sibling.tagName === element.tagName) index += 1
|
|
77
|
+
sibling = sibling.previousElementSibling
|
|
78
|
+
}
|
|
79
|
+
return index
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function selectorFor(element: Element): string {
|
|
83
|
+
const parts: string[] = []
|
|
84
|
+
let current: Element | null = element
|
|
85
|
+
while (current && current.tagName.toLowerCase() !== 'main') {
|
|
86
|
+
const tag = current.tagName.toLowerCase()
|
|
87
|
+
parts.unshift(`${tag}:nth-of-type(${nthOfType(current)})`)
|
|
88
|
+
current = current.parentElement
|
|
89
|
+
}
|
|
90
|
+
return parts.join(' > ')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function issueId(ruleId: string, target: string): string {
|
|
94
|
+
return `${ruleId}:${target}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function pushIssue(
|
|
98
|
+
issues: AccessibilityIssue[],
|
|
99
|
+
input: Omit<AccessibilityIssue, 'id' | 'standard'>,
|
|
100
|
+
): void {
|
|
101
|
+
issues.push({
|
|
102
|
+
...input,
|
|
103
|
+
id: issueId(input.ruleId, input.target),
|
|
104
|
+
standard: 'WCAG 2.2',
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function closestCaptionText(element: Element): string {
|
|
109
|
+
const figure = element.closest('figure')
|
|
110
|
+
return textOf(figure?.querySelector('figcaption'))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function clipDraft(input: string, maxLength = 140): string {
|
|
114
|
+
const text = input.replace(/\s+/g, ' ').trim()
|
|
115
|
+
if (text.length <= maxLength) return text
|
|
116
|
+
return `${text.slice(0, maxLength - 1).trim()}…`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function humanizePathPart(value: string): string {
|
|
120
|
+
const withoutQuery = value.split(/[?#]/)[0] ?? ''
|
|
121
|
+
const lastPart = withoutQuery.split('/').filter(Boolean).pop() ?? ''
|
|
122
|
+
const withoutExtension = lastPart.replace(/\.[a-z0-9]+$/i, '')
|
|
123
|
+
return withoutExtension
|
|
124
|
+
.replace(/[-_]+/g, ' ')
|
|
125
|
+
.replace(/\s+/g, ' ')
|
|
126
|
+
.trim()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readableLinkDestination(href: string): string {
|
|
130
|
+
try {
|
|
131
|
+
const url = new URL(href, 'https://example.test')
|
|
132
|
+
const pathLabel = humanizePathPart(url.pathname)
|
|
133
|
+
if (pathLabel) return clipDraft(pathLabel, 80)
|
|
134
|
+
return url.hostname.replace(/^www\./, '')
|
|
135
|
+
} catch {
|
|
136
|
+
return clipDraft(humanizePathPart(href) || href, 80)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function nearbySentence(element: Element): string {
|
|
141
|
+
const candidates = [
|
|
142
|
+
element.previousElementSibling,
|
|
143
|
+
element.nextElementSibling,
|
|
144
|
+
element.parentElement?.previousElementSibling,
|
|
145
|
+
element.parentElement?.nextElementSibling,
|
|
146
|
+
element.closest('p'),
|
|
147
|
+
]
|
|
148
|
+
for (const candidate of candidates) {
|
|
149
|
+
const text = textOf(candidate)
|
|
150
|
+
if (text) return clipDraft(text)
|
|
151
|
+
}
|
|
152
|
+
return ''
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function tableCaptionDraft(table: Element): string {
|
|
156
|
+
const headers = Array.from(table.querySelectorAll('th'))
|
|
157
|
+
.map(header => textOf(header))
|
|
158
|
+
.filter(Boolean)
|
|
159
|
+
if (headers.length > 0) {
|
|
160
|
+
return clipDraft(`Table of ${headers.slice(0, 4).join(', ')}`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const firstRowCells = Array.from(table.querySelectorAll('tr:first-child > *'))
|
|
164
|
+
.map(cell => textOf(cell))
|
|
165
|
+
.filter(Boolean)
|
|
166
|
+
if (firstRowCells.length > 0) {
|
|
167
|
+
return clipDraft(`Table of ${firstRowCells.slice(0, 4).join(', ')}`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return nearbySentence(table) || 'Table summary'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function imageAltDraft(image: Element): string {
|
|
174
|
+
const caption = closestCaptionText(image)
|
|
175
|
+
if (caption) return clipDraft(caption)
|
|
176
|
+
|
|
177
|
+
const src = image.getAttribute('src') ?? ''
|
|
178
|
+
const sourceLabel = humanizePathPart(src)
|
|
179
|
+
if (sourceLabel) return clipDraft(sourceLabel, 100)
|
|
180
|
+
|
|
181
|
+
return nearbySentence(image) || 'Image description'
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function figureCaptionDraft(figure: Element): string {
|
|
185
|
+
const image = figure.querySelector('img')
|
|
186
|
+
const alt = image?.getAttribute('alt')?.trim()
|
|
187
|
+
if (alt) return clipDraft(alt)
|
|
188
|
+
|
|
189
|
+
const sourceLabel = image ? humanizePathPart(image.getAttribute('src') ?? '') : ''
|
|
190
|
+
if (sourceLabel) return clipDraft(sourceLabel, 100)
|
|
191
|
+
|
|
192
|
+
return nearbySentence(figure) || 'Figure summary'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function hasAccessibleName(element: Element): boolean {
|
|
196
|
+
return Boolean(
|
|
197
|
+
element.getAttribute('aria-label')?.trim() ||
|
|
198
|
+
element.getAttribute('aria-labelledby')?.trim() ||
|
|
199
|
+
textOf(element),
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function auditHtmlAccessibility(html: string): AccessibilityIssue[] {
|
|
204
|
+
const document = parseHtml(html)
|
|
205
|
+
const main = document.body.querySelector('main')
|
|
206
|
+
if (!main) return []
|
|
207
|
+
|
|
208
|
+
const issues: AccessibilityIssue[] = []
|
|
209
|
+
|
|
210
|
+
const headings = Array.from(
|
|
211
|
+
main.querySelectorAll('h1,h2,h3,h4,h5,h6'),
|
|
212
|
+
) as HTMLElement[]
|
|
213
|
+
let previousLevel = 0
|
|
214
|
+
for (const heading of headings) {
|
|
215
|
+
const target = selectorFor(heading)
|
|
216
|
+
const level = Number(heading.tagName.slice(1))
|
|
217
|
+
if (!textOf(heading)) {
|
|
218
|
+
pushIssue(issues, {
|
|
219
|
+
ruleId: 'empty-heading',
|
|
220
|
+
category: 'headings',
|
|
221
|
+
severity: 'error',
|
|
222
|
+
title: 'Empty heading',
|
|
223
|
+
detail:
|
|
224
|
+
'Headings need visible text so readers and assistive technology can understand the structure.',
|
|
225
|
+
target,
|
|
226
|
+
targetLabel: heading.tagName.toLowerCase(),
|
|
227
|
+
wcag: ['1.3.1 Info and Relationships', '2.4.6 Headings and Labels'],
|
|
228
|
+
fix: {
|
|
229
|
+
label: 'Remove heading',
|
|
230
|
+
description: 'Remove the empty heading element.',
|
|
231
|
+
action: 'remove-empty-heading',
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
}
|
|
235
|
+
if (previousLevel > 0 && level > previousLevel + 1) {
|
|
236
|
+
pushIssue(issues, {
|
|
237
|
+
ruleId: 'heading-level-jump',
|
|
238
|
+
category: 'headings',
|
|
239
|
+
severity: 'warning',
|
|
240
|
+
title: 'Heading level jumps',
|
|
241
|
+
detail: `This heading moves from h${previousLevel} to h${level}. Check whether the outline skips a section.`,
|
|
242
|
+
target,
|
|
243
|
+
targetLabel: textOf(heading) || heading.tagName.toLowerCase(),
|
|
244
|
+
wcag: ['1.3.1 Info and Relationships', '2.4.6 Headings and Labels'],
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
if (textOf(heading)) previousLevel = level
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const image of Array.from(main.querySelectorAll('img'))) {
|
|
251
|
+
const target = selectorFor(image)
|
|
252
|
+
const hasAlt = image.hasAttribute('alt')
|
|
253
|
+
const alt = image.getAttribute('alt')?.trim() ?? ''
|
|
254
|
+
const caption = closestCaptionText(image)
|
|
255
|
+
if (!hasAlt) {
|
|
256
|
+
pushIssue(issues, {
|
|
257
|
+
ruleId: 'image-missing-alt',
|
|
258
|
+
category: 'images',
|
|
259
|
+
severity: 'error',
|
|
260
|
+
title: 'Image needs alt text or a decorative mark',
|
|
261
|
+
detail:
|
|
262
|
+
'Images need an accessible text alternative, unless they are purely decorative.',
|
|
263
|
+
target,
|
|
264
|
+
targetLabel: elementLabel(image),
|
|
265
|
+
wcag: ['1.1.1 Non-text Content'],
|
|
266
|
+
fix: {
|
|
267
|
+
label: 'Mark decorative',
|
|
268
|
+
description:
|
|
269
|
+
'Set alt="" so assistive technology can ignore the image.',
|
|
270
|
+
action: 'set-empty-alt',
|
|
271
|
+
},
|
|
272
|
+
})
|
|
273
|
+
} else if (alt && caption && alt.toLowerCase() === caption.toLowerCase()) {
|
|
274
|
+
pushIssue(issues, {
|
|
275
|
+
ruleId: 'image-alt-duplicates-caption',
|
|
276
|
+
category: 'images',
|
|
277
|
+
severity: 'notice',
|
|
278
|
+
title: 'Alt text repeats the caption',
|
|
279
|
+
detail:
|
|
280
|
+
'If the caption already explains the figure, the alt text may need a shorter functional description.',
|
|
281
|
+
target,
|
|
282
|
+
targetLabel: alt,
|
|
283
|
+
wcag: ['1.1.1 Non-text Content'],
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (const figure of Array.from(main.querySelectorAll('figure'))) {
|
|
289
|
+
if (!figure.querySelector('img,svg,canvas,pre[data-type="mermaid"]')) {
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
const target = selectorFor(figure)
|
|
293
|
+
if (!textOf(figure.querySelector('figcaption'))) {
|
|
294
|
+
pushIssue(issues, {
|
|
295
|
+
ruleId: 'figure-missing-caption',
|
|
296
|
+
category: 'captions',
|
|
297
|
+
severity: 'warning',
|
|
298
|
+
title: 'Figure needs a caption',
|
|
299
|
+
detail:
|
|
300
|
+
'Figures should have captions when the image contributes to the argument or instructions.',
|
|
301
|
+
target,
|
|
302
|
+
targetLabel: elementLabel(figure),
|
|
303
|
+
wcag: ['1.1.1 Non-text Content', '1.3.1 Info and Relationships'],
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for (const table of Array.from(main.querySelectorAll('table'))) {
|
|
309
|
+
const target = selectorFor(table)
|
|
310
|
+
if (!textOf(table.querySelector('caption'))) {
|
|
311
|
+
pushIssue(issues, {
|
|
312
|
+
ruleId: 'table-missing-caption',
|
|
313
|
+
category: 'tables',
|
|
314
|
+
severity: 'warning',
|
|
315
|
+
title: 'Table needs a caption',
|
|
316
|
+
detail:
|
|
317
|
+
'A caption gives the table a clear accessible name and helps readers understand its purpose.',
|
|
318
|
+
target,
|
|
319
|
+
targetLabel: elementLabel(table),
|
|
320
|
+
wcag: ['1.3.1 Info and Relationships'],
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
if (!table.querySelector('th')) {
|
|
324
|
+
pushIssue(issues, {
|
|
325
|
+
ruleId: 'table-no-headers',
|
|
326
|
+
category: 'tables',
|
|
327
|
+
severity: 'error',
|
|
328
|
+
title: 'Table has no header cells',
|
|
329
|
+
detail:
|
|
330
|
+
'Tables need header cells so row and column relationships are available to assistive technology.',
|
|
331
|
+
target,
|
|
332
|
+
targetLabel: elementLabel(table),
|
|
333
|
+
wcag: ['1.3.1 Info and Relationships'],
|
|
334
|
+
fix: {
|
|
335
|
+
label: 'Use first row as headers',
|
|
336
|
+
description: 'Convert the first row cells to column headers.',
|
|
337
|
+
action: 'promote-first-row-headers',
|
|
338
|
+
},
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
for (const link of Array.from(main.querySelectorAll('a'))) {
|
|
344
|
+
const target = selectorFor(link)
|
|
345
|
+
const label = textOf(link)
|
|
346
|
+
if (!link.getAttribute('href')?.trim()) {
|
|
347
|
+
pushIssue(issues, {
|
|
348
|
+
ruleId: 'link-missing-target',
|
|
349
|
+
category: 'links',
|
|
350
|
+
severity: 'error',
|
|
351
|
+
title: 'Link has no destination',
|
|
352
|
+
detail: 'Links need a destination or should be plain text.',
|
|
353
|
+
target,
|
|
354
|
+
targetLabel: label || 'link',
|
|
355
|
+
wcag: ['2.4.4 Link Purpose (In Context)'],
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
if (!hasAccessibleName(link)) {
|
|
359
|
+
pushIssue(issues, {
|
|
360
|
+
ruleId: 'link-empty-label',
|
|
361
|
+
category: 'links',
|
|
362
|
+
severity: 'error',
|
|
363
|
+
title: 'Link has no readable text',
|
|
364
|
+
detail:
|
|
365
|
+
'The link needs text or an accessible label that explains where it goes.',
|
|
366
|
+
target,
|
|
367
|
+
targetLabel: 'link',
|
|
368
|
+
wcag: ['2.4.4 Link Purpose (In Context)', '4.1.2 Name, Role, Value'],
|
|
369
|
+
})
|
|
370
|
+
} else if (/^(click here|here|read more|more)$/i.test(label)) {
|
|
371
|
+
pushIssue(issues, {
|
|
372
|
+
ruleId: 'link-ambiguous-label',
|
|
373
|
+
category: 'links',
|
|
374
|
+
severity: 'warning',
|
|
375
|
+
title: 'Link text is ambiguous',
|
|
376
|
+
detail:
|
|
377
|
+
'Link text should make sense out of context, especially in screen reader link lists.',
|
|
378
|
+
target,
|
|
379
|
+
targetLabel: label,
|
|
380
|
+
wcag: ['2.4.4 Link Purpose (In Context)'],
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const media of Array.from(main.querySelectorAll('video,audio'))) {
|
|
386
|
+
const target = selectorFor(media)
|
|
387
|
+
if (!media.querySelector('track[kind="captions"],track[kind="subtitles"]')) {
|
|
388
|
+
pushIssue(issues, {
|
|
389
|
+
ruleId: 'media-missing-captions',
|
|
390
|
+
category: 'media',
|
|
391
|
+
severity: 'warning',
|
|
392
|
+
title: 'Media needs captions or a transcript',
|
|
393
|
+
detail:
|
|
394
|
+
'Audio and video should include captions, subtitles, or a nearby transcript.',
|
|
395
|
+
target,
|
|
396
|
+
targetLabel: media.tagName.toLowerCase(),
|
|
397
|
+
wcag: ['1.2.1 Audio-only and Video-only', '1.2.2 Captions'],
|
|
398
|
+
fix:
|
|
399
|
+
media.tagName.toLowerCase() === 'video'
|
|
400
|
+
? {
|
|
401
|
+
label: 'Add captions track placeholder',
|
|
402
|
+
description: 'Insert a captions track placeholder.',
|
|
403
|
+
action: 'add-video-captions-track',
|
|
404
|
+
}
|
|
405
|
+
: undefined,
|
|
406
|
+
})
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
for (const iframe of Array.from(main.querySelectorAll('iframe'))) {
|
|
411
|
+
const target = selectorFor(iframe)
|
|
412
|
+
if (!iframe.getAttribute('title')?.trim()) {
|
|
413
|
+
pushIssue(issues, {
|
|
414
|
+
ruleId: 'frame-missing-title',
|
|
415
|
+
category: 'structure',
|
|
416
|
+
severity: 'error',
|
|
417
|
+
title: 'Embedded frame needs a title',
|
|
418
|
+
detail:
|
|
419
|
+
'Frames need titles so assistive technology can identify embedded content.',
|
|
420
|
+
target,
|
|
421
|
+
targetLabel: 'iframe',
|
|
422
|
+
wcag: ['4.1.2 Name, Role, Value'],
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return issues
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function findTarget(document: Document, issue: AccessibilityIssue): Element | null {
|
|
431
|
+
const main = document.body.querySelector('main')
|
|
432
|
+
return issue.target ? main?.querySelector(issue.target) ?? null : null
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function applyAccessibilityIssueFix(
|
|
436
|
+
html: string,
|
|
437
|
+
issue: AccessibilityIssue,
|
|
438
|
+
): string {
|
|
439
|
+
if (!issue.fix) return html
|
|
440
|
+
const document = parseHtml(html)
|
|
441
|
+
const target = findTarget(document, issue)
|
|
442
|
+
if (!target) return html
|
|
443
|
+
|
|
444
|
+
switch (issue.fix.action) {
|
|
445
|
+
case 'set-empty-alt':
|
|
446
|
+
if (target instanceof HTMLImageElement) target.alt = ''
|
|
447
|
+
else target.setAttribute('alt', '')
|
|
448
|
+
break
|
|
449
|
+
case 'use-caption-as-alt': {
|
|
450
|
+
const caption = closestCaptionText(target)
|
|
451
|
+
if (caption) target.setAttribute('alt', caption)
|
|
452
|
+
break
|
|
453
|
+
}
|
|
454
|
+
case 'add-figure-caption': {
|
|
455
|
+
if (!target.querySelector('figcaption')) {
|
|
456
|
+
const caption = document.createElement('figcaption')
|
|
457
|
+
caption.textContent = 'Describe this figure.'
|
|
458
|
+
target.append(caption)
|
|
459
|
+
}
|
|
460
|
+
break
|
|
461
|
+
}
|
|
462
|
+
case 'add-table-caption': {
|
|
463
|
+
if (!target.querySelector('caption')) {
|
|
464
|
+
const caption = document.createElement('caption')
|
|
465
|
+
caption.textContent = 'Describe this table.'
|
|
466
|
+
target.prepend(caption)
|
|
467
|
+
}
|
|
468
|
+
break
|
|
469
|
+
}
|
|
470
|
+
case 'promote-first-row-headers': {
|
|
471
|
+
const firstRow = target.querySelector('tr')
|
|
472
|
+
if (firstRow) {
|
|
473
|
+
for (const cell of Array.from(firstRow.children)) {
|
|
474
|
+
if (cell.tagName.toLowerCase() !== 'td') continue
|
|
475
|
+
const header = document.createElement('th')
|
|
476
|
+
header.setAttribute('scope', 'col')
|
|
477
|
+
header.innerHTML = cell.innerHTML
|
|
478
|
+
for (const attr of Array.from(cell.attributes)) {
|
|
479
|
+
if (attr.name !== 'scope') header.setAttribute(attr.name, attr.value)
|
|
480
|
+
}
|
|
481
|
+
cell.replaceWith(header)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
break
|
|
485
|
+
}
|
|
486
|
+
case 'add-video-captions-track': {
|
|
487
|
+
const track = document.createElement('track')
|
|
488
|
+
track.setAttribute('kind', 'captions')
|
|
489
|
+
track.setAttribute('src', '')
|
|
490
|
+
track.setAttribute('srclang', 'en')
|
|
491
|
+
track.setAttribute('label', 'English captions')
|
|
492
|
+
target.append(track)
|
|
493
|
+
break
|
|
494
|
+
}
|
|
495
|
+
case 'remove-empty-heading':
|
|
496
|
+
target.remove()
|
|
497
|
+
break
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return serializeHtml(document)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function getAccessibilityIssueDraft(
|
|
504
|
+
html: string,
|
|
505
|
+
issue: AccessibilityIssue,
|
|
506
|
+
): AccessibilityIssueDraft | null {
|
|
507
|
+
const document = parseHtml(html)
|
|
508
|
+
const target = findTarget(document, issue)
|
|
509
|
+
if (!target) return null
|
|
510
|
+
|
|
511
|
+
switch (issue.ruleId) {
|
|
512
|
+
case 'image-missing-alt':
|
|
513
|
+
return {
|
|
514
|
+
label: 'Alt text',
|
|
515
|
+
description:
|
|
516
|
+
'A short description for readers who cannot see the image.',
|
|
517
|
+
value: imageAltDraft(target),
|
|
518
|
+
applyLabel: 'Use alt text',
|
|
519
|
+
}
|
|
520
|
+
case 'figure-missing-caption':
|
|
521
|
+
return {
|
|
522
|
+
label: 'Caption',
|
|
523
|
+
description: 'A visible caption that explains why this figure is here.',
|
|
524
|
+
value: figureCaptionDraft(target),
|
|
525
|
+
applyLabel: 'Add caption',
|
|
526
|
+
}
|
|
527
|
+
case 'table-missing-caption':
|
|
528
|
+
return {
|
|
529
|
+
label: 'Caption',
|
|
530
|
+
description: 'A brief table title for readers and assistive technology.',
|
|
531
|
+
value: tableCaptionDraft(target),
|
|
532
|
+
applyLabel: 'Add caption',
|
|
533
|
+
}
|
|
534
|
+
case 'link-empty-label':
|
|
535
|
+
case 'link-ambiguous-label': {
|
|
536
|
+
const href = target.getAttribute('href')?.trim() ?? ''
|
|
537
|
+
return {
|
|
538
|
+
label: 'Link text',
|
|
539
|
+
description: 'Text that still makes sense when read by itself.',
|
|
540
|
+
value: readableLinkDestination(href) || nearbySentence(target),
|
|
541
|
+
applyLabel: 'Use link text',
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
case 'frame-missing-title': {
|
|
545
|
+
const src = target.getAttribute('src')?.trim() ?? ''
|
|
546
|
+
return {
|
|
547
|
+
label: 'Frame title',
|
|
548
|
+
description: 'A short name for the embedded content.',
|
|
549
|
+
value: readableLinkDestination(src) || 'Embedded content',
|
|
550
|
+
applyLabel: 'Use title',
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
default:
|
|
554
|
+
return null
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export function applyAccessibilityIssueDraft(
|
|
559
|
+
html: string,
|
|
560
|
+
issue: AccessibilityIssue,
|
|
561
|
+
value: string,
|
|
562
|
+
): string {
|
|
563
|
+
const text = value.replace(/\s+/g, ' ').trim()
|
|
564
|
+
if (!text) return html
|
|
565
|
+
|
|
566
|
+
const document = parseHtml(html)
|
|
567
|
+
const target = findTarget(document, issue)
|
|
568
|
+
if (!target) return html
|
|
569
|
+
|
|
570
|
+
switch (issue.ruleId) {
|
|
571
|
+
case 'image-missing-alt':
|
|
572
|
+
target.setAttribute('alt', text)
|
|
573
|
+
break
|
|
574
|
+
case 'figure-missing-caption': {
|
|
575
|
+
let caption = target.querySelector('figcaption')
|
|
576
|
+
if (!caption) {
|
|
577
|
+
caption = document.createElement('figcaption')
|
|
578
|
+
target.append(caption)
|
|
579
|
+
}
|
|
580
|
+
caption.textContent = text
|
|
581
|
+
break
|
|
582
|
+
}
|
|
583
|
+
case 'table-missing-caption': {
|
|
584
|
+
let caption = target.querySelector('caption')
|
|
585
|
+
if (!caption) {
|
|
586
|
+
caption = document.createElement('caption')
|
|
587
|
+
target.prepend(caption)
|
|
588
|
+
}
|
|
589
|
+
caption.textContent = text
|
|
590
|
+
break
|
|
591
|
+
}
|
|
592
|
+
case 'link-empty-label':
|
|
593
|
+
case 'link-ambiguous-label':
|
|
594
|
+
target.textContent = text
|
|
595
|
+
break
|
|
596
|
+
case 'frame-missing-title':
|
|
597
|
+
target.setAttribute('title', text)
|
|
598
|
+
break
|
|
599
|
+
default:
|
|
600
|
+
return html
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return serializeHtml(document)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function createCommentId(): string {
|
|
607
|
+
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
|
|
608
|
+
return `accessibility-${crypto.randomUUID()}`
|
|
609
|
+
}
|
|
610
|
+
return `accessibility-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function annotateAccessibilityIssue(
|
|
614
|
+
html: string,
|
|
615
|
+
issue: AccessibilityIssue,
|
|
616
|
+
): string {
|
|
617
|
+
const document = parseHtml(html)
|
|
618
|
+
const target = findTarget(document, issue)
|
|
619
|
+
if (!target) return html
|
|
620
|
+
|
|
621
|
+
const classes = new Set(
|
|
622
|
+
(target.getAttribute('class') ?? '').split(/\s+/).filter(Boolean),
|
|
623
|
+
)
|
|
624
|
+
classes.add('comment-mark')
|
|
625
|
+
classes.add('comment-mark--agent-review')
|
|
626
|
+
target.setAttribute('class', Array.from(classes).join(' '))
|
|
627
|
+
target.setAttribute('data-comment-id', createCommentId())
|
|
628
|
+
target.setAttribute('data-comment-type', 'agent-review')
|
|
629
|
+
target.setAttribute(
|
|
630
|
+
'data-comment-text',
|
|
631
|
+
`${issue.title}: ${issue.detail} (${issue.standard} ${issue.wcag.join(', ')})`,
|
|
632
|
+
)
|
|
633
|
+
target.setAttribute('data-comment-author', ISSUE_AUTHOR)
|
|
634
|
+
target.setAttribute('data-comment-created-at', new Date().toISOString())
|
|
635
|
+
target.setAttribute('data-comment-replies', '[]')
|
|
636
|
+
target.setAttribute('title', `${ISSUE_AUTHOR}: ${issue.title}`)
|
|
637
|
+
|
|
638
|
+
return serializeHtml(document)
|
|
639
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export {
|
|
2
|
+
AccessibilityReviewDialog,
|
|
3
|
+
type AccessibilityReviewDialogProps,
|
|
4
|
+
} from './AccessibilityReviewDialog.js'
|
|
5
|
+
export {
|
|
6
|
+
annotateAccessibilityIssue,
|
|
7
|
+
applyAccessibilityIssueDraft,
|
|
8
|
+
applyAccessibilityIssueFix,
|
|
9
|
+
auditHtmlAccessibility,
|
|
10
|
+
getAccessibilityIssueDraft,
|
|
11
|
+
type AccessibilityIssue,
|
|
12
|
+
type AccessibilityIssueCategory,
|
|
13
|
+
type AccessibilityIssueDraft,
|
|
14
|
+
type AccessibilityIssueFix,
|
|
15
|
+
type AccessibilityIssueSeverity,
|
|
16
|
+
} from './accessibilityReview.js'
|