Skip to content

Instantly share code, notes, and snippets.

@brianfoody
Last active October 4, 2022 07:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brianfoody/86060ad9dc1132c4a366e80dcbd25f1f to your computer and use it in GitHub Desktop.
Save brianfoody/86060ad9dc1132c4a366e80dcbd25f1f to your computer and use it in GitHub Desktop.
tldrraw scrollable HTML element
src/shapes/scrollbox
// Shape
import type { TLShape } from '@tldraw/core'
export interface ScrollBoxShape extends TLShape {
type: 'scrollbox'
size: number[]
}
// Indicator
import { TLShapeUtil } from '@tldraw/core'
import * as React from 'react'
import type { ScrollBoxShape } from './ScrollBoxShape'
export const ScrollBoxIndicator = TLShapeUtil.Indicator<ScrollBoxShape>(({ shape }) => {
return (
<rect
pointerEvents="none"
width={shape.size[0]}
height={shape.size[1]}
fill="none"
stroke="tl-selectedStroke"
strokeWidth={0}
rx={4}
/>
)
})
// Component
import { HTMLContainer, TLShapeUtil } from '@tldraw/core'
import * as React from 'react'
import type { ScrollBoxShape } from './ScrollBoxShape'
export const ScrollBoxComponent = TLShapeUtil.Component<ScrollBoxShape, HTMLDivElement>(
({ shape, events, isGhost, meta, isEditing }, ref) => {
const color = meta.isDarkMode ? 'white' : 'black'
return (
<HTMLContainer ref={ref} {...events}>
<div
style={{
width: shape.size[0],
height: shape.size[1],
borderWidth: 3,
borderColor: color,
borderStyle: 'solid',
borderRadius: 4,
opacity: isGhost ? 0.3 : 1,
pointerEvents: 'all',
background: 'transparent',
overflow: 'scroll',
}}
onWheel={(e) => {
e.stopPropagation()
}}
>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map((i) => (
<div key={`scrollBox-${i}`} style={{ paddingTop: 16, paddingBottom: 16 }}>
<p>Element {i}</p>
</div>
))}
</div>
</HTMLContainer>
)
}
)
// Util
import { TLBounds, Utils } from '@tldraw/core'
import { intersectLineSegmentBounds } from '@tldraw/intersect'
import { nanoid } from 'nanoid'
import { CustomShapeUtil } from 'shapes/CustomShapeUtil'
import { ScrollBoxComponent } from './ScrollBoxComponent'
import { ScrollBoxIndicator } from './ScrollBoxIndicator'
import type { ScrollBoxShape } from './ScrollBoxShape'
type T = ScrollBoxShape
type E = HTMLDivElement
export class ScrollBoxUtil extends CustomShapeUtil<T, E> {
Component = ScrollBoxComponent
Indicator = ScrollBoxIndicator
isAspectRatioLocked = true
canEdit = true
hideResizeHandles = false
getBounds = (shape: T) => {
const bounds = Utils.getFromCache(this.boundsCache, shape, () => {
const [width, height] = shape.size
return {
minX: 0,
maxX: width,
minY: 0,
maxY: height,
width,
height,
} as TLBounds
})
return Utils.translateBounds(bounds, shape.point)
}
/* ----------------- Custom Methods ----------------- */
canBind = true
getShape = (props: Partial<T>): T => {
return {
id: nanoid(),
type: 'scrollbox',
name: 'ScrollBox',
parentId: 'page1',
point: [0, 0],
size: [100, 100],
childIndex: 1,
...props,
}
}
shouldRender = (prev: T, next: T) => {
return next.size !== prev.size
}
getCenter = (shape: T) => {
return Utils.getBoundsCenter(this.getBounds(shape))
}
hitTestPoint = (shape: T, point: number[]) => {
return Utils.pointInBounds(point, this.getBounds(shape))
}
hitTestLineSegment = (shape: T, A: number[], B: number[]) => {
return intersectLineSegmentBounds(A, B, this.getBounds(shape)).length > 0
}
transform = (shape: T, bounds: TLBounds, initialShape: T, scale: number[]) => {
shape.point = [bounds.minX, bounds.minY]
shape.size = [bounds.width, bounds.height]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment