Document inherited members in reference (#3956)

Our reference docs don't currently include members inherited through the
`extends` keyword. These extended items are barely referenced at all -
you have to find them in the signature.

This diff adds a clearer note to the docs saying which type has been
extended, and if possible brings the extended items through onto the
current documentation page (with a note saying where they're from)


![image](https://github.com/tldraw/tldraw/assets/1489520/0349252d-e8bc-406b-bf47-636da424ebe0)


### Change Type

- [x] `docs` — Changes to the documentation, examples, or templates.
- [x] `improvement` — Improving existing features
This commit is contained in:
alex 2024-06-17 15:47:22 +01:00 committed by GitHub
parent 12aea7ed68
commit c4b9ea30f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 312 additions and 188 deletions

View file

@ -1,4 +1,5 @@
import classNames from 'classnames'
import Link from 'next/link'
import { ReactNode } from 'react'
import { Icon } from '../Icon'
@ -35,32 +36,56 @@ export function TitleWithSourceLink({
source,
large,
tags,
inherited,
}: {
children: ReactNode
source?: string | null
large?: boolean
tags?: string[]
inherited?: { name: string; link: string }
}) {
return (
<div
className={classNames(
'article__title-with-source-link',
large && 'article__title-with-source-link--large'
)}
>
{children}
<div className="article__title-with-source-link__meta">
{tags?.map((tag) => <Tag key={tag}>{tag}</Tag>)}
{source && (
<a href={source} target="_blank" rel="noopener noreferrer" title="Source code">
<Icon icon="code" />
</a>
<>
<div
className={classNames(
'article__title-with-source-link',
large && 'article__title-with-source-link--large'
)}
>
{children}
<div className="article__title-with-source-link__meta">
{tags?.map((tag) => (
<Tag key={tag} tag={tag}>
{tag}
</Tag>
))}
{source && (
<a
href={source}
target="_blank"
rel="noopener noreferrer"
title="Source code"
className="article__title-with-source-link__source"
>
<Icon icon="code" />
</a>
)}
</div>
</div>
</div>
{inherited && (
<div className="article__title-with-source-link__from">
from{' '}
<code className="hljs">
<Link href={inherited.link} className="code-link">
{inherited.name}
</Link>
</code>
</div>
)}
</>
)
}
export function Tag({ children }: { children: string }) {
return <span className={classNames(`article__tag`, `article__tag--${children}`)}>{children}</span>
export function Tag({ children, tag }: { children: ReactNode; tag: string }) {
return <span className={classNames(`article__tag`, `article__tag--${tag}`)}>{children}</span>
}

View file

@ -85,4 +85,6 @@ export async function createApiMarkdown() {
)
sectionsJson.push(apiInputSection)
fs.writeFileSync(sectionsJsonPath, JSON.stringify(sectionsJson, null, '\t') + '\n')
model.throwEncounteredErrors()
}

View file

@ -22,6 +22,7 @@ import {
ApiTypeAlias,
ApiVariable,
Excerpt,
HeritageType,
} from '@microsoft/api-extractor-model'
import { MarkdownWriter, formatWithPrettier, getPath, getSlug } from '../utils'
@ -36,6 +37,11 @@ const date = new Intl.DateTimeFormat('en-US', {
day: '2-digit',
}).format(new Date())
interface Member {
item: ApiItem
inheritedFrom: ApiItem | null
}
export async function getApiMarkdown(
model: TldrawApiModel,
categoryName: string,
@ -49,100 +55,59 @@ export async function getApiMarkdown(
const isComponent = model.isComponent(item)
const componentProps = isComponent ? model.getReactPropsItem(item) : null
const members = componentProps?.members ?? item.members
if (members) {
const constructors = []
const properties = []
const methods = []
for (const member of members) {
switch (member.kind) {
case ApiItemKind.Constructor:
case ApiItemKind.ConstructSignature:
constructors.push(member)
break
case ApiItemKind.Variable:
case ApiItemKind.Property:
case ApiItemKind.PropertySignature:
properties.push(member)
break
case ApiItemKind.Method:
case ApiItemKind.Function:
case ApiItemKind.MethodSignature:
if (isComponent) {
properties.push(member)
} else {
methods.push(member)
}
break
case ApiItemKind.EnumMember:
case ApiItemKind.Class:
case ApiItemKind.TypeAlias:
case ApiItemKind.Interface:
// TODO: document these
break
default:
throw new Error(`Unknown member kind: ${member.kind} ${member.displayName}`)
}
}
const contents = collectMembersAndExtends(model, item)
if (contents.constructors.length) {
const constructorResult: Result = { markdown: '', keywords: [] }
for (const member of contents.constructors) {
await addMarkdownForMember(model, constructorResult, member)
addHorizontalRule(constructorResult)
}
addMarkdown(membersResult, constructorResult.markdown)
}
if (contents.properties.length || isComponent) {
const propertiesResult: Result = { markdown: '', keywords: [] }
if (componentProps) addExtends(propertiesResult, item, contents.heritage)
for (const member of contents.properties) {
const slug = getSlug(member.item)
addMarkdown(toc, ` - [${member.item.displayName}](#${slug})\n`)
await addMarkdownForMember(model, propertiesResult, member, {
isComponentProp: isComponent,
})
addHorizontalRule(propertiesResult)
}
if (
componentProps &&
componentProps instanceof ApiDeclaredItem &&
componentProps?.kind !== 'Interface'
) {
propertiesResult.markdown += await excerptToMarkdown(componentProps, componentProps.excerpt, {
kind: componentProps.kind,
})
}
if (propertiesResult.markdown.trim()) {
addMarkdown(toc, `- [Properties](#properties)\n`)
addMarkdown(membersResult, `## Properties\n\n`)
addMarkdown(membersResult, propertiesResult.markdown)
} else if (isComponent && !componentProps) {
addMarkdown(membersResult, `## Properties\n\n`)
addMarkdown(membersResult, `This component does not take any props.\n\n`)
}
}
if (contents.methods.length) {
const methodsResult: Result = { markdown: '', keywords: [] }
if (constructors.length) {
for (const member of constructors) {
await addMarkdownForMember(model, constructorResult, member)
addHorizontalRule(constructorResult)
}
addMarkdown(membersResult, constructorResult.markdown)
}
if (properties.length || isComponent) {
if (componentProps) addExtends(propertiesResult, componentProps)
for (const member of properties) {
const slug = getSlug(member)
addMarkdown(toc, ` - [${member.displayName}](#${slug})\n`)
await addMarkdownForMember(model, propertiesResult, member, {
isComponentProp: isComponent,
})
addHorizontalRule(propertiesResult)
}
if (
componentProps &&
componentProps instanceof ApiDeclaredItem &&
componentProps?.kind !== 'Interface'
) {
propertiesResult.markdown += await excerptToMarkdown(
componentProps,
componentProps.excerpt,
{
kind: componentProps.kind,
}
)
}
if (propertiesResult.markdown.trim()) {
addMarkdown(toc, `- [Properties](#properties)\n`)
addMarkdown(membersResult, `## Properties\n\n`)
addMarkdown(membersResult, propertiesResult.markdown)
} else if (isComponent && !componentProps) {
addMarkdown(membersResult, `## Properties\n\n`)
addMarkdown(membersResult, `This component does not take any props.\n\n`)
}
}
if (methods.length) {
addMarkdown(toc, `- [Methods](#methods)\n`)
addMarkdown(methodsResult, `## Methods\n\n`)
for (const member of methods) {
const slug = getSlug(member)
addMarkdown(toc, ` - [${member.displayName}](#${slug})\n`)
await addMarkdownForMember(model, methodsResult, member)
addHorizontalRule(methodsResult)
}
addMarkdown(membersResult, methodsResult.markdown)
addMarkdown(toc, `- [Methods](#methods)\n`)
addMarkdown(methodsResult, `## Methods\n\n`)
for (const member of contents.methods) {
const slug = getSlug(member.item)
addMarkdown(toc, ` - [${member.item.displayName}](#${slug})\n`)
await addMarkdownForMember(model, methodsResult, member)
addHorizontalRule(methodsResult)
}
addMarkdown(membersResult, methodsResult.markdown)
}
await addFrontmatter(model, result, item, categoryName, j)
@ -153,6 +118,10 @@ export async function getApiMarkdown(
result.markdown += `</details>\n\n`
}
if (!isComponent) {
addExtends(result, item, contents.heritage)
}
await addDocComment(model, result, item)
if (membersResult.markdown.length) {
@ -165,6 +134,108 @@ export async function getApiMarkdown(
/* --------------------- Helpers -------------------- */
function sortMembers(members: Member[]) {
return members.sort((a, b) => {
const aIsStatic = ApiStaticMixin.isBaseClassOf(a.item) && a.item.isStatic
const bIsStatic = ApiStaticMixin.isBaseClassOf(b.item) && b.item.isStatic
if (aIsStatic && !bIsStatic) return -1
if (!aIsStatic && bIsStatic) return 1
return a.item.displayName.localeCompare(b.item.displayName)
})
}
function collectMembersAndExtends(model: TldrawApiModel, item: ApiItem) {
const isComponent = model.isComponent(item)
const constructors: Member[] = []
const properties: Member[] = []
const methods: Member[] = []
const heritage: HeritageType[] = []
function visit(item: ApiItem, inheritedFrom: ApiItem | null) {
if (item.members) {
for (const member of item.members) {
switch (member.kind) {
case ApiItemKind.Constructor:
case ApiItemKind.ConstructSignature:
addMember(constructors, member, inheritedFrom)
break
case ApiItemKind.Variable:
case ApiItemKind.Property:
case ApiItemKind.PropertySignature:
addMember(properties, member, inheritedFrom)
break
case ApiItemKind.Method:
case ApiItemKind.Function:
case ApiItemKind.MethodSignature:
if (isComponent) {
addMember(properties, member, inheritedFrom)
} else {
addMember(methods, member, inheritedFrom)
}
break
case ApiItemKind.EnumMember:
case ApiItemKind.Class:
case ApiItemKind.TypeAlias:
case ApiItemKind.Interface:
// TODO: document these
break
default:
model.nonBlockingError(
member,
`Unknown member kind: ${member.kind} in ${item.displayName}`
)
break
}
}
}
if (model.isComponent(item)) {
const componentProps = model.getReactPropsItem(item)
if (componentProps) {
visit(componentProps, null)
}
}
const extendsTypes =
item instanceof ApiClass && item.extendsType
? [item.extendsType]
: item instanceof ApiInterface
? item.extendsTypes
: []
for (const extendsType of extendsTypes) {
if (!inheritedFrom) {
heritage.push(extendsType)
}
const tokens = extendsType.excerpt.spannedTokens
let extendedItem = null
if (tokens[0].kind === 'Reference') {
extendedItem = model.tryResolveToken(item, tokens[0])
}
if (extendedItem) {
visit(extendedItem, extendedItem)
}
}
}
visit(item, null)
sortMembers(constructors)
sortMembers(properties)
sortMembers(methods)
return { constructors, properties, methods, heritage }
}
function addMember(members: Member[], item: ApiItem, inheritedFrom: ApiItem | null) {
if (members.some((m) => m.item.displayName === item.displayName)) return
members.push({ item, inheritedFrom })
}
function addMarkdown(result: Result, markdown: string) {
result.markdown += markdown
}
@ -172,12 +243,12 @@ function addMarkdown(result: Result, markdown: string) {
async function addMarkdownForMember(
model: TldrawApiModel,
result: Result,
member: ApiItem,
{ item, inheritedFrom }: Member,
{ isComponentProp = false } = {}
) {
if (member.displayName.startsWith('_')) return
addMemberNameAndMeta(result, model, member, { isComponentProp })
await addDocComment(model, result, member)
if (item.displayName.startsWith('_')) return
addMemberNameAndMeta(result, model, item, { isComponentProp, inheritedFrom })
await addDocComment(model, result, item)
}
async function addFrontmatter(
@ -251,24 +322,27 @@ function addMemberNameAndMeta(
result: Result,
model: TldrawApiModel,
item: ApiItem,
{ level = 3, isComponentProp = false } = {}
{
level = 3,
isComponentProp = false,
inheritedFrom = null,
}: { level?: number; isComponentProp?: boolean; inheritedFrom?: ApiItem | null } = {}
) {
const heading = `${'#'.repeat(level)} ${getItemTitle(item)}`
if (item instanceof ApiDeclaredItem && item.sourceLocation.fileUrl) {
const source = item.sourceLocation.fileUrl
const tags = getTags(model, item, { isComponentProp, includeKind: false })
result.markdown += [
`<TitleWithSourceLink source={${JSON.stringify(source)}} tags={${JSON.stringify(tags)}}>`,
'',
heading,
'',
'</TitleWithSourceLink>',
'',
].join('\n')
} else {
result.markdown += `${heading}\n\n`
}
const inherited = inheritedFrom
? { name: inheritedFrom.displayName, link: `/reference/${getPath(inheritedFrom)}` }
: null
const tags = getTags(model, item, { isComponentProp, includeKind: false })
result.markdown += [
`<TitleWithSourceLink tags={${JSON.stringify(tags)}} inherited={${JSON.stringify(inherited)}}>`,
'',
heading,
'',
'</TitleWithSourceLink>',
'',
].join('\n')
}
async function addDocComment(model: TldrawApiModel, result: Result, member: ApiItem) {
@ -395,7 +469,7 @@ async function addDocComment(model: TldrawApiModel, result: Result, member: ApiI
result.markdown += '</ParametersTable>\n\n'
}
} else {
model.error(member, `Unknown member kind: ${member.kind}`)
model.nonBlockingError(member, `Unknown member kind: ${member.kind}`)
}
}
@ -514,18 +588,11 @@ function getTags(
return tags
}
function addExtends(result: Result, item: ApiItem) {
const extendsTypes =
item instanceof ApiClass && item.extendsType
? [item.extendsType]
: item instanceof ApiInterface
? item.extendsTypes
: []
if (!extendsTypes.length) return
function addExtends(result: Result, item: ApiItem, heritage: HeritageType[]) {
if (!heritage.length) return
const links = {} as Record<string, string>
for (const type of extendsTypes) {
for (const type of heritage) {
for (const token of type.excerpt.spannedTokens) {
if (!token.canonicalReference) continue
@ -543,7 +610,7 @@ function addExtends(result: Result, item: ApiItem) {
result.markdown += [
`<CodeLinkProvider links={${JSON.stringify(links)}}>`,
'',
`Extends \`${extendsTypes.map((type) => type.excerpt.text).join(', ')}\`.`,
`Extends \`${heritage.map((type) => type.excerpt.text).join(', ')}\`.`,
'',
'</CodeLinkProvider>',
'',

View file

@ -309,7 +309,7 @@ body {
margin-left: auto;
padding-left: 16px;
}
.article__title-with-source-link__meta > a {
.article__title-with-source-link__source {
display: block;
width: 32px;
height: 32px;
@ -318,11 +318,11 @@ body {
align-items: center;
justify-content: center;
}
.article__title-with-source-link .article__title-with-source-link__meta > a {
color: var(--color-text);
.article__title-with-source-link__source {
color: var(--color-text) !important;
}
.article__title-with-source-link > a:hover {
.article__title-with-source-link__source:hover {
background-color: var(--color-tint-1);
}
.article__title-with-source-link .icon {
@ -330,7 +330,7 @@ body {
width: 20px;
height: 20px;
}
.article__title-with-source-link--large .article__title-with-source-link__meta > a {
.article__title-with-source-link--large .article__title-with-source-link__source {
width: 42px;
height: 42px;
}
@ -342,15 +342,21 @@ body {
display: inline-block;
padding: 4px 6px;
border-radius: var(--border-radius-menu);
background-color: var(--color-tint-0);
background-color: var(--color-tint-1);
color: var(--color-tint-5);
font-size: 12px;
margin-right: 8px;
line-height: 1;
white-space: nowrap;
}
.article__title-with-source-link--large .article__tag {
font-size: 14px;
}
.article__title-with-source-link__from {
font-size: 12px;
line-height: 1.5;
color: var(--color-tint-5);
}
/* Prev / Next Links */

View file

@ -83,8 +83,8 @@
}
a.code-link::before {
content: '';
width: 15px;
height: 15px;
width: 1.0714em; /* 15px when font-size is 14px */
height: 1.0714em;
display: inline-block;
background: currentColor;
-webkit-mask: url('data:image/svg+xml;utf8,<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.5 2.5H3.5C2.94772 2.5 2.5 2.94772 2.5 3.5V11.5C2.5 12.0523 2.94772 12.5 3.5 12.5H11.5C12.0523 12.5 12.5 12.0523 12.5 11.5V8.5M9.5 2.5H12.5M12.5 2.5V5.5M12.5 2.5L6.5 8.5" stroke="black" stroke-linecap="round" stroke-linejoin="round"/></svg>')
@ -93,6 +93,6 @@ a.code-link::before {
no-repeat 50% 50%;
-webkit-mask-size: cover;
mask-size: cover;
vertical-align: -2px;
margin-right: 2px;
vertical-align: -0.142em; /* 2px when font-size is 14px */
margin-right: 0.142em;
}

View file

@ -15,8 +15,9 @@ export class TldrawApiModel extends ApiModel {
private reactComponents = new Set<ApiItem>()
private reactComponentProps = new Set<ApiItem>()
nonBlockingErrors: Error[] = []
async preprocessReactComponents() {
const errors = []
for (const packageModel of this.members) {
assert(packageModel instanceof ApiPackage)
if (packageModel.name !== 'tldraw') continue
@ -37,22 +38,18 @@ export class TldrawApiModel extends ApiModel {
props.tsdocComment.summarySection
)
if (markdown.trim()) {
this.error(
this.nonBlockingError(
props,
"Component props should not contain documentation as it won't be included in the docs site. Add it to the component instead."
)
}
}
if (props) this.reactComponentProps.add(props)
} catch (e) {
errors.push(e)
} catch (e: any) {
this.nonBlockingErrors.push(e)
}
}
}
if (errors.length > 0) {
throw new Error(errors.map((e) => (e as any).message).join('\n\n'))
}
}
resolveToken(origin: ApiItem, token: ExcerptToken) {
@ -63,6 +60,14 @@ export class TldrawApiModel extends ApiModel {
return apiItemResult.resolvedApiItem!
}
tryResolveToken(origin: ApiItem, token: ExcerptToken) {
const apiItemResult = this.resolveDeclarationReference(token.canonicalReference!, origin)
if (apiItemResult.errorMessage) {
return null
}
return apiItemResult.resolvedApiItem!
}
getReactPropsItem(component: ApiItem): ApiItem | null {
if (component instanceof ApiFunction) {
if (component.parameters.length === 0) return null
@ -84,10 +89,11 @@ export class TldrawApiModel extends ApiModel {
return this.resolveToken(component, tokens[0])
}
this.error(
this.nonBlockingError(
component,
`Expected props parameter to be a simple reference. Rewrite this to use a \`${component.displayName}Props\` interface.\nFound: ${propsParam.parameterTypeExcerpt.text}`
)
return null
} else if (component instanceof ApiVariable) {
const tokens = component.variableTypeExcerpt.spannedTokens
if (
@ -130,12 +136,16 @@ export class TldrawApiModel extends ApiModel {
return null
}
this.error(
this.nonBlockingError(
component,
`Expected a simple props interface for react component. Got: ${component.variableTypeExcerpt.text}`
)
return null
} else {
this.error(component, `Unknown item kind for @react component: ${component.kind}`)
this.nonBlockingError(component, `Unknown item kind for @react component: ${component.kind}`)
return null
}
}
@ -147,12 +157,26 @@ export class TldrawApiModel extends ApiModel {
return this.reactComponentProps.has(item)
}
error(item: ApiItem, message: string): never {
private createError(item: ApiItem, message: string) {
const suffix =
'_fileUrlPath' in item && typeof item._fileUrlPath === 'string'
? `\nin ${item._fileUrlPath}`
: ''
throw new Error(`${item.displayName}: ${message}${suffix}`)
return new Error(`${item.displayName}: ${message}${suffix}`)
}
nonBlockingError(item: ApiItem, message: string) {
this.nonBlockingErrors.push(this.createError(item, message))
}
throwEncounteredErrors() {
if (this.nonBlockingErrors.length > 0) {
throw new Error(this.nonBlockingErrors.map((e) => (e as any).message).join('\n\n'))
}
}
error(item: ApiItem, message: string): never {
throw this.createError(item, message)
}
assert(item: ApiItem, condition: unknown, message: string): asserts condition {

View file

@ -1045,13 +1045,13 @@ export class Editor extends EventEmitter<TLEventMap> {
getStateDescendant<T extends StateNode>(path: string): T | undefined;
getStyleForNextShape<T>(style: StyleProp<T>): T;
// @deprecated (undocumented)
getSvg(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<SVGSVGElement | undefined>;
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<{
getSvg(shapes: TLShape[] | TLShapeId[], opts?: TLSvgOptions): Promise<SVGSVGElement | undefined>;
getSvgElement(shapes: TLShape[] | TLShapeId[], opts?: TLSvgOptions): Promise<{
height: number;
svg: SVGSVGElement;
width: number;
} | undefined>;
getSvgString(shapes: TLShape[] | TLShapeId[], opts?: Partial<TLSvgOptions>): Promise<{
getSvgString(shapes: TLShape[] | TLShapeId[], opts?: TLSvgOptions): Promise<{
height: number;
svg: string;
width: number;
@ -3299,17 +3299,17 @@ export type TLStoreWithStatus = {
// @public (undocumented)
export interface TLSvgOptions {
// (undocumented)
background: boolean;
background?: boolean;
// (undocumented)
bounds: Box;
bounds?: Box;
// (undocumented)
darkMode?: boolean;
// (undocumented)
padding: number;
padding?: number;
// (undocumented)
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio'];
// (undocumented)
scale: number;
scale?: number;
}
// @public (undocumented)

View file

@ -8224,7 +8224,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
async getSvgElement(shapes: TLShapeId[] | TLShape[], opts: TLSvgOptions = {}) {
const result = await getSvgJsx(this, shapes, opts)
if (!result) return undefined
@ -8251,7 +8251,7 @@ export class Editor extends EventEmitter<TLEventMap> {
*
* @public
*/
async getSvgString(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
async getSvgString(shapes: TLShapeId[] | TLShape[], opts: TLSvgOptions = {}) {
const result = await this.getSvgElement(shapes, opts)
if (!result) return undefined
@ -8264,7 +8264,7 @@ export class Editor extends EventEmitter<TLEventMap> {
}
/** @deprecated Use {@link Editor.getSvgString} or {@link Editor.getSvgElement} instead. */
async getSvg(shapes: TLShapeId[] | TLShape[], opts = {} as Partial<TLSvgOptions>) {
async getSvg(shapes: TLShapeId[] | TLShape[], opts: TLSvgOptions = {}) {
const result = await this.getSvgElement(shapes, opts)
if (!result) return undefined
return result.svg

View file

@ -13,7 +13,7 @@ import { TLSvgOptions } from './types/misc-types'
export async function getSvgJsx(
editor: Editor,
shapes: TLShapeId[] | TLShape[],
opts = {} as Partial<TLSvgOptions>
opts: TLSvgOptions = {}
) {
const ids =
typeof shapes[0] === 'string' ? (shapes as TLShapeId[]) : (shapes as TLShape[]).map((s) => s.id)

View file

@ -9,12 +9,12 @@ export type OptionalKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>
/** @public */
export interface TLSvgOptions {
bounds: Box
scale: number
background: boolean
padding: number
bounds?: Box
scale?: number
background?: boolean
padding?: number
darkMode?: boolean
preserveAspectRatio: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
preserveAspectRatio?: React.SVGAttributes<SVGSVGElement>['preserveAspectRatio']
}
/** @public */

View file

@ -359,7 +359,7 @@ export function ConvertToBookmarkMenuItem(): JSX_2.Element | null;
export function ConvertToEmbedMenuItem(): JSX_2.Element | null;
// @public
export function copyAs(editor: Editor, ids: TLShapeId[], format?: TLCopyType, opts?: Partial<TLSvgOptions>): Promise<void>;
export function copyAs(editor: Editor, ids: TLShapeId[], format?: TLCopyType, opts?: TLSvgOptions): Promise<void>;
// @public (undocumented)
export function CopyAsMenuGroup(): JSX_2.Element;
@ -634,7 +634,7 @@ export interface ExampleDialogProps {
}
// @public
export function exportAs(editor: Editor, ids: TLShapeId[], format: TLExportType | undefined, name: string | undefined, opts?: Partial<TLSvgOptions>): Promise<void>;
export function exportAs(editor: Editor, ids: TLShapeId[], format: TLExportType | undefined, name: string | undefined, opts?: TLSvgOptions): Promise<void>;
// @public (undocumented)
export function ExportFileContentSubMenu(): JSX_2.Element;
@ -644,7 +644,7 @@ export function exportToBlob({ editor, ids, format, opts, }: {
editor: Editor;
format: TLExportType;
ids: TLShapeId[];
opts?: Partial<TLSvgOptions>;
opts?: TLSvgOptions;
}): Promise<Blob>;
// @public (undocumented)
@ -1660,7 +1660,7 @@ export function TldrawHandles({ children }: TLHandlesProps): JSX_2.Element | nul
export const TldrawImage: NamedExoticComponent<TldrawImageProps>;
// @public (undocumented)
export interface TldrawImageProps extends Partial<TLSvgOptions> {
export interface TldrawImageProps extends TLSvgOptions {
bindingUtils?: readonly TLAnyBindingUtilConstructor[];
format?: 'png' | 'svg';
pageId?: TLPageId;

View file

@ -20,7 +20,7 @@ import { getSvgAsImage } from './utils/export/export'
import { useDefaultEditorAssetsWithOverrides } from './utils/static-assets/assetUrls'
/** @public */
export interface TldrawImageProps extends Partial<TLSvgOptions> {
export interface TldrawImageProps extends TLSvgOptions {
/**
* The snapshot to display.
*/

View file

@ -18,7 +18,7 @@ export function copyAs(
editor: Editor,
ids: TLShapeId[],
format: TLCopyType = 'svg',
opts = {} as Partial<TLSvgOptions>
opts: TLSvgOptions = {}
): Promise<void> {
// Note: it's important that this function itself isn't async and doesn't really use promises -
// we need to create the relevant `ClipboardItem`s and call window.navigator.clipboard.write

View file

@ -96,7 +96,7 @@ export async function getSvgAsImage(
}
}
async function getSvgString(editor: Editor, ids: TLShapeId[], opts: Partial<TLSvgOptions>) {
async function getSvgString(editor: Editor, ids: TLShapeId[], opts: TLSvgOptions) {
const svg = await editor.getSvgString(ids?.length ? ids : [...editor.getCurrentPageShapeIds()], {
scale: 1,
background: editor.getInstanceState().exportBackground,
@ -112,7 +112,7 @@ export async function exportToString(
editor: Editor,
ids: TLShapeId[],
format: 'svg' | 'json',
opts = {} as Partial<TLSvgOptions>
opts: TLSvgOptions = {}
) {
switch (format) {
case 'svg': {
@ -141,12 +141,12 @@ export async function exportToBlob({
editor,
ids,
format,
opts = {} as Partial<TLSvgOptions>,
opts = {},
}: {
editor: Editor
ids: TLShapeId[]
format: TLExportType
opts?: Partial<TLSvgOptions>
opts?: TLSvgOptions
}): Promise<Blob> {
switch (format) {
case 'svg':
@ -188,7 +188,7 @@ export function exportToBlobPromise(
editor: Editor,
ids: TLShapeId[],
format: TLExportType,
opts = {} as Partial<TLSvgOptions>
opts: TLSvgOptions = {}
): { blobPromise: Promise<Blob>; mimeType: string } {
return {
blobPromise: exportToBlob({ editor, ids, format, opts }),

View file

@ -20,7 +20,7 @@ export async function exportAs(
ids: TLShapeId[],
format: TLExportType = 'png',
name: string | undefined,
opts = {} as Partial<TLSvgOptions>
opts: TLSvgOptions = {}
) {
// If we don't get name then use a predefined one
if (!name) {