Skip to content

Commit 42590ae

Browse files
author
Zaimwa9
authored
fix: Dashboard alias not updating in safari (#5310)
1 parent a0589eb commit 42590ae

5 files changed

Lines changed: 112 additions & 69 deletions

File tree

frontend/common/services/useIdentity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export const identityService = service
141141
query.environmentId
142142
}/${Utils.getIdentitiesEndpoint()}/${
143143
query.data.identity_uuid || query.data.id
144-
}`,
144+
}/`,
145145
}),
146146
}),
147147
// END OF ENDPOINTS

frontend/web/components/EditIdentity.tsx

Lines changed: 16 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Identity } from 'common/types/responses'
33
import { useUpdateIdentityMutation } from 'common/services/useIdentity'
44
import Button from './base/forms/Button'
55
import ErrorMessage from './ErrorMessage'
6-
import classNames from 'classnames'
6+
import GhostInput from './base/forms/GhostInput'
77

88
type EditIdentityType = {
99
data: Identity
@@ -13,7 +13,7 @@ type EditIdentityType = {
1313

1414
const EditIdentity: FC<EditIdentityType> = ({ data, environmentId }) => {
1515
const [alias, setAlias] = useState(data.dashboard_alias)
16-
const aliasRef = useRef<HTMLSpanElement>(null)
16+
const aliasRef = useRef<HTMLInputElement>(null)
1717

1818
useEffect(() => {
1919
setAlias(data?.dashboard_alias)
@@ -22,20 +22,11 @@ const EditIdentity: FC<EditIdentityType> = ({ data, environmentId }) => {
2222
const [updateIdentity, { error, isLoading }] = useUpdateIdentityMutation({})
2323

2424
const handleBlur = () => {
25-
if (aliasRef.current) {
26-
const updatedAlias = (aliasRef.current.textContent || '')
27-
.replace(/\n/g, ' ')
28-
.trim()
29-
.toLowerCase()
30-
31-
aliasRef.current.textContent = alias
32-
setAlias(updatedAlias)
33-
onSubmit(updatedAlias)
34-
}
25+
onSubmit(alias)
3526
}
3627

37-
const onSubmit = (updatedAlias: string) => {
38-
if (!isLoading && updatedAlias) {
28+
const onSubmit = (updatedAlias?: string) => {
29+
if (!isLoading) {
3930
updateIdentity({
4031
data: { ...data, dashboard_alias: updatedAlias },
4132
environmentId,
@@ -44,26 +35,7 @@ const EditIdentity: FC<EditIdentityType> = ({ data, environmentId }) => {
4435
}
4536

4637
const handleFocus = () => {
47-
if (!alias) {
48-
aliasRef.current.textContent = '' // Clear the content
49-
}
50-
51-
// Ensure that aliasRef.current has at least one child node (a text node)
52-
if (aliasRef.current && aliasRef.current.childNodes.length === 0) {
53-
aliasRef.current.appendChild(document.createTextNode(''))
54-
}
55-
56-
if (aliasRef.current) {
57-
const selection = window.getSelection()
58-
const range = document.createRange()
59-
60-
const textLength = aliasRef.current.textContent?.length || 0
61-
range.setStart(aliasRef.current.childNodes[0], textLength)
62-
range.collapse(true)
63-
64-
selection?.removeAllRanges()
65-
selection?.addRange(range)
66-
}
38+
aliasRef.current?.focus()
6739
}
6840

6941
const handleKeyDown = (e: React.KeyboardEvent<HTMLSpanElement>) => {
@@ -73,48 +45,27 @@ const EditIdentity: FC<EditIdentityType> = ({ data, environmentId }) => {
7345
}
7446
}
7547

76-
const handleInput = () => {
77-
if (aliasRef.current) {
78-
const selection = window.getSelection()
79-
const range = selection?.getRangeAt(0)
80-
const cursorPosition = range?.startOffset || 0
81-
82-
const lowerCaseText = aliasRef.current.textContent?.toLowerCase() || ''
83-
aliasRef.current.textContent = lowerCaseText
84-
85-
// Restore cursor position
86-
const newRange = document.createRange()
87-
newRange.setStart(
88-
aliasRef.current.childNodes[0],
89-
Math.min(cursorPosition, lowerCaseText.length),
90-
)
91-
newRange.collapse(true)
92-
93-
selection?.removeAllRanges()
94-
selection?.addRange(newRange)
95-
}
48+
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
49+
const newValue = e.target.value.toLowerCase()
50+
setAlias(newValue.replace(/\n/g, ' ').trim())
9651
}
9752

9853
return (
9954
<>
100-
<span
55+
<GhostInput
10156
ref={aliasRef}
102-
className={classNames('fw-normal', { 'text-muted': !alias })}
103-
contentEditable={true}
104-
suppressContentEditableWarning={true}
57+
value={alias}
58+
onChange={handleInput}
10559
onBlur={handleBlur}
10660
onKeyDown={handleKeyDown}
107-
onInput={handleInput}
108-
role='textbox'
109-
aria-label='Alias'
110-
>
111-
{alias || 'None'}
112-
</span>
61+
placeholder='None'
62+
/>
11363
<Button
11464
disabled={!data}
11565
iconSize={18}
11666
theme='text'
117-
className='ms-2 text-primary'
67+
style={{ lineHeight: 'inherit' }}
68+
className='text-primary'
11869
iconRightColour='primary'
11970
iconRight={'edit'}
12071
onClick={handleFocus}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { forwardRef, useEffect, useRef, useState } from 'react'
2+
import classNames from 'classnames'
3+
4+
type GhostInputProps = {
5+
value?: string
6+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
7+
onBlur?: () => void
8+
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void
9+
placeholder: string
10+
className?: string
11+
width?: number
12+
'aria-label'?: string
13+
}
14+
15+
const GhostInput = forwardRef<HTMLInputElement, GhostInputProps>(
16+
(
17+
{
18+
'aria-label': ariaLabel,
19+
className,
20+
onBlur,
21+
onChange,
22+
onKeyDown,
23+
placeholder = '',
24+
value,
25+
},
26+
ref,
27+
) => {
28+
const spanRef = useRef<HTMLSpanElement>(null)
29+
const [inputWidth, setInputWidth] = useState(5)
30+
31+
useEffect(() => {
32+
if (spanRef.current) {
33+
setInputWidth(spanRef.current.offsetWidth * 0.95)
34+
}
35+
}, [value])
36+
37+
return (
38+
<div style={{ display: 'inline-block', position: 'relative' }}>
39+
<span
40+
ref={spanRef}
41+
style={{
42+
border: 0,
43+
fontFamily: 'inherit',
44+
fontSize: 'inherit',
45+
fontWeight: 'inherit',
46+
letterSpacing: 'inherit',
47+
lineHeight: 'inherit',
48+
margin: 0,
49+
padding: 0,
50+
position: 'absolute',
51+
visibility: 'hidden',
52+
whiteSpace: 'pre',
53+
}}
54+
>
55+
{value || placeholder}
56+
</span>
57+
<input
58+
ref={ref}
59+
maxLength={100}
60+
className={classNames('fw-normal', className, {
61+
'text-muted': !value,
62+
})}
63+
value={value}
64+
placeholder={placeholder}
65+
onChange={onChange}
66+
onBlur={onBlur}
67+
onKeyDown={onKeyDown}
68+
role='textbox'
69+
aria-label={ariaLabel}
70+
spellCheck={false}
71+
style={{
72+
background: 'transparent',
73+
border: 'none',
74+
boxShadow: 'none',
75+
color: 'inherit',
76+
cursor: 'text',
77+
fontFamily: 'inherit',
78+
fontSize: 'inherit',
79+
margin: 0,
80+
outline: 'none',
81+
padding: 0,
82+
width: inputWidth,
83+
}}
84+
/>
85+
</div>
86+
)
87+
},
88+
)
89+
90+
GhostInput.displayName = 'GhostInput'
91+
92+
export default GhostInput

frontend/web/components/pages/HomeAside.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { ComponentProps, FC, useEffect, useMemo, useState } from 'react'
1+
import React, { ComponentProps, FC, useEffect, useState } from 'react'
22
import ProjectStore from 'common/stores/project-store'
33
import ChangeRequestStore from 'common/stores/change-requests-store'
44
import Utils from 'common/utils/utils'
@@ -355,7 +355,7 @@ const HomeAside: FC<HomeAsideType> = ({
355355
exact
356356
to={`/project/${project.id}/environment/${environment.api_key}/split-tests`}
357357
>
358-
<IonIcon className='mr-2' icon={flask} />
358+
<IonIcon className='mr-2' icon={flask} color={'#9DA4AE'} />
359359
Split Tests
360360
</NavLink>
361361
)}

frontend/web/components/pages/UserPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ const UserPage: FC<UserPageType> = (props) => {
332332
}
333333
/>
334334
{showAliases && (
335-
<h6>
335+
<h6 className='d-flex align-items-center gap-1'>
336336
<Tooltip
337337
title={
338338
<span className='user-select-none'>

0 commit comments

Comments
 (0)