@@ -34,7 +34,7 @@ type TagOption = {
3434 margin ?: string ;
3535 padding ?: string ;
3636 width ?: string ;
37- icon ?: React . ReactNode | string ; // ignored at runtime to keep tags clean
37+ icon ?: any ;
3838} ;
3939
4040const colors = PresetStatusColorTypes ;
@@ -108,8 +108,7 @@ const multiTags = (function () {
108108 display: inline-flex;
109109 align-items: center;
110110 min-width: fit-content;
111- width: ${ ( props ) => props . $customStyle ?. width || "auto" } ;
112- max-width: 100%;
111+
113112 background: ${ ( props ) => props . $customStyle ?. backgroundColor || props . $style ?. background } ;
114113 color: ${ ( props ) => props . $customStyle ?. color || props . $style ?. text } ;
115114 border-radius: ${ ( props ) => props . $customStyle ?. borderRadius || props . $style ?. borderRadius } ;
@@ -129,17 +128,38 @@ const multiTags = (function () {
129128 opacity: 0.9;
130129 ` ;
131130
132- const EditableSpan = styled . span `
131+ const EditInput = styled . input `
132+ border: none;
133133 outline: none;
134- white-space: nowrap;
134+ background: transparent;
135+ font-size: inherit;
136+ font-weight: inherit;
137+ color: inherit;
138+ ` ;
139+
140+ const TagIcon = styled . span `
141+ display: inline-flex;
142+ align-items: center;
143+ margin-right: 4px;
144+
145+ &.icon-right {
146+ margin-right: 0;
147+ margin-left: 4px;
148+ }
149+ ` ;
150+
151+ const TagContent = styled . span `
152+ display: inline-flex;
153+ align-items: center;
135154 ` ;
136155
156+
157+
137158 const childrenMap = {
138159 options : TagsCompOptionsControl , // initial tags (PropertyView)
139160 style : styleControl ( InputLikeStyle , "style" ) ,
140161 onEvent : ButtonEventHandlerControl ,
141162 editable : BoolControl , // editable switch field
142- allowEdit : BoolCodeControl , // enable runtime CRUD
143163 preventDuplicates : BoolCodeControl , // runtime de-dupe
144164 allowEmptyEdits : BoolCodeControl , // allow blank labels on edit
145165 maxTags : BoolCodeControl , // truthy => 50 (or provide number if your control supports)
@@ -160,27 +180,24 @@ const multiTags = (function () {
160180
161181 // State
162182 const [ editingIndex , setEditingIndex ] = useState < number | null > ( null ) ;
183+ const [ editValue , setEditValue ] = useState < string > ( "" ) ;
163184 const [ draft , setDraft ] = useState < string > ( "" ) ; // typing buffer for creating a new tag
164185 const containerRef = useRef < HTMLDivElement > ( null ) ;
165- const editableRef = useRef < HTMLSpanElement > ( null ) ;
166- const initRef = useRef < boolean > ( false ) ;
167186
168187 const preventDuplicates = ! ! props . preventDuplicates ;
169188 const allowEmptyEdits = ! ! props . allowEmptyEdits ;
170189 const maxTags = toMax ( props . maxTags ) ;
171- // Seed runtimeOptions from design-time options once
172- const toJsonSafe = ( opts : TagOption [ ] ) => opts . map ( ( { icon, ...rest } ) => ( { ...rest } ) ) ;
173- useEffect ( ( ) => {
174- if ( ! initRef . current ) {
175- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( props . options ) , false ) ) ;
176- initRef . current = true ;
177- }
178- } , [ dispatch , props . options ] ) ;
179-
180- const displayOptions = ( props as any ) . runtimeOptions ?. length
190+
191+
192+ const displayOptions = ( props as any ) . runtimeOptions ?. length && props . editable
181193 ? ( ( props as any ) . runtimeOptions as TagOption [ ] )
182194 : props . options ;
183195
196+ useEffect ( ( ) => {
197+ // every time the editable prop changes, we need to update the runtimeOptions
198+ dispatch ( changeChildAction ( "runtimeOptions" , [ ...props . options ] as TagOption [ ] , false ) ) ;
199+ } , [ props . editable ] ) ;
200+
184201 // Events helper
185202 const fireEvent = ( type : "add" | "edit" | "delete" | "change" | "click" , payload : any ) => {
186203 try { if ( props . onEvent ) ( props . onEvent as any ) ( type , payload ) ; } catch { }
@@ -221,33 +238,18 @@ const multiTags = (function () {
221238 width : "" ,
222239 } ;
223240 const next = [ ...displayOptions , newTag ] ;
224- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( next ) , false ) ) ;
241+ dispatch ( changeChildAction ( "runtimeOptions" , next , false ) ) ;
225242 setDraft ( "" ) ;
226243 fireEvent ( "add" , { label, value : next } ) ;
227244 } ;
228245
229246 const startEdit = ( index : number ) => {
230247 setEditingIndex ( index ) ;
231- // set content when span mounts via effect-less ref trick below
232- // we'll fill it in render via default textContent
233- requestAnimationFrame ( ( ) => {
234- editableRef . current ?. focus ( ) ;
235- // place caret at end
236- const range = document . createRange ( ) ;
237- const node = editableRef . current ;
238- if ( node && node . firstChild ) {
239- range . setStart ( node . firstChild , node . firstChild . textContent ?. length || 0 ) ;
240- range . collapse ( true ) ;
241- const sel = window . getSelection ( ) ;
242- sel ?. removeAllRanges ( ) ;
243- sel ?. addRange ( range ) ;
244- }
245- } ) ;
248+ setEditValue ( displayOptions [ index ] ?. label || "" ) ;
246249 } ;
247250
248251 const confirmEdit = ( index : number ) => {
249- const raw = editableRef . current ?. textContent ?? "" ;
250- const val = normalize ( raw ) ;
252+ const val = normalize ( editValue ) ;
251253 if ( ! val && ! allowEmptyEdits ) {
252254 cancelEdit ( ) ;
253255 return ;
@@ -258,25 +260,27 @@ const multiTags = (function () {
258260 }
259261 const prev = displayOptions [ index ] ?. label ?? "" ;
260262 const next = displayOptions . map ( ( t , i ) => ( i === index ? { ...t , label : val } : t ) ) ;
261- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( next ) , false ) ) ;
263+ dispatch ( changeChildAction ( "runtimeOptions" , next , false ) ) ;
262264 setEditingIndex ( null ) ;
265+ setEditValue ( "" ) ;
263266 fireEvent ( "edit" , { from : prev , to : val , index, value : next } ) ;
264267 } ;
265268
266269 const cancelEdit = ( ) => {
267270 setEditingIndex ( null ) ;
271+ setEditValue ( "" ) ;
268272 } ;
269273
270274 const deleteTag = ( index : number ) => {
271275 const removed = displayOptions [ index ] ?. label ;
272276 const next = displayOptions . filter ( ( _ , i ) => i !== index ) ;
273- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( next ) , false ) ) ;
277+ dispatch ( changeChildAction ( "runtimeOptions" , next , false ) ) ;
274278 fireEvent ( "delete" , { removed, index, value : next } ) ;
275279 } ;
276280
277281 // Container keyboard handling for *adding* without inputs
278282 const onContainerKeyDown : React . KeyboardEventHandler < HTMLDivElement > = ( e ) => {
279- if ( ! props . allowEdit ) return ;
283+ if ( ! props . editable ) return ;
280284
281285 const { key, ctrlKey, metaKey, altKey } = e ;
282286
@@ -335,34 +339,32 @@ const multiTags = (function () {
335339 { displayOptions . map ( ( tag , index ) => {
336340 const tagColor = getTagColor ( tag . label , displayOptions ) ;
337341 const tagStyle = getTagStyle ( tag . label , displayOptions , props . style ) ;
338- const isEditing = props . allowEdit && editingIndex === index ;
342+ const isEditing = props . editable && editingIndex === index ;
339343
340344 return (
341345 < StyledTag
342346 key = { `tag-${ index } ` }
343347 $style = { props . style }
344348 $customStyle = { tagStyle }
349+ icon = { tag . icon }
345350 color = { tagColor }
346- closable = { props . allowEdit }
351+ closable = { props . editable }
347352 onClose = { ( e ) => { e . preventDefault ( ) ; deleteTag ( index ) ; } }
348353 onDoubleClick = { ( ) => startEdit ( index ) } // double-click to edit
349354 onClick = { ( ) => onTagClick ( tag , index ) } // normal click event
350355 >
351356 { isEditing ? (
352- < EditableSpan
353- ref = { editableRef }
354- contentEditable
355- suppressContentEditableWarning
357+ < EditInput
358+ autoFocus
359+ value = { editValue }
360+ onChange = { ( e ) => setEditValue ( e . target . value ) }
356361 onBlur = { ( ) => confirmEdit ( index ) }
357362 onKeyDown = { ( e ) => {
358363 if ( e . key === "Enter" ) { e . preventDefault ( ) ; confirmEdit ( index ) ; }
359364 if ( e . key === "Escape" ) { e . preventDefault ( ) ; cancelEdit ( ) ; }
360- // stop container from also capturing these keystrokes
361365 e . stopPropagation ( ) ;
362366 } }
363- >
364- { tag . label }
365- </ EditableSpan >
367+ />
366368 ) : (
367369 tag . label
368370 ) }
@@ -371,7 +373,7 @@ const multiTags = (function () {
371373 } ) }
372374
373375 { /* Draft chip appears only while typing; press Enter to commit, Esc to cancel */ }
374- { props . allowEdit && draft && (
376+ { props . editable && draft && (
375377 < DraftTag $style = { props . style } $customStyle = { { } } color = "default" >
376378 { draft }
377379 </ DraftTag >
@@ -385,7 +387,6 @@ const multiTags = (function () {
385387 < Section name = { sectionNames . basic } >
386388 { children . options . propertyView ( { label : "Initial Tags (PropertyView)" } ) }
387389 { children . editable . propertyView ( { label : "Editable" } ) }
388- { children . allowEdit . propertyView ( { label : "Allow Runtime Editing" } ) }
389390 { children . preventDuplicates . propertyView ( { label : "Prevent Duplicates (Runtime)" } ) }
390391 { children . allowEmptyEdits . propertyView ( { label : "Allow Empty Edit (Runtime)" } ) }
391392 { children . maxTags . propertyView ( { label : "Set Max Tags (Runtime) — true=50" } ) }
0 commit comments