@@ -34,7 +34,7 @@ type TagOption = {
3434margin ?:string ;
3535padding ?:string ;
3636width ?: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+
137158const childrenMap = {
138159options :TagsCompOptionsControl , // initial tags (PropertyView)
139160style :styleControl ( InputLikeStyle , "style" ) ,
140161onEvent :ButtonEventHandlerControl ,
141162editable :BoolControl , // editable switch field
142- allowEdit :BoolCodeControl , // enable runtime CRUD
143163preventDuplicates :BoolCodeControl , // runtime de-dupe
144164allowEmptyEdits :BoolCodeControl , // allow blank labels on edit
145165maxTags :BoolCodeControl , // truthy => 50 (or provide number if your control supports)
@@ -160,27 +180,24 @@ const multiTags = (function () {
160180
161181// State
162182const [ editingIndex , setEditingIndex ] = useState < number | null > ( null ) ;
183+ const [ editValue , setEditValue ] = useState < string > ( "" ) ;
163184const [ draft , setDraft ] = useState < string > ( "" ) ; // typing buffer for creating a new tag
164185const containerRef = useRef < HTMLDivElement > ( null ) ;
165- const editableRef = useRef < HTMLSpanElement > ( null ) ;
166- const initRef = useRef < boolean > ( false ) ;
167186
168187const preventDuplicates = ! ! props . preventDuplicates ;
169188const allowEmptyEdits = ! ! props . allowEmptyEdits ;
170189const 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
185202const fireEvent = ( type :"add" | "edit" | "delete" | "change" | "click" , payload :any ) => {
186203try { if ( props . onEvent ) ( props . onEvent as any ) ( type , payload ) ; } catch { }
@@ -221,33 +238,18 @@ const multiTags = (function () {
221238width :"" ,
222239} ;
223240const next = [ ...displayOptions , newTag ] ;
224- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( next ) , false ) ) ;
241+ dispatch ( changeChildAction ( "runtimeOptions" , next , false ) ) ;
225242setDraft ( "" ) ;
226243fireEvent ( "add" , { label, value :next } ) ;
227244} ;
228245
229246const startEdit = ( index :number ) => {
230247setEditingIndex ( 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
248251const confirmEdit = ( index :number ) => {
249- const raw = editableRef . current ?. textContent ?? "" ;
250- const val = normalize ( raw ) ;
252+ const val = normalize ( editValue ) ;
251253if ( ! val && ! allowEmptyEdits ) {
252254cancelEdit ( ) ;
253255return ;
@@ -258,25 +260,27 @@ const multiTags = (function () {
258260}
259261const prev = displayOptions [ index ] ?. label ?? "" ;
260262const next = displayOptions . map ( ( t , i ) => ( i === index ?{ ...t , label :val } :t ) ) ;
261- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( next ) , false ) ) ;
263+ dispatch ( changeChildAction ( "runtimeOptions" , next , false ) ) ;
262264setEditingIndex ( null ) ;
265+ setEditValue ( "" ) ;
263266fireEvent ( "edit" , { from :prev , to :val , index, value :next } ) ;
264267} ;
265268
266269const cancelEdit = ( ) => {
267270setEditingIndex ( null ) ;
271+ setEditValue ( "" ) ;
268272} ;
269273
270274const deleteTag = ( index :number ) => {
271275const removed = displayOptions [ index ] ?. label ;
272276const next = displayOptions . filter ( ( _ , i ) => i !== index ) ;
273- dispatch ( changeChildAction ( "runtimeOptions" , toJsonSafe ( next ) , false ) ) ;
277+ dispatch ( changeChildAction ( "runtimeOptions" , next , false ) ) ;
274278fireEvent ( "delete" , { removed, index, value :next } ) ;
275279} ;
276280
277281// Container keyboard handling for *adding* without inputs
278282const onContainerKeyDown :React . KeyboardEventHandler < HTMLDivElement > = ( e ) => {
279- if ( ! props . allowEdit ) return ;
283+ if ( ! props . editable ) return ;
280284
281285const { key, ctrlKey, metaKey, altKey} = e ;
282286
@@ -335,34 +339,32 @@ const multiTags = (function () {
335339{ displayOptions . map ( ( tag , index ) => {
336340const tagColor = getTagColor ( tag . label , displayOptions ) ;
337341const tagStyle = getTagStyle ( tag . label , displayOptions , props . style ) ;
338- const isEditing = props . allowEdit && editingIndex === index ;
342+ const isEditing = props . editable && editingIndex === index ;
339343
340344return (
341345< StyledTag
342346key = { `tag-${ index } ` }
343347$style = { props . style }
344348$customStyle = { tagStyle }
349+ icon = { tag . icon }
345350color = { tagColor }
346- closable = { props . allowEdit }
351+ closable = { props . editable }
347352onClose = { ( e ) => { e . preventDefault ( ) ; deleteTag ( index ) ; } }
348353onDoubleClick = { ( ) => startEdit ( index ) } // double-click to edit
349354onClick = { ( ) => 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 ) }
356361onBlur = { ( ) => confirmEdit ( index ) }
357362onKeyDown = { ( e ) => {
358363if ( e . key === "Enter" ) { e . preventDefault ( ) ; confirmEdit ( index ) ; }
359364if ( e . key === "Escape" ) { e . preventDefault ( ) ; cancelEdit ( ) ; }
360- // stop container from also capturing these keystrokes
361365e . stopPropagation ( ) ;
362366} }
363- >
364- { tag . label }
365- </ EditableSpan >
367+ />
366368) :(
367369tag . 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" } ) }