@echothink-ui/documents 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/DocumentEditorShell.tsx","../src/components/DocumentLockBadge.tsx","../src/components/utils.tsx","../src/components/DocumentViewer.tsx","../src/components/DocumentToolbar.tsx","../src/components/DocumentOutline.tsx","../src/components/AgentLockedDocumentPanel.tsx","../src/components/DocumentWorkspaceTemplate.tsx","../src/index.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Badge, IconButton } from \"@echothink-ui/core\";\nimport { ChevronRightIcon } from \"@echothink-ui/icons\";\nimport { DocumentLockBadge } from \"./DocumentLockBadge\";\nimport type {\n DocumentComponentBaseProps,\n DocumentLockState,\n DocumentMode,\n DocumentOwner,\n DocumentReference\n} from \"./types\";\nimport { cx, documentRefLabel, modeLabel } from \"./utils\";\n\nexport interface DocumentEditorShellProps extends Omit<\n DocumentComponentBaseProps<HTMLElement>,\n \"children\"\n> {\n title: React.ReactNode;\n documentRef: DocumentReference;\n mode?: DocumentMode;\n lockState?: DocumentLockState;\n lockOwner?: DocumentOwner;\n toolbar?: React.ReactNode;\n outline?: React.ReactNode;\n inspector?: React.ReactNode;\n children: React.ReactNode;\n}\n\nexport function DocumentEditorShell({\n title,\n documentRef,\n mode = \"edit\",\n lockState = \"unlocked\",\n lockOwner,\n toolbar,\n outline,\n inspector,\n children,\n density = \"default\",\n className,\n ...props\n}: DocumentEditorShellProps) {\n const [outlineOpen, setOutlineOpen] = React.useState(Boolean(outline));\n const hasOutline = Boolean(outline);\n const hasInspector = Boolean(inspector);\n const gridTemplateColumns = [\n hasOutline && outlineOpen ? \"minmax(180px, 260px)\" : null,\n \"minmax(0, 1fr)\",\n hasInspector ? \"minmax(220px, 320px)\" : null\n ]\n .filter(Boolean)\n .join(\" \");\n const gridStyle = {\n \"--eth-doc-editor-shell-grid-template\": gridTemplateColumns\n } as React.CSSProperties;\n const ethComponent = props[\"data-eth-component\"] ?? \"DocumentEditorShell\";\n\n return (\n <section\n {...props}\n className={cx(\n \"eth-doc-editor-shell\",\n `eth-doc-editor-shell--${density}`,\n hasOutline && \"eth-doc-editor-shell--has-outline\",\n hasOutline && outlineOpen && \"eth-doc-editor-shell--outline-open\",\n hasInspector && \"eth-doc-editor-shell--has-inspector\",\n className\n )}\n data-eth-component={ethComponent}\n >\n <header className=\"eth-doc-editor-shell__bar\">\n <div className=\"eth-doc-editor-shell__title\">\n <p className=\"eth-eyebrow\">{documentRefLabel(documentRef)}</p>\n <h2>{title}</h2>\n </div>\n <div className=\"eth-doc-editor-shell__status\">\n {hasOutline ? (\n <IconButton\n label={outlineOpen ? \"Collapse outline\" : \"Expand outline\"}\n intent=\"ghost\"\n density={density}\n icon={\n <ChevronRightIcon\n size={16}\n style={{ transform: outlineOpen ? \"rotate(180deg)\" : undefined }}\n />\n }\n aria-expanded={outlineOpen}\n onClick={() => setOutlineOpen((current) => !current)}\n />\n ) : null}\n <Badge severity=\"neutral\">{modeLabel(mode)}</Badge>\n <DocumentLockBadge lockState={lockState} owner={lockOwner} />\n </div>\n </header>\n\n {toolbar ? <div className=\"eth-doc-editor-shell__toolbar\">{toolbar}</div> : null}\n\n <div className=\"eth-doc-editor-shell__grid\" style={gridStyle}>\n {hasOutline && outlineOpen ? (\n <aside className=\"eth-doc-editor-shell__outline\" aria-label=\"Document outline\">\n {outline}\n </aside>\n ) : null}\n <main className=\"eth-doc-editor-shell__editor\" aria-label=\"Document editor\">\n {children}\n </main>\n {inspector ? (\n <aside className=\"eth-doc-editor-shell__inspector\" aria-label=\"Document inspector\">\n {inspector}\n </aside>\n ) : null}\n </div>\n </section>\n );\n}\n","import * as React from \"react\";\nimport { Badge, Tooltip, type EthSeverity } from \"@echothink-ui/core\";\nimport { LockIcon, UnlockIcon } from \"@echothink-ui/icons\";\nimport type { DocumentComponentBaseProps, DocumentLockState, DocumentOwner } from \"./types\";\nimport { cx, formatDateTime } from \"./utils\";\n\nexport interface DocumentLockBadgeProps extends Omit<\n DocumentComponentBaseProps<HTMLSpanElement>,\n \"title\"\n> {\n lockState: DocumentLockState;\n owner?: DocumentOwner;\n since?: string;\n}\n\nconst lockConfig: Record<\n DocumentLockState,\n { label: string; severity: EthSeverity; tooltip: string }\n> = {\n unlocked: {\n label: \"Unlocked\",\n severity: \"success\",\n tooltip: \"This document is available for editing.\"\n },\n \"locked-by-user\": {\n label: \"Locked by you\",\n severity: \"info\",\n tooltip: \"You currently hold the document lock.\"\n },\n \"locked-by-agent\": {\n label: \"Agent lock\",\n severity: \"warning\",\n tooltip: \"An agent currently holds the document lock.\"\n },\n \"locked-by-other\": {\n label: \"Locked\",\n severity: \"danger\",\n tooltip: \"Another user currently holds the document lock.\"\n }\n};\n\nexport function DocumentLockBadge({\n lockState,\n owner,\n since,\n density = \"compact\",\n className,\n ...props\n}: DocumentLockBadgeProps) {\n const config = lockConfig[lockState];\n const Icon = lockState === \"unlocked\" ? UnlockIcon : LockIcon;\n const visibleDetail = getVisibleDetail(lockState, owner, since);\n const ownerText = owner ? ` Owner: ${owner.label}.` : \"\";\n const sinceText = since ? ` Since: ${formatDateTime(since)}.` : \"\";\n const tooltip = `${config.tooltip}${ownerText}${sinceText}`;\n\n return (\n <Tooltip label={tooltip}>\n <span\n {...props}\n className={cx(\"eth-doc-lock-badge\", `eth-doc-lock-badge--${density}`, className)}\n aria-label={tooltip}\n data-lock-state={lockState}\n data-eth-component=\"DocumentLockBadge\"\n >\n <Badge\n severity={config.severity}\n className={cx(\"eth-doc-lock-badge__tag\", `eth-doc-lock-badge__tag--${lockState}`)}\n >\n <span className=\"eth-doc-lock-badge__content\">\n <Icon className=\"eth-doc-lock-badge__icon\" size={12} />\n <span className=\"eth-doc-lock-badge__label\">{config.label}</span>\n {visibleDetail ? (\n <span className=\"eth-doc-lock-badge__detail\">{visibleDetail}</span>\n ) : null}\n </span>\n </Badge>\n </span>\n </Tooltip>\n );\n}\n\nfunction getVisibleDetail(\n lockState: DocumentLockState,\n owner: DocumentOwner | undefined,\n since: string | undefined\n) {\n const sinceLabel = since ? formatDateTime(since) : undefined;\n if (lockState === \"unlocked\") return sinceLabel;\n if (lockState === \"locked-by-user\") return sinceLabel;\n\n return [owner?.label, sinceLabel].filter(Boolean).join(\" / \") || undefined;\n}\n","import * as React from \"react\";\nimport type { EthAction } from \"@echothink-ui/core\";\nimport {\n AgentRunningIcon,\n ApprovalRequiredIcon,\n DocumentIcon,\n DownloadIcon,\n ExternalLinkIcon,\n SearchIcon,\n StatusIcon\n} from \"@echothink-ui/icons\";\nimport type { DocumentMode, DocumentReference } from \"./types\";\n\nexport function cx(...classes: Array<string | false | null | undefined>) {\n return classes.filter(Boolean).join(\" \");\n}\n\nexport function documentRefLabel(documentRef: DocumentReference) {\n if (typeof documentRef === \"string\") return documentRef;\n if (documentRef.label) return documentRef.label;\n return documentRef.id;\n}\n\nexport function documentRefId(documentRef: DocumentReference) {\n return typeof documentRef === \"string\" ? documentRef : documentRef.id;\n}\n\nexport function formatDateTime(value?: string) {\n if (!value) return \"Unknown\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return date.toLocaleString(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\"\n });\n}\n\nexport function modeLabel(mode: DocumentMode) {\n return mode.charAt(0).toUpperCase() + mode.slice(1);\n}\n\nexport function actionIcon(action: EthAction) {\n const key = `${action.id} ${action.label}`.toLowerCase();\n if (key.includes(\"bold\"))\n return (\n <strong className=\"eth-doc-toolbar__format-symbol\" aria-hidden>\n B\n </strong>\n );\n if (key.includes(\"italic\"))\n return (\n <em className=\"eth-doc-toolbar__format-symbol\" aria-hidden>\n I\n </em>\n );\n if (key.includes(\"underline\"))\n return (\n <span\n className=\"eth-doc-toolbar__format-symbol eth-doc-toolbar__format-symbol--underline\"\n aria-hidden\n >\n U\n </span>\n );\n if (key.includes(\"strike\"))\n return (\n <span\n className=\"eth-doc-toolbar__format-symbol eth-doc-toolbar__format-symbol--strike\"\n aria-hidden\n >\n S\n </span>\n );\n if (key.includes(\"code\"))\n return (\n <span className=\"eth-doc-toolbar__format-symbol\" aria-hidden>\n {\"<>\"}\n </span>\n );\n if (key.includes(\"download\") || key.includes(\"export\")) return <DownloadIcon size={16} />;\n if (key.includes(\"share\") || key.includes(\"open\")) return <ExternalLinkIcon size={16} />;\n if (key.includes(\"link\") || action.href) return <ExternalLinkIcon size={16} />;\n if (key.includes(\"search\") || key.includes(\"find\")) return <SearchIcon size={16} />;\n if (key.includes(\"agent\")) return <AgentRunningIcon size={16} />;\n if (key.includes(\"approve\") || key.includes(\"review\")) return <ApprovalRequiredIcon size={16} />;\n if (key.includes(\"status\")) return <StatusIcon size={16} />;\n return <DocumentIcon size={16} />;\n}\n","import * as React from \"react\";\nimport { Badge } from \"@echothink-ui/core\";\nimport type { DocumentAnnotation, DocumentComponentBaseProps, DocumentMetadataItem } from \"./types\";\nimport { cx, formatDateTime } from \"./utils\";\n\nexport interface DocumentViewerProps extends Omit<\n DocumentComponentBaseProps<HTMLElement>,\n \"children\"\n> {\n title: React.ReactNode;\n content: React.ReactNode;\n metadata?: DocumentMetadataItem[];\n versionRef?: React.ReactNode;\n annotations?: DocumentAnnotation[];\n}\n\nexport function DocumentViewer({\n title,\n content,\n metadata = [],\n versionRef,\n annotations = [],\n className,\n ...props\n}: DocumentViewerProps) {\n const titleId = React.useId();\n const meta = versionRef ? [...metadata, { label: \"Version\", value: versionRef }] : metadata;\n const labelledBy = props[\"aria-labelledby\"] ?? titleId;\n\n return (\n <article\n {...props}\n aria-labelledby={labelledBy}\n className={cx(\n \"eth-doc-viewer\",\n annotations.length > 0 && \"eth-doc-viewer--with-annotations\",\n className\n )}\n data-eth-component=\"DocumentViewer\"\n >\n <header className=\"eth-doc-viewer__header\">\n <h2 id={titleId}>{title}</h2>\n </header>\n\n {meta.length ? (\n <dl className=\"eth-meta-grid eth-doc-viewer__metadata\">\n {meta.map((item, index) => (\n <div key={`${String(item.label)}-${index}`}>\n <dt>{item.label}</dt>\n <dd>{item.value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n\n <div className=\"eth-doc-viewer__body\">\n <div className=\"eth-doc-viewer__content\">{content}</div>\n\n {annotations.length ? (\n <aside className=\"eth-doc-viewer__annotations\" aria-label=\"Annotations\">\n <h3>Annotations</h3>\n <ul>\n {annotations.map((annotation) => (\n <li key={annotation.id} className=\"eth-doc-viewer__annotation\">\n <div className=\"eth-doc-viewer__annotation-header\">\n <strong>{annotation.author}</strong>\n {annotation.range ? <Badge severity=\"neutral\">{annotation.range}</Badge> : null}\n <time dateTime={annotation.createdAt}>\n {formatDateTime(annotation.createdAt)}\n </time>\n </div>\n <p>{annotation.comment}</p>\n </li>\n ))}\n </ul>\n </aside>\n ) : null}\n </div>\n </article>\n );\n}\n","import * as React from \"react\";\nimport {\n Button,\n IconButton,\n LinkButton,\n type EthAction,\n type EthDensity\n} from \"@echothink-ui/core\";\nimport type { DocumentComponentBaseProps, DocumentMode } from \"./types\";\nimport { actionIcon, cx, modeLabel } from \"./utils\";\n\nexport interface DocumentToolbarProps extends Omit<\n DocumentComponentBaseProps<HTMLDivElement>,\n \"children\" | \"title\"\n> {\n actions: EthAction[];\n formattingActions?: EthAction[];\n mode?: DocumentMode;\n onModeChange?: (mode: DocumentMode) => void;\n density?: EthDensity;\n}\n\nconst modes: DocumentMode[] = [\"edit\", \"review\", \"view\"];\ntype ToolbarActionVariant = \"icon\" | \"button\";\n\nexport function DocumentToolbar({\n actions,\n formattingActions = [],\n mode,\n onModeChange,\n density = \"default\",\n className,\n ...props\n}: DocumentToolbarProps) {\n return (\n <div\n {...props}\n className={cx(\"eth-doc-toolbar\", className)}\n role=\"toolbar\"\n aria-label=\"Document toolbar\"\n data-eth-component=\"DocumentToolbar\"\n >\n {formattingActions.length ? (\n <div\n className=\"eth-doc-toolbar__group eth-doc-toolbar__group--formatting\"\n role=\"group\"\n aria-label=\"Formatting\"\n >\n {formattingActions.map((action) => renderToolbarAction(action, density, \"icon\"))}\n </div>\n ) : null}\n\n {mode && onModeChange ? (\n <div\n className=\"eth-doc-toolbar__group eth-doc-toolbar__group--mode\"\n role=\"group\"\n aria-label=\"Document mode\"\n >\n {modes.map((candidate) => (\n <Button\n key={candidate}\n className=\"eth-doc-toolbar__mode-button\"\n density={density}\n intent={candidate === mode ? \"primary\" : \"secondary\"}\n aria-pressed={candidate === mode}\n onClick={() => onModeChange(candidate)}\n >\n {modeLabel(candidate)}\n </Button>\n ))}\n </div>\n ) : null}\n\n {actions.length ? (\n <div\n className=\"eth-doc-toolbar__group eth-doc-toolbar__group--actions\"\n role=\"group\"\n aria-label=\"Document actions\"\n >\n {actions.map((action) => renderToolbarAction(action, density, \"button\"))}\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction renderToolbarAction(\n action: EthAction,\n density: EthDensity,\n variant: ToolbarActionVariant\n) {\n const icon = actionIcon(action);\n\n if (variant === \"button\") {\n if (action.href) {\n return (\n <LinkButton\n key={action.id}\n href={action.href}\n density={density}\n intent={action.intent ?? \"secondary\"}\n aria-disabled={action.disabled}\n tabIndex={action.disabled ? -1 : undefined}\n className={cx(\n \"eth-doc-toolbar__action\",\n action.disabled && \"eth-doc-toolbar__link--disabled\"\n )}\n onClick={(event) => {\n if (action.disabled) {\n event.preventDefault();\n return;\n }\n action.onSelect?.();\n }}\n >\n <span className=\"eth-doc-toolbar__action-icon\" aria-hidden=\"true\">\n {icon}\n </span>\n <span>{action.label}</span>\n </LinkButton>\n );\n }\n\n return (\n <Button\n key={action.id}\n className=\"eth-doc-toolbar__action\"\n density={density}\n intent={action.intent ?? \"secondary\"}\n icon={icon}\n disabled={action.disabled}\n onClick={action.onSelect}\n >\n {action.label}\n </Button>\n );\n }\n\n if (action.href) {\n return (\n <a\n key={action.id}\n href={action.href}\n aria-label={action.label}\n tabIndex={action.disabled ? -1 : undefined}\n className={cx(\n \"eth-button\",\n \"eth-icon-button\",\n \"eth-doc-toolbar__icon-link\",\n `eth-button--${action.intent ?? \"ghost\"}`,\n `eth-button--${density}`,\n action.disabled && \"eth-doc-toolbar__link--disabled\"\n )}\n aria-disabled={action.disabled}\n onClick={(event) => {\n if (action.disabled) {\n event.preventDefault();\n return;\n }\n action.onSelect?.();\n }}\n >\n <span className=\"eth-button__icon\">{icon}</span>\n </a>\n );\n }\n\n return (\n <IconButton\n key={action.id}\n density={density}\n intent={action.intent ?? \"ghost\"}\n label={action.label}\n icon={icon}\n disabled={action.disabled}\n onClick={action.onSelect}\n />\n );\n}\n","import * as React from \"react\";\nimport type { DocumentComponentBaseProps, DocumentHeading } from \"./types\";\nimport { cx } from \"./utils\";\n\nexport interface DocumentOutlineProps extends Omit<\n DocumentComponentBaseProps<HTMLElement>,\n \"children\" | \"title\"\n> {\n headings: DocumentHeading[];\n activeHeadingId?: string;\n onSelect?: (id: string) => void;\n}\n\ninterface OutlineNode extends DocumentHeading {\n children: OutlineNode[];\n}\n\nexport function DocumentOutline({\n headings,\n activeHeadingId,\n onSelect,\n className,\n \"aria-label\": ariaLabel,\n ...props\n}: DocumentOutlineProps) {\n const tree = React.useMemo(() => buildOutlineTree(headings), [headings]);\n const headingCountLabel = `${headings.length} section${headings.length === 1 ? \"\" : \"s\"}`;\n\n return (\n <nav\n {...props}\n className={cx(\"eth-doc-outline\", className)}\n aria-label={ariaLabel ?? \"Document outline\"}\n data-eth-component=\"DocumentOutline\"\n >\n <div className=\"eth-doc-outline__header\">\n <span className=\"eth-doc-outline__label\">On this page</span>\n <span className=\"eth-doc-outline__count\">{headingCountLabel}</span>\n </div>\n {tree.length ? (\n <ol className=\"eth-doc-outline__list\">{renderNodes(tree, activeHeadingId, onSelect)}</ol>\n ) : (\n <p className=\"eth-doc-outline__empty\" role=\"status\">\n No headings available.\n </p>\n )}\n </nav>\n );\n}\n\nfunction renderNodes(\n nodes: OutlineNode[],\n activeHeadingId: string | undefined,\n onSelect: ((id: string) => void) | undefined\n): React.ReactNode {\n return nodes.map((node) => {\n const href = node.href ?? `#${node.id}`;\n const isActive = node.id === activeHeadingId;\n const containsActiveHeading = activeHeadingId\n ? node.children.some((child) => containsHeading(child, activeHeadingId))\n : false;\n\n return (\n <li\n key={node.id}\n className={cx(\n \"eth-doc-outline__item\",\n `eth-doc-outline__item--level-${node.level}`,\n isActive && \"eth-doc-outline__item--active\",\n containsActiveHeading && \"eth-doc-outline__item--contains-active\"\n )}\n >\n <a\n className=\"eth-doc-outline__link\"\n href={href}\n aria-current={isActive ? \"location\" : undefined}\n onClick={(event) => {\n if (!href.startsWith(\"#\") && !onSelect) return;\n event.preventDefault();\n onSelect?.(node.id);\n scrollHeadingIntoView(node.href, node.id);\n }}\n >\n <span className=\"eth-doc-outline__text\">{node.text}</span>\n </a>\n {node.children.length ? (\n <ol className=\"eth-doc-outline__list\">\n {renderNodes(node.children, activeHeadingId, onSelect)}\n </ol>\n ) : null}\n </li>\n );\n });\n}\n\nfunction containsHeading(node: OutlineNode, id: string): boolean {\n return node.id === id || node.children.some((child) => containsHeading(child, id));\n}\n\nfunction buildOutlineTree(headings: DocumentHeading[]) {\n const roots: OutlineNode[] = [];\n const stack: Array<{ level: number; children: OutlineNode[] }> = [{ level: 0, children: roots }];\n\n for (const heading of headings) {\n const node: OutlineNode = { ...heading, children: [] };\n while (stack.length > 1 && stack[stack.length - 1].level >= heading.level) stack.pop();\n stack[stack.length - 1].children.push(node);\n stack.push(node);\n }\n\n return roots;\n}\n\nfunction scrollHeadingIntoView(href: string | undefined, id: string) {\n const targetId = href?.startsWith(\"#\") ? href.slice(1) : id;\n const target = globalThis.document?.getElementById(targetId);\n target?.scrollIntoView({ block: \"start\", behavior: \"smooth\" });\n}\n","import * as React from \"react\";\nimport { Button, InlineNotification, Surface } from \"@echothink-ui/core\";\nimport type {\n DocumentComponentBaseProps,\n DocumentOwner,\n DocumentPendingChange,\n DocumentReference\n} from \"./types\";\nimport { cx, documentRefLabel } from \"./utils\";\nimport { DocumentLockBadge } from \"./DocumentLockBadge\";\n\nexport interface AgentLockedDocumentPanelProps\n extends Omit<DocumentComponentBaseProps<HTMLElement>, \"children\" | \"title\"> {\n documentRef: DocumentReference;\n lockOwner?: DocumentOwner;\n pendingChanges?: DocumentPendingChange[];\n onTakeover?: () => void;\n onRelease?: () => void;\n onReviewChanges?: () => void;\n}\n\nexport function AgentLockedDocumentPanel({\n documentRef,\n lockOwner,\n pendingChanges = [],\n onTakeover,\n onRelease,\n onReviewChanges,\n density = \"default\",\n className,\n ...props\n}: AgentLockedDocumentPanelProps) {\n const headingId = React.useId();\n const ownerLabel = lockOwner?.label ?? \"An agent\";\n const documentLabel = documentRefLabel(documentRef);\n const documentVersion = typeof documentRef === \"string\" ? undefined : documentRef.version;\n const pendingCountLabel = `${pendingChanges.length} ${\n pendingChanges.length === 1 ? \"change\" : \"changes\"\n }`;\n const hasActions = Boolean(onReviewChanges || onRelease || onTakeover);\n\n return (\n <Surface\n {...props}\n title=\"Agent document lock\"\n subtitle={\n <>\n {ownerLabel} holds the editing lock for {documentLabel}.\n </>\n }\n severity=\"warning\"\n density={density}\n metadata={[\n {\n label: \"Lock state\",\n value: <DocumentLockBadge lockState=\"locked-by-agent\" owner={lockOwner} />\n },\n { label: \"Document\", value: documentLabel },\n ...(documentVersion ? [{ label: \"Version\", value: documentVersion }] : []),\n { label: \"Pending changes\", value: pendingCountLabel }\n ]}\n className={cx(\"eth-doc-agent-lock-panel\", className)}\n data-eth-component=\"AgentLockedDocumentPanel\"\n >\n <div className=\"eth-doc-agent-lock-panel__notice\">\n <InlineNotification severity=\"warning\" title=\"Document locked by agent\">\n {ownerLabel} is preparing changes for {documentLabel}. Review staged updates before\n releasing the lock or taking over.\n </InlineNotification>\n </div>\n\n <section className=\"eth-doc-agent-lock-panel__changes-section\" aria-labelledby={headingId}>\n <div className=\"eth-doc-agent-lock-panel__changes-header\">\n <h3 id={headingId}>Pending changes</h3>\n <span className=\"eth-doc-agent-lock-panel__count\">{pendingCountLabel}</span>\n </div>\n\n {pendingChanges.length ? (\n <ol className=\"eth-doc-agent-lock-panel__changes\">\n {pendingChanges.map((change) => (\n <li key={change.id} className=\"eth-doc-agent-lock-panel__change\">\n <div className=\"eth-doc-agent-lock-panel__change-body\">\n <strong className=\"eth-doc-agent-lock-panel__summary\">{change.summary}</strong>\n {change.diffSnippet ? (\n <pre className=\"eth-doc-agent-lock-panel__diff\">{change.diffSnippet}</pre>\n ) : null}\n </div>\n </li>\n ))}\n </ol>\n ) : (\n <p className=\"eth-doc-agent-lock-panel__empty\" role=\"status\">\n The agent holds the lock but has not staged changes.\n </p>\n )}\n </section>\n\n {hasActions ? (\n <div className=\"eth-actions eth-doc-agent-lock-panel__actions\">\n {onReviewChanges ? (\n <Button intent=\"primary\" density={density} onClick={onReviewChanges}>\n Review changes\n </Button>\n ) : null}\n {onRelease ? (\n <Button intent=\"secondary\" density={density} onClick={onRelease}>\n Release lock\n </Button>\n ) : null}\n {onTakeover ? (\n <Button intent=\"danger\" density={density} onClick={onTakeover}>\n Take over\n </Button>\n ) : null}\n </div>\n ) : null}\n </Surface>\n );\n}\n","import * as React from \"react\";\nimport { EmptyState, type EthAction } from \"@echothink-ui/core\";\nimport { DocumentEditorShell } from \"./DocumentEditorShell\";\nimport { DocumentOutline } from \"./DocumentOutline\";\nimport { DocumentToolbar } from \"./DocumentToolbar\";\nimport { DocumentViewer } from \"./DocumentViewer\";\nimport type {\n DocumentAnnotation,\n DocumentComponentBaseProps,\n DocumentHeading,\n DocumentLockState,\n DocumentMetadataItem,\n DocumentMode,\n DocumentOwner,\n DocumentReference\n} from \"./types\";\n\nexport interface DocumentWorkspaceTemplateProps\n extends Omit<DocumentComponentBaseProps<HTMLElement>, \"children\"> {\n title: React.ReactNode;\n documentRef: DocumentReference;\n mode?: DocumentMode;\n lockState?: DocumentLockState;\n lockOwner?: DocumentOwner;\n actions?: EthAction[];\n formattingActions?: EthAction[];\n onModeChange?: (mode: DocumentMode) => void;\n headings?: DocumentHeading[];\n activeHeadingId?: string;\n onSelectHeading?: (id: string) => void;\n inspector?: React.ReactNode;\n toolbar?: React.ReactNode;\n metadata?: DocumentMetadataItem[];\n versionRef?: React.ReactNode;\n annotations?: DocumentAnnotation[];\n content?: React.ReactNode;\n children?: React.ReactNode;\n}\n\nexport function DocumentWorkspaceTemplate({\n title,\n documentRef,\n mode = \"edit\",\n lockState = \"unlocked\",\n lockOwner,\n actions = [],\n formattingActions,\n onModeChange,\n headings = [],\n activeHeadingId,\n onSelectHeading,\n inspector,\n toolbar,\n metadata,\n versionRef,\n annotations,\n content,\n children,\n density,\n ...props\n}: DocumentWorkspaceTemplateProps) {\n const outline = headings.length ? (\n <DocumentOutline\n headings={headings}\n activeHeadingId={activeHeadingId}\n onSelect={onSelectHeading}\n />\n ) : null;\n const resolvedToolbar =\n toolbar ?? (\n <DocumentToolbar\n actions={actions}\n formattingActions={formattingActions}\n mode={mode}\n onModeChange={onModeChange}\n density={density}\n />\n );\n const body =\n mode === \"view\" && content !== undefined ? (\n <DocumentViewer\n title={title}\n content={content}\n metadata={metadata}\n versionRef={versionRef}\n annotations={annotations}\n />\n ) : (\n children ?? content ?? <EmptyState title=\"No document content\" />\n );\n\n return (\n <DocumentEditorShell\n {...props}\n title={title}\n documentRef={documentRef}\n mode={mode}\n lockState={lockState}\n lockOwner={lockOwner}\n toolbar={resolvedToolbar}\n outline={outline}\n inspector={inspector}\n density={density}\n data-eth-component=\"DocumentWorkspaceTemplate\"\n >\n {body}\n </DocumentEditorShell>\n );\n}\n","import \"./styles.css\";\n\nexport {\n DocumentEditorShell,\n type DocumentEditorShellProps\n} from \"./components/DocumentEditorShell\";\nexport { DocumentViewer, type DocumentViewerProps } from \"./components/DocumentViewer\";\nexport { DocumentToolbar, type DocumentToolbarProps } from \"./components/DocumentToolbar\";\nexport { DocumentOutline, type DocumentOutlineProps } from \"./components/DocumentOutline\";\nexport { DocumentLockBadge, type DocumentLockBadgeProps } from \"./components/DocumentLockBadge\";\nexport {\n AgentLockedDocumentPanel,\n type AgentLockedDocumentPanelProps\n} from \"./components/AgentLockedDocumentPanel\";\nexport {\n DocumentWorkspaceTemplate,\n type DocumentWorkspaceTemplateProps\n} from \"./components/DocumentWorkspaceTemplate\";\nexport type {\n DocumentAnnotation,\n DocumentHeading,\n DocumentLockState,\n DocumentMetadataItem,\n DocumentMode,\n DocumentOwner,\n DocumentPendingChange,\n DocumentReference\n} from \"./components/types\";\n\nexport const DocumentsComponentNames = [\n \"DocumentEditorShell\",\n \"DocumentViewer\",\n \"DocumentToolbar\",\n \"DocumentOutline\",\n \"DocumentLockBadge\",\n \"AgentLockedDocumentPanel\",\n \"DocumentWorkspaceTemplate\"\n] as const;\nexport type DocumentsComponentName = (typeof DocumentsComponentNames)[number];\n"],"mappings":"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","names":["Badge","jsx","jsx","jsxs","Badge","React","Badge","jsx","jsxs","Badge","IconButton","jsx","jsxs","IconButton","React","jsx","jsxs","React","Button","jsx","jsxs","Button","jsx"]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@echothink-ui/documents",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "sideEffects": [
7
+ "**/*.css"
8
+ ],
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./styles.css": "./src/styles.css"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src",
23
+ "README.md"
24
+ ],
25
+ "peerDependencies": {
26
+ "react": ">=18.3.0",
27
+ "react-dom": ">=18.3.0"
28
+ },
29
+ "dependencies": {
30
+ "@echothink-ui/core": "0.2.0",
31
+ "@echothink-ui/icons": "0.2.0",
32
+ "@echothink-ui/resources": "0.1.0"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup src/index.tsx --format esm,cjs --sourcemap --clean --external react --external react-dom && tsc -p tsconfig.json --declaration --emitDeclarationOnly --noEmit false --outDir dist",
39
+ "typecheck": "tsc -p tsconfig.json --noEmit",
40
+ "test": "vitest run --config ../../vitest.config.ts --passWithNoTests",
41
+ "lint": "eslint src"
42
+ }
43
+ }
@@ -0,0 +1,119 @@
1
+ import * as React from "react";
2
+ import { Button, InlineNotification, Surface } from "@echothink-ui/core";
3
+ import type {
4
+ DocumentComponentBaseProps,
5
+ DocumentOwner,
6
+ DocumentPendingChange,
7
+ DocumentReference
8
+ } from "./types";
9
+ import { cx, documentRefLabel } from "./utils";
10
+ import { DocumentLockBadge } from "./DocumentLockBadge";
11
+
12
+ export interface AgentLockedDocumentPanelProps
13
+ extends Omit<DocumentComponentBaseProps<HTMLElement>, "children" | "title"> {
14
+ documentRef: DocumentReference;
15
+ lockOwner?: DocumentOwner;
16
+ pendingChanges?: DocumentPendingChange[];
17
+ onTakeover?: () => void;
18
+ onRelease?: () => void;
19
+ onReviewChanges?: () => void;
20
+ }
21
+
22
+ export function AgentLockedDocumentPanel({
23
+ documentRef,
24
+ lockOwner,
25
+ pendingChanges = [],
26
+ onTakeover,
27
+ onRelease,
28
+ onReviewChanges,
29
+ density = "default",
30
+ className,
31
+ ...props
32
+ }: AgentLockedDocumentPanelProps) {
33
+ const headingId = React.useId();
34
+ const ownerLabel = lockOwner?.label ?? "An agent";
35
+ const documentLabel = documentRefLabel(documentRef);
36
+ const documentVersion = typeof documentRef === "string" ? undefined : documentRef.version;
37
+ const pendingCountLabel = `${pendingChanges.length} ${
38
+ pendingChanges.length === 1 ? "change" : "changes"
39
+ }`;
40
+ const hasActions = Boolean(onReviewChanges || onRelease || onTakeover);
41
+
42
+ return (
43
+ <Surface
44
+ {...props}
45
+ title="Agent document lock"
46
+ subtitle={
47
+ <>
48
+ {ownerLabel} holds the editing lock for {documentLabel}.
49
+ </>
50
+ }
51
+ severity="warning"
52
+ density={density}
53
+ metadata={[
54
+ {
55
+ label: "Lock state",
56
+ value: <DocumentLockBadge lockState="locked-by-agent" owner={lockOwner} />
57
+ },
58
+ { label: "Document", value: documentLabel },
59
+ ...(documentVersion ? [{ label: "Version", value: documentVersion }] : []),
60
+ { label: "Pending changes", value: pendingCountLabel }
61
+ ]}
62
+ className={cx("eth-doc-agent-lock-panel", className)}
63
+ data-eth-component="AgentLockedDocumentPanel"
64
+ >
65
+ <div className="eth-doc-agent-lock-panel__notice">
66
+ <InlineNotification severity="warning" title="Document locked by agent">
67
+ {ownerLabel} is preparing changes for {documentLabel}. Review staged updates before
68
+ releasing the lock or taking over.
69
+ </InlineNotification>
70
+ </div>
71
+
72
+ <section className="eth-doc-agent-lock-panel__changes-section" aria-labelledby={headingId}>
73
+ <div className="eth-doc-agent-lock-panel__changes-header">
74
+ <h3 id={headingId}>Pending changes</h3>
75
+ <span className="eth-doc-agent-lock-panel__count">{pendingCountLabel}</span>
76
+ </div>
77
+
78
+ {pendingChanges.length ? (
79
+ <ol className="eth-doc-agent-lock-panel__changes">
80
+ {pendingChanges.map((change) => (
81
+ <li key={change.id} className="eth-doc-agent-lock-panel__change">
82
+ <div className="eth-doc-agent-lock-panel__change-body">
83
+ <strong className="eth-doc-agent-lock-panel__summary">{change.summary}</strong>
84
+ {change.diffSnippet ? (
85
+ <pre className="eth-doc-agent-lock-panel__diff">{change.diffSnippet}</pre>
86
+ ) : null}
87
+ </div>
88
+ </li>
89
+ ))}
90
+ </ol>
91
+ ) : (
92
+ <p className="eth-doc-agent-lock-panel__empty" role="status">
93
+ The agent holds the lock but has not staged changes.
94
+ </p>
95
+ )}
96
+ </section>
97
+
98
+ {hasActions ? (
99
+ <div className="eth-actions eth-doc-agent-lock-panel__actions">
100
+ {onReviewChanges ? (
101
+ <Button intent="primary" density={density} onClick={onReviewChanges}>
102
+ Review changes
103
+ </Button>
104
+ ) : null}
105
+ {onRelease ? (
106
+ <Button intent="secondary" density={density} onClick={onRelease}>
107
+ Release lock
108
+ </Button>
109
+ ) : null}
110
+ {onTakeover ? (
111
+ <Button intent="danger" density={density} onClick={onTakeover}>
112
+ Take over
113
+ </Button>
114
+ ) : null}
115
+ </div>
116
+ ) : null}
117
+ </Surface>
118
+ );
119
+ }
@@ -0,0 +1,116 @@
1
+ import * as React from "react";
2
+ import { Badge, IconButton } from "@echothink-ui/core";
3
+ import { ChevronRightIcon } from "@echothink-ui/icons";
4
+ import { DocumentLockBadge } from "./DocumentLockBadge";
5
+ import type {
6
+ DocumentComponentBaseProps,
7
+ DocumentLockState,
8
+ DocumentMode,
9
+ DocumentOwner,
10
+ DocumentReference
11
+ } from "./types";
12
+ import { cx, documentRefLabel, modeLabel } from "./utils";
13
+
14
+ export interface DocumentEditorShellProps extends Omit<
15
+ DocumentComponentBaseProps<HTMLElement>,
16
+ "children"
17
+ > {
18
+ title: React.ReactNode;
19
+ documentRef: DocumentReference;
20
+ mode?: DocumentMode;
21
+ lockState?: DocumentLockState;
22
+ lockOwner?: DocumentOwner;
23
+ toolbar?: React.ReactNode;
24
+ outline?: React.ReactNode;
25
+ inspector?: React.ReactNode;
26
+ children: React.ReactNode;
27
+ }
28
+
29
+ export function DocumentEditorShell({
30
+ title,
31
+ documentRef,
32
+ mode = "edit",
33
+ lockState = "unlocked",
34
+ lockOwner,
35
+ toolbar,
36
+ outline,
37
+ inspector,
38
+ children,
39
+ density = "default",
40
+ className,
41
+ ...props
42
+ }: DocumentEditorShellProps) {
43
+ const [outlineOpen, setOutlineOpen] = React.useState(Boolean(outline));
44
+ const hasOutline = Boolean(outline);
45
+ const hasInspector = Boolean(inspector);
46
+ const gridTemplateColumns = [
47
+ hasOutline && outlineOpen ? "minmax(180px, 260px)" : null,
48
+ "minmax(0, 1fr)",
49
+ hasInspector ? "minmax(220px, 320px)" : null
50
+ ]
51
+ .filter(Boolean)
52
+ .join(" ");
53
+ const gridStyle = {
54
+ "--eth-doc-editor-shell-grid-template": gridTemplateColumns
55
+ } as React.CSSProperties;
56
+ const ethComponent = props["data-eth-component"] ?? "DocumentEditorShell";
57
+
58
+ return (
59
+ <section
60
+ {...props}
61
+ className={cx(
62
+ "eth-doc-editor-shell",
63
+ `eth-doc-editor-shell--${density}`,
64
+ hasOutline && "eth-doc-editor-shell--has-outline",
65
+ hasOutline && outlineOpen && "eth-doc-editor-shell--outline-open",
66
+ hasInspector && "eth-doc-editor-shell--has-inspector",
67
+ className
68
+ )}
69
+ data-eth-component={ethComponent}
70
+ >
71
+ <header className="eth-doc-editor-shell__bar">
72
+ <div className="eth-doc-editor-shell__title">
73
+ <p className="eth-eyebrow">{documentRefLabel(documentRef)}</p>
74
+ <h2>{title}</h2>
75
+ </div>
76
+ <div className="eth-doc-editor-shell__status">
77
+ {hasOutline ? (
78
+ <IconButton
79
+ label={outlineOpen ? "Collapse outline" : "Expand outline"}
80
+ intent="ghost"
81
+ density={density}
82
+ icon={
83
+ <ChevronRightIcon
84
+ size={16}
85
+ style={{ transform: outlineOpen ? "rotate(180deg)" : undefined }}
86
+ />
87
+ }
88
+ aria-expanded={outlineOpen}
89
+ onClick={() => setOutlineOpen((current) => !current)}
90
+ />
91
+ ) : null}
92
+ <Badge severity="neutral">{modeLabel(mode)}</Badge>
93
+ <DocumentLockBadge lockState={lockState} owner={lockOwner} />
94
+ </div>
95
+ </header>
96
+
97
+ {toolbar ? <div className="eth-doc-editor-shell__toolbar">{toolbar}</div> : null}
98
+
99
+ <div className="eth-doc-editor-shell__grid" style={gridStyle}>
100
+ {hasOutline && outlineOpen ? (
101
+ <aside className="eth-doc-editor-shell__outline" aria-label="Document outline">
102
+ {outline}
103
+ </aside>
104
+ ) : null}
105
+ <main className="eth-doc-editor-shell__editor" aria-label="Document editor">
106
+ {children}
107
+ </main>
108
+ {inspector ? (
109
+ <aside className="eth-doc-editor-shell__inspector" aria-label="Document inspector">
110
+ {inspector}
111
+ </aside>
112
+ ) : null}
113
+ </div>
114
+ </section>
115
+ );
116
+ }
@@ -0,0 +1,93 @@
1
+ import * as React from "react";
2
+ import { Badge, Tooltip, type EthSeverity } from "@echothink-ui/core";
3
+ import { LockIcon, UnlockIcon } from "@echothink-ui/icons";
4
+ import type { DocumentComponentBaseProps, DocumentLockState, DocumentOwner } from "./types";
5
+ import { cx, formatDateTime } from "./utils";
6
+
7
+ export interface DocumentLockBadgeProps extends Omit<
8
+ DocumentComponentBaseProps<HTMLSpanElement>,
9
+ "title"
10
+ > {
11
+ lockState: DocumentLockState;
12
+ owner?: DocumentOwner;
13
+ since?: string;
14
+ }
15
+
16
+ const lockConfig: Record<
17
+ DocumentLockState,
18
+ { label: string; severity: EthSeverity; tooltip: string }
19
+ > = {
20
+ unlocked: {
21
+ label: "Unlocked",
22
+ severity: "success",
23
+ tooltip: "This document is available for editing."
24
+ },
25
+ "locked-by-user": {
26
+ label: "Locked by you",
27
+ severity: "info",
28
+ tooltip: "You currently hold the document lock."
29
+ },
30
+ "locked-by-agent": {
31
+ label: "Agent lock",
32
+ severity: "warning",
33
+ tooltip: "An agent currently holds the document lock."
34
+ },
35
+ "locked-by-other": {
36
+ label: "Locked",
37
+ severity: "danger",
38
+ tooltip: "Another user currently holds the document lock."
39
+ }
40
+ };
41
+
42
+ export function DocumentLockBadge({
43
+ lockState,
44
+ owner,
45
+ since,
46
+ density = "compact",
47
+ className,
48
+ ...props
49
+ }: DocumentLockBadgeProps) {
50
+ const config = lockConfig[lockState];
51
+ const Icon = lockState === "unlocked" ? UnlockIcon : LockIcon;
52
+ const visibleDetail = getVisibleDetail(lockState, owner, since);
53
+ const ownerText = owner ? ` Owner: ${owner.label}.` : "";
54
+ const sinceText = since ? ` Since: ${formatDateTime(since)}.` : "";
55
+ const tooltip = `${config.tooltip}${ownerText}${sinceText}`;
56
+
57
+ return (
58
+ <Tooltip label={tooltip}>
59
+ <span
60
+ {...props}
61
+ className={cx("eth-doc-lock-badge", `eth-doc-lock-badge--${density}`, className)}
62
+ aria-label={tooltip}
63
+ data-lock-state={lockState}
64
+ data-eth-component="DocumentLockBadge"
65
+ >
66
+ <Badge
67
+ severity={config.severity}
68
+ className={cx("eth-doc-lock-badge__tag", `eth-doc-lock-badge__tag--${lockState}`)}
69
+ >
70
+ <span className="eth-doc-lock-badge__content">
71
+ <Icon className="eth-doc-lock-badge__icon" size={12} />
72
+ <span className="eth-doc-lock-badge__label">{config.label}</span>
73
+ {visibleDetail ? (
74
+ <span className="eth-doc-lock-badge__detail">{visibleDetail}</span>
75
+ ) : null}
76
+ </span>
77
+ </Badge>
78
+ </span>
79
+ </Tooltip>
80
+ );
81
+ }
82
+
83
+ function getVisibleDetail(
84
+ lockState: DocumentLockState,
85
+ owner: DocumentOwner | undefined,
86
+ since: string | undefined
87
+ ) {
88
+ const sinceLabel = since ? formatDateTime(since) : undefined;
89
+ if (lockState === "unlocked") return sinceLabel;
90
+ if (lockState === "locked-by-user") return sinceLabel;
91
+
92
+ return [owner?.label, sinceLabel].filter(Boolean).join(" / ") || undefined;
93
+ }
@@ -0,0 +1,118 @@
1
+ import * as React from "react";
2
+ import type { DocumentComponentBaseProps, DocumentHeading } from "./types";
3
+ import { cx } from "./utils";
4
+
5
+ export interface DocumentOutlineProps extends Omit<
6
+ DocumentComponentBaseProps<HTMLElement>,
7
+ "children" | "title"
8
+ > {
9
+ headings: DocumentHeading[];
10
+ activeHeadingId?: string;
11
+ onSelect?: (id: string) => void;
12
+ }
13
+
14
+ interface OutlineNode extends DocumentHeading {
15
+ children: OutlineNode[];
16
+ }
17
+
18
+ export function DocumentOutline({
19
+ headings,
20
+ activeHeadingId,
21
+ onSelect,
22
+ className,
23
+ "aria-label": ariaLabel,
24
+ ...props
25
+ }: DocumentOutlineProps) {
26
+ const tree = React.useMemo(() => buildOutlineTree(headings), [headings]);
27
+ const headingCountLabel = `${headings.length} section${headings.length === 1 ? "" : "s"}`;
28
+
29
+ return (
30
+ <nav
31
+ {...props}
32
+ className={cx("eth-doc-outline", className)}
33
+ aria-label={ariaLabel ?? "Document outline"}
34
+ data-eth-component="DocumentOutline"
35
+ >
36
+ <div className="eth-doc-outline__header">
37
+ <span className="eth-doc-outline__label">On this page</span>
38
+ <span className="eth-doc-outline__count">{headingCountLabel}</span>
39
+ </div>
40
+ {tree.length ? (
41
+ <ol className="eth-doc-outline__list">{renderNodes(tree, activeHeadingId, onSelect)}</ol>
42
+ ) : (
43
+ <p className="eth-doc-outline__empty" role="status">
44
+ No headings available.
45
+ </p>
46
+ )}
47
+ </nav>
48
+ );
49
+ }
50
+
51
+ function renderNodes(
52
+ nodes: OutlineNode[],
53
+ activeHeadingId: string | undefined,
54
+ onSelect: ((id: string) => void) | undefined
55
+ ): React.ReactNode {
56
+ return nodes.map((node) => {
57
+ const href = node.href ?? `#${node.id}`;
58
+ const isActive = node.id === activeHeadingId;
59
+ const containsActiveHeading = activeHeadingId
60
+ ? node.children.some((child) => containsHeading(child, activeHeadingId))
61
+ : false;
62
+
63
+ return (
64
+ <li
65
+ key={node.id}
66
+ className={cx(
67
+ "eth-doc-outline__item",
68
+ `eth-doc-outline__item--level-${node.level}`,
69
+ isActive && "eth-doc-outline__item--active",
70
+ containsActiveHeading && "eth-doc-outline__item--contains-active"
71
+ )}
72
+ >
73
+ <a
74
+ className="eth-doc-outline__link"
75
+ href={href}
76
+ aria-current={isActive ? "location" : undefined}
77
+ onClick={(event) => {
78
+ if (!href.startsWith("#") && !onSelect) return;
79
+ event.preventDefault();
80
+ onSelect?.(node.id);
81
+ scrollHeadingIntoView(node.href, node.id);
82
+ }}
83
+ >
84
+ <span className="eth-doc-outline__text">{node.text}</span>
85
+ </a>
86
+ {node.children.length ? (
87
+ <ol className="eth-doc-outline__list">
88
+ {renderNodes(node.children, activeHeadingId, onSelect)}
89
+ </ol>
90
+ ) : null}
91
+ </li>
92
+ );
93
+ });
94
+ }
95
+
96
+ function containsHeading(node: OutlineNode, id: string): boolean {
97
+ return node.id === id || node.children.some((child) => containsHeading(child, id));
98
+ }
99
+
100
+ function buildOutlineTree(headings: DocumentHeading[]) {
101
+ const roots: OutlineNode[] = [];
102
+ const stack: Array<{ level: number; children: OutlineNode[] }> = [{ level: 0, children: roots }];
103
+
104
+ for (const heading of headings) {
105
+ const node: OutlineNode = { ...heading, children: [] };
106
+ while (stack.length > 1 && stack[stack.length - 1].level >= heading.level) stack.pop();
107
+ stack[stack.length - 1].children.push(node);
108
+ stack.push(node);
109
+ }
110
+
111
+ return roots;
112
+ }
113
+
114
+ function scrollHeadingIntoView(href: string | undefined, id: string) {
115
+ const targetId = href?.startsWith("#") ? href.slice(1) : id;
116
+ const target = globalThis.document?.getElementById(targetId);
117
+ target?.scrollIntoView({ block: "start", behavior: "smooth" });
118
+ }