1- import { type Node , type NodeProps , NodeResizeControl } from '@xyflow/react'
1+ import { type Node , type NodeProps , NodeResizeControl , useUpdateNodeInternals } from '@xyflow/react'
22import { FlowConfig } from '~/routes/studio/canvas/flow.config'
33import { ResizeIcon } from '~/routes/studio/canvas/nodetypes/frank-node'
4- import React , { useEffect , useRef , useState } from 'react'
4+ import { useEffect , useLayoutEffect , useRef , useState } from 'react'
55import useFlowStore from '~/stores/flow-store'
66import { useNodeContextMenu } from '~/routes/studio/canvas/node-context-menu-context'
77import useNodeContextStore from '~/stores/node-context-store'
@@ -33,37 +33,42 @@ export default function StickyNoteComponent(properties: NodeProps<StickyNote>) {
3333 const minHeight = FlowConfig . STICKY_NOTE_DEFAULT_HEIGHT
3434 const minWidth = FlowConfig . STICKY_NOTE_DEFAULT_WIDTH
3535 const showNodeContextMenu = useNodeContextMenu ( )
36-
37- const [ localContent , setLocalContent ] = useState ( properties . data . content )
36+ const updateNodeInternals = useUpdateNodeInternals ( )
37+ const containerRef = useRef < HTMLDivElement > ( null )
38+ const contentRef = useRef < HTMLDivElement > ( null )
3839 const [ isOverflowing , setIsOverflowing ] = useState ( false )
3940
40- const textareaReference = useRef < HTMLTextAreaElement > ( null )
41- const containerReference = useRef < HTMLDivElement > ( null )
42-
4341 const color = properties . data . color ?? 'var(--sticky-color-yellow)'
42+ const content = properties . data . content
43+
44+ const CONTENT_PADDING_Y = 24
45+
46+ useLayoutEffect ( ( ) => {
47+ if ( properties . data . collapsed || ! contentRef . current ) return
48+
49+ const naturalHeight = contentRef . current . scrollHeight + CONTENT_PADDING_Y
50+ const clamped = Math . min (
51+ FlowConfig . STICKY_NOTE_MAX_HEIGHT ,
52+ Math . max ( FlowConfig . STICKY_NOTE_DEFAULT_HEIGHT , naturalHeight ) ,
53+ )
54+
55+ useFlowStore . getState ( ) . setStickyHeight ( properties . id , clamped )
56+ } , [ content , properties . data . collapsed , properties . id ] )
4457
4558 useEffect ( ( ) => {
46- setLocalContent ( properties . data . content )
47- } , [ properties . data . content ] )
59+ updateNodeInternals ( properties . id )
60+ } , [ properties . data . collapsed , properties . id , updateNodeInternals ] )
4861
4962 useEffect ( ( ) => {
5063 if ( properties . data . collapsed ) return
51- const textarea = textareaReference . current
52- const container = containerReference . current
53- if ( ! textarea || ! container ) return
54-
55- const check = ( ) => setIsOverflowing ( textarea . scrollHeight > textarea . clientHeight )
64+ const container = containerRef . current
65+ if ( ! container ) return
66+ const check = ( ) => setIsOverflowing ( container . scrollHeight > container . clientHeight )
5667 check ( )
57-
5868 const observer = new ResizeObserver ( check )
5969 observer . observe ( container )
6070 return ( ) => observer . disconnect ( )
61- } , [ localContent , properties . data . collapsed ] )
62-
63- const updateContent = ( changeEvent : React . ChangeEvent < HTMLTextAreaElement > ) => {
64- setLocalContent ( changeEvent . target . value )
65- useFlowStore . getState ( ) . setStickyText ( properties . id , changeEvent . target . value )
66- }
71+ } , [ content , properties . data . collapsed ] )
6772
6873 const handleDelete = ( ) => {
6974 useNodeContextStore . getState ( ) . setSelectedStickyId ( null )
@@ -81,7 +86,9 @@ export default function StickyNoteComponent(properties: NodeProps<StickyNote>) {
8186 className = "flex items-center overflow-hidden rounded-lg px-2"
8287 style = { { width : `${ FlowConfig . STICKY_NOTE_BALLOON_WIDTH } px` , height : '46px' , background : color } }
8388 >
84- < span className = "flex-1 truncate text-xs" > { localContent } </ span >
89+ < span className = "line-clamp-2 flex-1 overflow-hidden text-xs leading-snug whitespace-pre-wrap" >
90+ { content }
91+ </ span >
8592 < button
8693 className = "nodrag ml-1 shrink-0 text-xs hover:cursor-pointer hover:opacity-70"
8794 onClick = { ( e ) => {
@@ -127,7 +134,7 @@ export default function StickyNoteComponent(properties: NodeProps<StickyNote>) {
127134 < ResizeIcon />
128135 </ NodeResizeControl >
129136 < div
130- ref = { containerReference }
137+ ref = { containerRef }
131138 className = { `relative h-full w-full overflow-hidden p-3 text-xs ${ properties . selected ? 'ring-1 ring-black/40' : '' } ` }
132139 style = { {
133140 minHeight : `${ minHeight } px` ,
@@ -138,19 +145,14 @@ export default function StickyNoteComponent(properties: NodeProps<StickyNote>) {
138145 ` ,
139146 } }
140147 >
141- < textarea
142- ref = { textareaReference }
143- value = { localContent }
144- onChange = { updateContent }
145- className = "nodrag h-full w-full resize-none overflow-hidden bg-transparent text-xs leading-snug outline-none"
146- />
148+ < div ref = { contentRef } className = "w-full text-xs leading-snug break-words whitespace-pre-wrap" >
149+ { content }
150+ </ div >
147151 { isOverflowing && (
148152 < div
149- className = "pointer-events-none absolute right-0 bottom-0 left-0 flex justify-end pr-3 pb-1 text-xs opacity-60 "
153+ className = "pointer-events-none absolute right-0 bottom-0 left-0 h-8 "
150154 style = { { background : `linear-gradient(transparent, ${ color } )` } }
151- >
152- ···
153- </ div >
155+ />
154156 ) }
155157 < div className = "nodrag absolute top-0 right-5 flex items-center" >
156158 < div
0 commit comments