Drawing
The React half: the hooks a surface is built on, the components it draws labels, maths and rich text with, the footer controls, and the geometry behind a mark.
@internote/canvas-sdk@internote/canvas-sdk/reactAlso exported here, documented under @internote/canvas-sdk/host: BlankRenderContext, BlankElementRenderer.
#Components
It is always type="button", so it never submits a form. Any other button attribute, className included, reaches the button.
<Button icon="play" iconOnly aria-label="Play" />Also takes className, id and style. Any other prop is passed to the rendered element.
activebooleaniconBuiltinIconName | React.ReactElementiconOnlybooleanvariantdefaultprimaryghost'default'The slope is {{#f.slope}}. renders as.Also takes className. Any other prop is passed to the rendered element.
expressionnull shows an em dash.ChalkExpression | nulloptionsLiveValueFormatOptionsvariablesRecord<string, number>region.Takes no className, style or id.
regionRegionspechighlightSpec.HighlightSpecspan, drawn in the current text colour at the current font size.A glyph element is drawn as given; otherwise name picks one of the built-in 24×24 paths, and with neither the span holds an empty SVG. The built-in SVG is hidden from screen readers, so an icon that carries meaning needs an aria-label.
Also takes className. Any other prop is passed to the rendered element.
aria-labelspan.stringglyphname.React.ReactElementnameplaypauserestartloopnextprevfullscreenexit-fullscreencopychecksizenumber | string'1em'Takes no className, style or id.
itemsunknown!format:comma{…}, !truncate:8{…} or !pad:6{…}, applied to its body's current text.Takes no className, style or id.
attributesas: and places: for format, by name.Record<string, unknown>itemsunknownnameformatupperlowertitletruncatepadLiveValueStore the store every live value beneath it publishes to and reads from.Without a provider, reads come back undefined, which displays as an em dash, and publishing does nothing.
Takes no className, style or id.
childrenReact.ReactNodestoreLiveValueStorereact-katex writes its output through dangerouslySetInnerHTML, so every render replaces a subtree of dozens of spans — the browser re-parses that HTML and lays it out again whether or not a character changed. An environment with a running animation renders every frame, which made every label on a plot and every reading in a footer a full typeset and relayout per frame.
Memoised on its source, so a formula that has not changed is not touched. Everything that sets maths goes through here. Notation that does not parse shows as the source text rather than an error.
Takes no className, style or id.
blockbooleanNot the same thing as display, and the difference is not only layout. display sets \displaystyle on an inline formula, which grows the glyphs and puts limits over their operators — right for a reading in a footer, which has to sit in the row beside its label. Display mode is the mode itself, and some notation is admitted in no other: align* and the other alignment environments are refused inline, whatever \displaystyle says, so a body that aligns has to come through here.
childrenstringdisplaybooleanTakes no className, style or id.
sweptbooleanThe same row whoever is driving the letter. A cue.parameter sweeping through its years gets exactly what a hand-dragged parameter gets, because a reader looking at a moving number wants the same two things — to read it, and to be able to take hold of it. Dragging a swept one displaces it and it plays on from there; the transport that plays it sits in the TOOLBAR, where it drives the step's whole choreography rather than this one letter, and this panel folds away, which is no reason for the clock to stop.
Takes no className, style or id.
axisParameterAxisonChange(next: number) => voidvaluenumberThe box is deliberately roomy and transparent; the flex alignment inside it is what puts the text exactly where align and baseline say. Nothing is clipped to it, so a label never loses a descender to its own container.
Also takes className. Any other prop is passed to the rendered element.
alignstartmiddleend'middle'baselineabovebelowcentre'above'colourstringcontentunknownmaxWidthnumberrotatenumbersizenumberxnumberynumber!symbol, drawn at the size and colour of the text around it.The SVG is labelled rather than hidden: a symbol stands in for a word (a tick for "yes", a house for "house"), so a reader who cannot see it loses the sentence, not a decoration.
Also takes className. Any other prop is passed to the rendered element.
namename: the author wrote. An unknown one draws nothing.string#Hooks
<S>(ctx: EnvironmentContext, initial: () => S): [
S,
(updater: (previous: S) => S) => void
]#nodeId.member, live; undefined without a provider or publisher.(nodeId: string | null, member: string | null): number | undefinedLiveValuesProvider's store, or null outside one.(): LiveValueStore | nullref for as long as it is mounted and enabled.<V>(ref: React.RefObject<HTMLElement | null>, options: PanZoomOptions<V>): void(entries: ReadonlyArray<LiveValueEntry>): voidUnmounting deliberately clears NOTHING. An environment leaves the tree when its scene scrolls away, but its session state — the reader's parameters, the fit they chose — persists, and the environment will resume it on return. The published values are the prose-visible face of that same state, so they persist with it; clearing on unmount would flash every citation back to an em dash whenever the reader scrolled past the environment's scene.
(
containerRef: React.RefObject<HTMLElement | null>,
svgRef: React.RefObject<SVGSVGElement | null>,
presenting: boolean,
): SurfaceMetricspresenting is ctx.isPresenting; passing it is what schedules the settle-measure after the portal restage.
channel, re-rendering when it changes.<T>(channel: TransientValue<T>, where: Addressed): T | nullchannel for this environment.<T>(channel: TransientCollection<T>, where: Addressed): readonly T[]#Functions
(
placement: LabelBoxPlacement,
anchorX: number,
anchorY: number,
u: number,
clear?: number,
): {
x1: number;
y1: number;
x2: number;
y2: number;
} | nullIt starts clear of the mark's own ink and stops at the edge of the label's box, so it points at the words without touching either — a leader that runs under the letters reads as a strikethrough. Null when the two are close enough that the line would be shorter than the gaps at its ends, which is the case a leader is not for.
cos, sin), in pixels.(
tipX: number,
tipY: number,
cos: number,
sin: number,
arrowSize: number,
base: number,
): {
points: string;
trimX: number;
trimY: number;
}points is the four-point SVG polygon; base is its length from tip to back and arrowSize sets its width. trimX/trimY is where the line it caps should end, inside the notch, so the stroke does not poke through the tip.
(x: number, y: number, r: number, type: string, n: number): Vec2[]& of its own, outside any environment it opens for itself.(latex: string): booleancue.highlight as the rings to draw right now: none before the anchor, deepest mid-window, gone by the end.(
node: CompiledNode,
runtime: EnvironmentContext["animation"],
unit: number,
id: string,
): HighlightSpec | null(nodes: CompiledNode[], runtime: EnvironmentContext["animation"]): booleanx^2/4 or sqrt(2), into LaTeX that KaTeX can render.(input: string): stringusePublishLiveValues, which renders nothing.({ entries }: { entries: ReadonlyArray<LiveValueEntry>; }): nulline-rt-* classes.(nodes: RichTextNode[] | string, options?: RichTextOptions): React.ReactNodeA string is returned as is. It renders bold, italic, code, highlight, link (in a new tab), latex (with KaTeX, showing the source on a parse error), symbol, the colour names, and the six text functions; a bound expression renders as its live value. footnote, cite and internote go to the host's renderers in options when given, and to a plain fallback otherwise: a native tooltip, a raised dot, and a link to /{id}. An unknown micronode renders its body; a list or a block break renders nothing.
(nodes: CompiledNode[], runtime: EnvironmentContext["animation"]): numberA spotlight is purely the dimming: everything else on the environment goes down around its content, which is left at full strength. No rings — a highlight is what rings something, and the two can be used together.
#Constants
numbernumberSymbolName, as the children of a 16×16 <svg>.Record<SymbolName, React.ReactNode>#Interfaces
heightnumberwidthnumberxnumberynumberusePanZoomGestures: the arithmetic, not the binding.baseV | nullclampVcommitvoiddragfrom to to, or null to ignore it. Absent means this surface does not pan — a polar plot zooms about its origin and has no pan, for the same reason a compass rose is not draggable.V | nullenabledbooleanresetvoidwheelV | nullxnumberynumberheightnumberwidthnumberxnumberynumberheightnumberunit--cnv-unit as pixels — 17 reading, 22 presenting.numberwidthnumber#Types
Icon draws by name, without the host supplying an SVG.playpauserestartloopnextprevfullscreenexit-fullscreencopycheckstartmiddleendabovebelowcentre