@elementor/editor-ui 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +8 -0
- package/dist/index.d.mts +33 -6
- package/dist/index.d.ts +33 -6
- package/dist/index.js +105 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +102 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -2
- package/src/components/editable-field.tsx +24 -0
- package/src/hooks/__tests__/use-editable.test.ts +158 -0
- package/src/hooks/use-editable.ts +117 -0
- package/src/index.ts +5 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
|
|
2
|
-
> @elementor/editor-ui@0.
|
|
2
|
+
> @elementor/editor-ui@0.2.0 build
|
|
3
3
|
> tsup --config=../../tsup.build.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: ../../../tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.
|
|
7
|
+
[34mCLI[39m tsup v8.4.0
|
|
8
8
|
[34mCLI[39m Using tsup config: /home/runner/work/elementor-packages/elementor-packages/tsup.build.ts
|
|
9
9
|
[34mCLI[39m Target: esnext
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.mjs [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.mjs.map [22m[
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[32mCJS[39m [1mdist/index.js [22m[
|
|
17
|
-
[32mCJS[39m [1mdist/index.js.map [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m3.87 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m8.21 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 134ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.js [22m[32m5.65 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m8.45 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 135ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.mts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 40577ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.48 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.48 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @elementor/editor-ui
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 910044c: update `@elementor/icons` and `@elementor/ui` packages.
|
|
8
|
+
- 4ed562a: Support renaming label in classes manager.
|
|
9
|
+
- 5caf78d: update `@elementor/ui` package.
|
|
10
|
+
|
|
3
11
|
## 0.1.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
1
|
+
import * as React$1 from 'react';
|
|
2
2
|
|
|
3
|
-
type EllipsisWithTooltipProps<T extends React.ElementType> = {
|
|
4
|
-
maxWidth?: React.CSSProperties['maxWidth'];
|
|
3
|
+
type EllipsisWithTooltipProps<T extends React$1.ElementType> = {
|
|
4
|
+
maxWidth?: React$1.CSSProperties['maxWidth'];
|
|
5
5
|
title: string;
|
|
6
6
|
as?: T;
|
|
7
|
-
} & React.ComponentProps<T>;
|
|
8
|
-
declare const EllipsisWithTooltip: <T extends React.ElementType>({ maxWidth, title, as, ...props }: EllipsisWithTooltipProps<T>) => React.JSX.Element;
|
|
7
|
+
} & React$1.ComponentProps<T>;
|
|
8
|
+
declare const EllipsisWithTooltip: <T extends React$1.ElementType>({ maxWidth, title, as, ...props }: EllipsisWithTooltipProps<T>) => React$1.JSX.Element;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
declare const EditableField: React$1.ForwardRefExoticComponent<Omit<any, "ref"> & React$1.RefAttributes<unknown>>;
|
|
11
|
+
|
|
12
|
+
type UseEditableParams = {
|
|
13
|
+
value: string;
|
|
14
|
+
onSubmit: (value: string) => unknown;
|
|
15
|
+
validation?: (value: string) => string | undefined | null;
|
|
16
|
+
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
17
|
+
};
|
|
18
|
+
declare const useEditable: ({ value, onSubmit, validation, onClick }: UseEditableParams) => {
|
|
19
|
+
readonly ref: React$1.MutableRefObject<HTMLElement | null>;
|
|
20
|
+
readonly isEditing: boolean;
|
|
21
|
+
readonly openEditMode: () => void;
|
|
22
|
+
readonly closeEditMode: () => void;
|
|
23
|
+
readonly value: string;
|
|
24
|
+
readonly error: string | null | undefined;
|
|
25
|
+
readonly getProps: () => {
|
|
26
|
+
suppressContentEditableWarning?: boolean | undefined;
|
|
27
|
+
value: string;
|
|
28
|
+
role: "textbox";
|
|
29
|
+
contentEditable: boolean;
|
|
30
|
+
onClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
31
|
+
onKeyDown: (event: React.KeyboardEvent) => void;
|
|
32
|
+
onInput: (event: React.ChangeEvent<HTMLSpanElement>) => void;
|
|
33
|
+
onBlur: () => void;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { EditableField, EllipsisWithTooltip, useEditable };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
1
|
+
import * as React$1 from 'react';
|
|
2
2
|
|
|
3
|
-
type EllipsisWithTooltipProps<T extends React.ElementType> = {
|
|
4
|
-
maxWidth?: React.CSSProperties['maxWidth'];
|
|
3
|
+
type EllipsisWithTooltipProps<T extends React$1.ElementType> = {
|
|
4
|
+
maxWidth?: React$1.CSSProperties['maxWidth'];
|
|
5
5
|
title: string;
|
|
6
6
|
as?: T;
|
|
7
|
-
} & React.ComponentProps<T>;
|
|
8
|
-
declare const EllipsisWithTooltip: <T extends React.ElementType>({ maxWidth, title, as, ...props }: EllipsisWithTooltipProps<T>) => React.JSX.Element;
|
|
7
|
+
} & React$1.ComponentProps<T>;
|
|
8
|
+
declare const EllipsisWithTooltip: <T extends React$1.ElementType>({ maxWidth, title, as, ...props }: EllipsisWithTooltipProps<T>) => React$1.JSX.Element;
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
declare const EditableField: React$1.ForwardRefExoticComponent<Omit<any, "ref"> & React$1.RefAttributes<unknown>>;
|
|
11
|
+
|
|
12
|
+
type UseEditableParams = {
|
|
13
|
+
value: string;
|
|
14
|
+
onSubmit: (value: string) => unknown;
|
|
15
|
+
validation?: (value: string) => string | undefined | null;
|
|
16
|
+
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
17
|
+
};
|
|
18
|
+
declare const useEditable: ({ value, onSubmit, validation, onClick }: UseEditableParams) => {
|
|
19
|
+
readonly ref: React$1.MutableRefObject<HTMLElement | null>;
|
|
20
|
+
readonly isEditing: boolean;
|
|
21
|
+
readonly openEditMode: () => void;
|
|
22
|
+
readonly closeEditMode: () => void;
|
|
23
|
+
readonly value: string;
|
|
24
|
+
readonly error: string | null | undefined;
|
|
25
|
+
readonly getProps: () => {
|
|
26
|
+
suppressContentEditableWarning?: boolean | undefined;
|
|
27
|
+
value: string;
|
|
28
|
+
role: "textbox";
|
|
29
|
+
contentEditable: boolean;
|
|
30
|
+
onClick: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
31
|
+
onKeyDown: (event: React.KeyboardEvent) => void;
|
|
32
|
+
onInput: (event: React.ChangeEvent<HTMLSpanElement>) => void;
|
|
33
|
+
onBlur: () => void;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { EditableField, EllipsisWithTooltip, useEditable };
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
|
|
33
|
+
EditableField: () => EditableField,
|
|
34
|
+
EllipsisWithTooltip: () => EllipsisWithTooltip,
|
|
35
|
+
useEditable: () => useEditable
|
|
34
36
|
});
|
|
35
37
|
module.exports = __toCommonJS(index_exports);
|
|
36
38
|
|
|
@@ -77,8 +79,109 @@ var useIsOverflowing = () => {
|
|
|
77
79
|
}, [el]);
|
|
78
80
|
return [setEl, isOverflowing];
|
|
79
81
|
};
|
|
82
|
+
|
|
83
|
+
// src/components/editable-field.tsx
|
|
84
|
+
var React2 = __toESM(require("react"));
|
|
85
|
+
var import_react2 = require("react");
|
|
86
|
+
var import_ui2 = require("@elementor/ui");
|
|
87
|
+
var EditableField = (0, import_react2.forwardRef)(
|
|
88
|
+
({ value, error, as: Component = "span", ...props }, ref) => {
|
|
89
|
+
return /* @__PURE__ */ React2.createElement(import_ui2.Tooltip, { title: error, open: !!error, placement: "top" }, /* @__PURE__ */ React2.createElement(Component, { ref, ...props }, value));
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// src/hooks/use-editable.ts
|
|
94
|
+
var import_react3 = require("react");
|
|
95
|
+
var useEditable = ({ value, onSubmit, validation, onClick }) => {
|
|
96
|
+
const [isEditing, setIsEditing] = (0, import_react3.useState)(false);
|
|
97
|
+
const [error, setError] = (0, import_react3.useState)(null);
|
|
98
|
+
const ref = useSelection(isEditing);
|
|
99
|
+
const openEditMode = () => {
|
|
100
|
+
setIsEditing(true);
|
|
101
|
+
};
|
|
102
|
+
const closeEditMode = () => {
|
|
103
|
+
ref.current?.blur();
|
|
104
|
+
setError(null);
|
|
105
|
+
setIsEditing(false);
|
|
106
|
+
};
|
|
107
|
+
const submit = (newValue) => {
|
|
108
|
+
if (!error) {
|
|
109
|
+
try {
|
|
110
|
+
onSubmit(newValue);
|
|
111
|
+
} finally {
|
|
112
|
+
closeEditMode();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const onChange = (event) => {
|
|
117
|
+
const { innerText: newValue } = event.target;
|
|
118
|
+
if (validation) {
|
|
119
|
+
setError(validation(newValue));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const handleKeyDown = (event) => {
|
|
123
|
+
event.stopPropagation();
|
|
124
|
+
if (["Escape"].includes(event.key)) {
|
|
125
|
+
return closeEditMode();
|
|
126
|
+
}
|
|
127
|
+
if (["Enter"].includes(event.key)) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
return submit(event.target.innerText);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const handleClick = (event) => {
|
|
133
|
+
if (isEditing) {
|
|
134
|
+
event.stopPropagation();
|
|
135
|
+
}
|
|
136
|
+
onClick?.(event);
|
|
137
|
+
};
|
|
138
|
+
const listeners = {
|
|
139
|
+
onClick: handleClick,
|
|
140
|
+
onKeyDown: handleKeyDown,
|
|
141
|
+
onInput: onChange,
|
|
142
|
+
onBlur: closeEditMode
|
|
143
|
+
};
|
|
144
|
+
const attributes = {
|
|
145
|
+
value,
|
|
146
|
+
role: "textbox",
|
|
147
|
+
contentEditable: isEditing,
|
|
148
|
+
...isEditing && {
|
|
149
|
+
suppressContentEditableWarning: true
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
ref,
|
|
154
|
+
isEditing,
|
|
155
|
+
openEditMode,
|
|
156
|
+
closeEditMode,
|
|
157
|
+
value,
|
|
158
|
+
error,
|
|
159
|
+
getProps: () => ({ ...listeners, ...attributes })
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
var useSelection = (isEditing) => {
|
|
163
|
+
const ref = (0, import_react3.useRef)(null);
|
|
164
|
+
(0, import_react3.useEffect)(() => {
|
|
165
|
+
if (isEditing) {
|
|
166
|
+
selectAll(ref.current);
|
|
167
|
+
}
|
|
168
|
+
}, [isEditing]);
|
|
169
|
+
return ref;
|
|
170
|
+
};
|
|
171
|
+
var selectAll = (el) => {
|
|
172
|
+
const selection = getSelection();
|
|
173
|
+
if (!selection || !el) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const range = document.createRange();
|
|
177
|
+
range.selectNodeContents(el);
|
|
178
|
+
selection.removeAllRanges();
|
|
179
|
+
selection.addRange(range);
|
|
180
|
+
};
|
|
80
181
|
// Annotate the CommonJS export names for ESM import in node:
|
|
81
182
|
0 && (module.exports = {
|
|
82
|
-
|
|
183
|
+
EditableField,
|
|
184
|
+
EllipsisWithTooltip,
|
|
185
|
+
useEditable
|
|
83
186
|
});
|
|
84
187
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/ellipsis-with-tooltip.tsx"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/ellipsis-with-tooltip.tsx","../src/components/editable-field.tsx","../src/hooks/use-editable.ts"],"sourcesContent":["// components\nexport { EllipsisWithTooltip } from './components/ellipsis-with-tooltip';\nexport { EditableField } from './components/editable-field';\n\n// hooks\nexport { useEditable } from './hooks/use-editable';\n","import * as React from 'react';\nimport { useEffect, useState } from 'react';\nimport { Box, Tooltip } from '@elementor/ui';\n\ntype EllipsisWithTooltipProps< T extends React.ElementType > = {\n\tmaxWidth?: React.CSSProperties[ 'maxWidth' ];\n\ttitle: string;\n\tas?: T;\n} & React.ComponentProps< T >;\n\nexport const EllipsisWithTooltip = < T extends React.ElementType >( {\n\tmaxWidth,\n\ttitle,\n\tas,\n\t...props\n}: EllipsisWithTooltipProps< T > ) => {\n\tconst [ setRef, isOverflowing ] = useIsOverflowing();\n\n\tif ( isOverflowing ) {\n\t\treturn (\n\t\t\t<Tooltip title={ title } placement=\"top\">\n\t\t\t\t<Content maxWidth={ maxWidth } ref={ setRef } as={ as } { ...props }>\n\t\t\t\t\t{ title }\n\t\t\t\t</Content>\n\t\t\t</Tooltip>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Content maxWidth={ maxWidth } ref={ setRef } as={ as } { ...props }>\n\t\t\t{ title }\n\t\t</Content>\n\t);\n};\n\ntype ContentProps< T extends React.ElementType > = React.PropsWithChildren<\n\tOmit< EllipsisWithTooltipProps< T >, 'title' >\n>;\n\nconst Content = React.forwardRef(\n\t< T extends React.ElementType >(\n\t\t{ maxWidth, as: Component = Box, ...props }: ContentProps< T >,\n\t\t// forwardRef loses the typing when using generic components.\n\t\tref: unknown\n\t) => (\n\t\t<Component\n\t\t\tref={ ref }\n\t\t\tposition=\"relative\"\n\t\t\t{ ...props }\n\t\t\tstyle={ { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth } }\n\t\t/>\n\t)\n);\n\nconst useIsOverflowing = () => {\n\tconst [ el, setEl ] = useState< HTMLElement | null >( null );\n\tconst [ isOverflowing, setIsOverflown ] = useState( false );\n\n\tuseEffect( () => {\n\t\tconst observer = new ResizeObserver( ( [ { target } ] ) => {\n\t\t\tsetIsOverflown( target.scrollWidth > target.clientWidth );\n\t\t} );\n\n\t\tif ( el ) {\n\t\t\tobserver.observe( el );\n\t\t}\n\n\t\treturn () => {\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [ el ] );\n\n\treturn [ setEl, isOverflowing ] as const;\n};\n","import * as React from 'react';\nimport { forwardRef } from 'react';\nimport { Tooltip } from '@elementor/ui';\n\ntype EditableFieldProps< T extends React.ElementType > = {\n\tvalue: string;\n\terror?: string;\n\tas?: T;\n} & React.ComponentPropsWithRef< T >;\n\nexport const EditableField = forwardRef(\n\t< T extends React.ElementType >(\n\t\t{ value, error, as: Component = 'span', ...props }: EditableFieldProps< T >,\n\t\tref: unknown\n\t) => {\n\t\treturn (\n\t\t\t<Tooltip title={ error } open={ !! error } placement=\"top\">\n\t\t\t\t<Component ref={ ref } { ...props }>\n\t\t\t\t\t{ value }\n\t\t\t\t</Component>\n\t\t\t</Tooltip>\n\t\t);\n\t}\n);\n","import { useEffect, useRef, useState } from 'react';\n\ntype UseEditableParams = {\n\tvalue: string;\n\tonSubmit: ( value: string ) => unknown;\n\tvalidation?: ( value: string ) => string | undefined | null;\n\tonClick?: ( event: React.MouseEvent< HTMLDivElement > ) => void;\n};\n\nexport const useEditable = ( { value, onSubmit, validation, onClick }: UseEditableParams ) => {\n\tconst [ isEditing, setIsEditing ] = useState( false );\n\tconst [ error, setError ] = useState< string | null | undefined >( null );\n\n\tconst ref = useSelection( isEditing );\n\n\tconst openEditMode = () => {\n\t\tsetIsEditing( true );\n\t};\n\n\tconst closeEditMode = () => {\n\t\tref.current?.blur();\n\n\t\tsetError( null );\n\t\tsetIsEditing( false );\n\t};\n\n\tconst submit = ( newValue: string ) => {\n\t\tif ( ! error ) {\n\t\t\ttry {\n\t\t\t\tonSubmit( newValue );\n\t\t\t} finally {\n\t\t\t\tcloseEditMode();\n\t\t\t}\n\t\t}\n\t};\n\n\tconst onChange = ( event: React.ChangeEvent< HTMLSpanElement > ) => {\n\t\tconst { innerText: newValue } = event.target;\n\n\t\tif ( validation ) {\n\t\t\tsetError( validation( newValue ) );\n\t\t}\n\t};\n\n\tconst handleKeyDown = ( event: React.KeyboardEvent ) => {\n\t\tevent.stopPropagation();\n\n\t\tif ( [ 'Escape' ].includes( event.key ) ) {\n\t\t\treturn closeEditMode();\n\t\t}\n\n\t\tif ( [ 'Enter' ].includes( event.key ) ) {\n\t\t\tevent.preventDefault();\n\t\t\treturn submit( ( event.target as HTMLElement ).innerText );\n\t\t}\n\t};\n\n\tconst handleClick = ( event: React.MouseEvent< HTMLDivElement > ) => {\n\t\tif ( isEditing ) {\n\t\t\tevent.stopPropagation();\n\t\t}\n\n\t\tonClick?.( event );\n\t};\n\n\tconst listeners = {\n\t\tonClick: handleClick,\n\t\tonKeyDown: handleKeyDown,\n\t\tonInput: onChange,\n\t\tonBlur: closeEditMode,\n\t} as const;\n\n\tconst attributes = {\n\t\tvalue,\n\t\trole: 'textbox',\n\t\tcontentEditable: isEditing,\n\t\t...( isEditing && {\n\t\t\tsuppressContentEditableWarning: true,\n\t\t} ),\n\t} as const;\n\n\treturn {\n\t\tref,\n\t\tisEditing,\n\t\topenEditMode,\n\t\tcloseEditMode,\n\t\tvalue,\n\t\terror,\n\t\tgetProps: () => ( { ...listeners, ...attributes } ),\n\t} as const;\n};\n\nconst useSelection = ( isEditing: boolean ) => {\n\tconst ref = useRef< HTMLElement | null >( null );\n\n\tuseEffect( () => {\n\t\tif ( isEditing ) {\n\t\t\tselectAll( ref.current );\n\t\t}\n\t}, [ isEditing ] );\n\n\treturn ref;\n};\n\nconst selectAll = ( el: HTMLElement | null ) => {\n\tconst selection = getSelection();\n\n\tif ( ! selection || ! el ) {\n\t\treturn;\n\t}\n\n\tconst range = document.createRange();\n\trange.selectNodeContents( el );\n\n\tselection.removeAllRanges();\n\tselection.addRange( range );\n};\n"],"mappings":"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","names":["React","import_react","import_ui","import_react"]}
|
package/dist/index.mjs
CHANGED
|
@@ -41,7 +41,108 @@ var useIsOverflowing = () => {
|
|
|
41
41
|
}, [el]);
|
|
42
42
|
return [setEl, isOverflowing];
|
|
43
43
|
};
|
|
44
|
+
|
|
45
|
+
// src/components/editable-field.tsx
|
|
46
|
+
import * as React2 from "react";
|
|
47
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
48
|
+
import { Tooltip as Tooltip2 } from "@elementor/ui";
|
|
49
|
+
var EditableField = forwardRef2(
|
|
50
|
+
({ value, error, as: Component = "span", ...props }, ref) => {
|
|
51
|
+
return /* @__PURE__ */ React2.createElement(Tooltip2, { title: error, open: !!error, placement: "top" }, /* @__PURE__ */ React2.createElement(Component, { ref, ...props }, value));
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// src/hooks/use-editable.ts
|
|
56
|
+
import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
57
|
+
var useEditable = ({ value, onSubmit, validation, onClick }) => {
|
|
58
|
+
const [isEditing, setIsEditing] = useState2(false);
|
|
59
|
+
const [error, setError] = useState2(null);
|
|
60
|
+
const ref = useSelection(isEditing);
|
|
61
|
+
const openEditMode = () => {
|
|
62
|
+
setIsEditing(true);
|
|
63
|
+
};
|
|
64
|
+
const closeEditMode = () => {
|
|
65
|
+
ref.current?.blur();
|
|
66
|
+
setError(null);
|
|
67
|
+
setIsEditing(false);
|
|
68
|
+
};
|
|
69
|
+
const submit = (newValue) => {
|
|
70
|
+
if (!error) {
|
|
71
|
+
try {
|
|
72
|
+
onSubmit(newValue);
|
|
73
|
+
} finally {
|
|
74
|
+
closeEditMode();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const onChange = (event) => {
|
|
79
|
+
const { innerText: newValue } = event.target;
|
|
80
|
+
if (validation) {
|
|
81
|
+
setError(validation(newValue));
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
const handleKeyDown = (event) => {
|
|
85
|
+
event.stopPropagation();
|
|
86
|
+
if (["Escape"].includes(event.key)) {
|
|
87
|
+
return closeEditMode();
|
|
88
|
+
}
|
|
89
|
+
if (["Enter"].includes(event.key)) {
|
|
90
|
+
event.preventDefault();
|
|
91
|
+
return submit(event.target.innerText);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const handleClick = (event) => {
|
|
95
|
+
if (isEditing) {
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
}
|
|
98
|
+
onClick?.(event);
|
|
99
|
+
};
|
|
100
|
+
const listeners = {
|
|
101
|
+
onClick: handleClick,
|
|
102
|
+
onKeyDown: handleKeyDown,
|
|
103
|
+
onInput: onChange,
|
|
104
|
+
onBlur: closeEditMode
|
|
105
|
+
};
|
|
106
|
+
const attributes = {
|
|
107
|
+
value,
|
|
108
|
+
role: "textbox",
|
|
109
|
+
contentEditable: isEditing,
|
|
110
|
+
...isEditing && {
|
|
111
|
+
suppressContentEditableWarning: true
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
ref,
|
|
116
|
+
isEditing,
|
|
117
|
+
openEditMode,
|
|
118
|
+
closeEditMode,
|
|
119
|
+
value,
|
|
120
|
+
error,
|
|
121
|
+
getProps: () => ({ ...listeners, ...attributes })
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
var useSelection = (isEditing) => {
|
|
125
|
+
const ref = useRef(null);
|
|
126
|
+
useEffect2(() => {
|
|
127
|
+
if (isEditing) {
|
|
128
|
+
selectAll(ref.current);
|
|
129
|
+
}
|
|
130
|
+
}, [isEditing]);
|
|
131
|
+
return ref;
|
|
132
|
+
};
|
|
133
|
+
var selectAll = (el) => {
|
|
134
|
+
const selection = getSelection();
|
|
135
|
+
if (!selection || !el) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const range = document.createRange();
|
|
139
|
+
range.selectNodeContents(el);
|
|
140
|
+
selection.removeAllRanges();
|
|
141
|
+
selection.addRange(range);
|
|
142
|
+
};
|
|
44
143
|
export {
|
|
45
|
-
|
|
144
|
+
EditableField,
|
|
145
|
+
EllipsisWithTooltip,
|
|
146
|
+
useEditable
|
|
46
147
|
};
|
|
47
148
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/ellipsis-with-tooltip.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useEffect, useState } from 'react';\nimport { Box, Tooltip } from '@elementor/ui';\n\ntype EllipsisWithTooltipProps< T extends React.ElementType > = {\n\tmaxWidth?: React.CSSProperties[ 'maxWidth' ];\n\ttitle: string;\n\tas?: T;\n} & React.ComponentProps< T >;\n\nexport const EllipsisWithTooltip = < T extends React.ElementType >( {\n\tmaxWidth,\n\ttitle,\n\tas,\n\t...props\n}: EllipsisWithTooltipProps< T > ) => {\n\tconst [ setRef, isOverflowing ] = useIsOverflowing();\n\n\tif ( isOverflowing ) {\n\t\treturn (\n\t\t\t<Tooltip title={ title } placement=\"top\">\n\t\t\t\t<Content maxWidth={ maxWidth } ref={ setRef } as={ as } { ...props }>\n\t\t\t\t\t{ title }\n\t\t\t\t</Content>\n\t\t\t</Tooltip>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Content maxWidth={ maxWidth } ref={ setRef } as={ as } { ...props }>\n\t\t\t{ title }\n\t\t</Content>\n\t);\n};\n\ntype ContentProps< T extends React.ElementType > = React.PropsWithChildren<\n\tOmit< EllipsisWithTooltipProps< T >, 'title' >\n>;\n\nconst Content = React.forwardRef(\n\t< T extends React.ElementType >(\n\t\t{ maxWidth, as: Component = Box, ...props }: ContentProps< T >,\n\t\t// forwardRef loses the typing when using generic components.\n\t\tref: unknown\n\t) => (\n\t\t<Component\n\t\t\tref={ ref }\n\t\t\tposition=\"relative\"\n\t\t\t{ ...props }\n\t\t\tstyle={ { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth } }\n\t\t/>\n\t)\n);\n\nconst useIsOverflowing = () => {\n\tconst [ el, setEl ] = useState< HTMLElement | null >( null );\n\tconst [ isOverflowing, setIsOverflown ] = useState( false );\n\n\tuseEffect( () => {\n\t\tconst observer = new ResizeObserver( ( [ { target } ] ) => {\n\t\t\tsetIsOverflown( target.scrollWidth > target.clientWidth );\n\t\t} );\n\n\t\tif ( el ) {\n\t\t\tobserver.observe( el );\n\t\t}\n\n\t\treturn () => {\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [ el ] );\n\n\treturn [ setEl, isOverflowing ] as const;\n};\n"],"mappings":";AAAA,YAAY,WAAW;AACvB,SAAS,WAAW,gBAAgB;AACpC,SAAS,KAAK,eAAe;AAQtB,IAAM,sBAAsB,CAAiC;AAAA,EACnE;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACJ,MAAsC;AACrC,QAAM,CAAE,QAAQ,aAAc,IAAI,iBAAiB;AAEnD,MAAK,eAAgB;AACpB,WACC,oCAAC,WAAQ,OAAgB,WAAU,SAClC,oCAAC,WAAQ,UAAsB,KAAM,QAAS,IAAY,GAAG,SAC1D,KACH,CACD;AAAA,EAEF;AAEA,SACC,oCAAC,WAAQ,UAAsB,KAAM,QAAS,IAAY,GAAG,SAC1D,KACH;AAEF;AAMA,IAAM,UAAgB;AAAA,EACrB,CACC,EAAE,UAAU,IAAI,YAAY,KAAK,GAAG,MAAM,GAE1C,QAEA;AAAA,IAAC;AAAA;AAAA,MACA;AAAA,MACA,UAAS;AAAA,MACP,GAAG;AAAA,MACL,OAAQ,EAAE,UAAU,UAAU,cAAc,YAAY,YAAY,UAAU,SAAS;AAAA;AAAA,EACxF;AAEF;AAEA,IAAM,mBAAmB,MAAM;AAC9B,QAAM,CAAE,IAAI,KAAM,IAAI,SAAgC,IAAK;AAC3D,QAAM,CAAE,eAAe,cAAe,IAAI,SAAU,KAAM;AAE1D,YAAW,MAAM;AAChB,UAAM,WAAW,IAAI,eAAgB,CAAE,CAAE,EAAE,OAAO,CAAE,MAAO;AAC1D,qBAAgB,OAAO,cAAc,OAAO,WAAY;AAAA,IACzD,CAAE;AAEF,QAAK,IAAK;AACT,eAAS,QAAS,EAAG;AAAA,IACtB;AAEA,WAAO,MAAM;AACZ,eAAS,WAAW;AAAA,IACrB;AAAA,EACD,GAAG,CAAE,EAAG,CAAE;AAEV,SAAO,CAAE,OAAO,aAAc;AAC/B;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/components/ellipsis-with-tooltip.tsx","../src/components/editable-field.tsx","../src/hooks/use-editable.ts"],"sourcesContent":["import * as React from 'react';\nimport { useEffect, useState } from 'react';\nimport { Box, Tooltip } from '@elementor/ui';\n\ntype EllipsisWithTooltipProps< T extends React.ElementType > = {\n\tmaxWidth?: React.CSSProperties[ 'maxWidth' ];\n\ttitle: string;\n\tas?: T;\n} & React.ComponentProps< T >;\n\nexport const EllipsisWithTooltip = < T extends React.ElementType >( {\n\tmaxWidth,\n\ttitle,\n\tas,\n\t...props\n}: EllipsisWithTooltipProps< T > ) => {\n\tconst [ setRef, isOverflowing ] = useIsOverflowing();\n\n\tif ( isOverflowing ) {\n\t\treturn (\n\t\t\t<Tooltip title={ title } placement=\"top\">\n\t\t\t\t<Content maxWidth={ maxWidth } ref={ setRef } as={ as } { ...props }>\n\t\t\t\t\t{ title }\n\t\t\t\t</Content>\n\t\t\t</Tooltip>\n\t\t);\n\t}\n\n\treturn (\n\t\t<Content maxWidth={ maxWidth } ref={ setRef } as={ as } { ...props }>\n\t\t\t{ title }\n\t\t</Content>\n\t);\n};\n\ntype ContentProps< T extends React.ElementType > = React.PropsWithChildren<\n\tOmit< EllipsisWithTooltipProps< T >, 'title' >\n>;\n\nconst Content = React.forwardRef(\n\t< T extends React.ElementType >(\n\t\t{ maxWidth, as: Component = Box, ...props }: ContentProps< T >,\n\t\t// forwardRef loses the typing when using generic components.\n\t\tref: unknown\n\t) => (\n\t\t<Component\n\t\t\tref={ ref }\n\t\t\tposition=\"relative\"\n\t\t\t{ ...props }\n\t\t\tstyle={ { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth } }\n\t\t/>\n\t)\n);\n\nconst useIsOverflowing = () => {\n\tconst [ el, setEl ] = useState< HTMLElement | null >( null );\n\tconst [ isOverflowing, setIsOverflown ] = useState( false );\n\n\tuseEffect( () => {\n\t\tconst observer = new ResizeObserver( ( [ { target } ] ) => {\n\t\t\tsetIsOverflown( target.scrollWidth > target.clientWidth );\n\t\t} );\n\n\t\tif ( el ) {\n\t\t\tobserver.observe( el );\n\t\t}\n\n\t\treturn () => {\n\t\t\tobserver.disconnect();\n\t\t};\n\t}, [ el ] );\n\n\treturn [ setEl, isOverflowing ] as const;\n};\n","import * as React from 'react';\nimport { forwardRef } from 'react';\nimport { Tooltip } from '@elementor/ui';\n\ntype EditableFieldProps< T extends React.ElementType > = {\n\tvalue: string;\n\terror?: string;\n\tas?: T;\n} & React.ComponentPropsWithRef< T >;\n\nexport const EditableField = forwardRef(\n\t< T extends React.ElementType >(\n\t\t{ value, error, as: Component = 'span', ...props }: EditableFieldProps< T >,\n\t\tref: unknown\n\t) => {\n\t\treturn (\n\t\t\t<Tooltip title={ error } open={ !! error } placement=\"top\">\n\t\t\t\t<Component ref={ ref } { ...props }>\n\t\t\t\t\t{ value }\n\t\t\t\t</Component>\n\t\t\t</Tooltip>\n\t\t);\n\t}\n);\n","import { useEffect, useRef, useState } from 'react';\n\ntype UseEditableParams = {\n\tvalue: string;\n\tonSubmit: ( value: string ) => unknown;\n\tvalidation?: ( value: string ) => string | undefined | null;\n\tonClick?: ( event: React.MouseEvent< HTMLDivElement > ) => void;\n};\n\nexport const useEditable = ( { value, onSubmit, validation, onClick }: UseEditableParams ) => {\n\tconst [ isEditing, setIsEditing ] = useState( false );\n\tconst [ error, setError ] = useState< string | null | undefined >( null );\n\n\tconst ref = useSelection( isEditing );\n\n\tconst openEditMode = () => {\n\t\tsetIsEditing( true );\n\t};\n\n\tconst closeEditMode = () => {\n\t\tref.current?.blur();\n\n\t\tsetError( null );\n\t\tsetIsEditing( false );\n\t};\n\n\tconst submit = ( newValue: string ) => {\n\t\tif ( ! error ) {\n\t\t\ttry {\n\t\t\t\tonSubmit( newValue );\n\t\t\t} finally {\n\t\t\t\tcloseEditMode();\n\t\t\t}\n\t\t}\n\t};\n\n\tconst onChange = ( event: React.ChangeEvent< HTMLSpanElement > ) => {\n\t\tconst { innerText: newValue } = event.target;\n\n\t\tif ( validation ) {\n\t\t\tsetError( validation( newValue ) );\n\t\t}\n\t};\n\n\tconst handleKeyDown = ( event: React.KeyboardEvent ) => {\n\t\tevent.stopPropagation();\n\n\t\tif ( [ 'Escape' ].includes( event.key ) ) {\n\t\t\treturn closeEditMode();\n\t\t}\n\n\t\tif ( [ 'Enter' ].includes( event.key ) ) {\n\t\t\tevent.preventDefault();\n\t\t\treturn submit( ( event.target as HTMLElement ).innerText );\n\t\t}\n\t};\n\n\tconst handleClick = ( event: React.MouseEvent< HTMLDivElement > ) => {\n\t\tif ( isEditing ) {\n\t\t\tevent.stopPropagation();\n\t\t}\n\n\t\tonClick?.( event );\n\t};\n\n\tconst listeners = {\n\t\tonClick: handleClick,\n\t\tonKeyDown: handleKeyDown,\n\t\tonInput: onChange,\n\t\tonBlur: closeEditMode,\n\t} as const;\n\n\tconst attributes = {\n\t\tvalue,\n\t\trole: 'textbox',\n\t\tcontentEditable: isEditing,\n\t\t...( isEditing && {\n\t\t\tsuppressContentEditableWarning: true,\n\t\t} ),\n\t} as const;\n\n\treturn {\n\t\tref,\n\t\tisEditing,\n\t\topenEditMode,\n\t\tcloseEditMode,\n\t\tvalue,\n\t\terror,\n\t\tgetProps: () => ( { ...listeners, ...attributes } ),\n\t} as const;\n};\n\nconst useSelection = ( isEditing: boolean ) => {\n\tconst ref = useRef< HTMLElement | null >( null );\n\n\tuseEffect( () => {\n\t\tif ( isEditing ) {\n\t\t\tselectAll( ref.current );\n\t\t}\n\t}, [ isEditing ] );\n\n\treturn ref;\n};\n\nconst selectAll = ( el: HTMLElement | null ) => {\n\tconst selection = getSelection();\n\n\tif ( ! selection || ! el ) {\n\t\treturn;\n\t}\n\n\tconst range = document.createRange();\n\trange.selectNodeContents( el );\n\n\tselection.removeAllRanges();\n\tselection.addRange( range );\n};\n"],"mappings":"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","names":["React","forwardRef","Tooltip","useEffect","useState"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elementor/editor-ui",
|
|
3
3
|
"description": "Elementor Editor UI",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "Elementor Team",
|
|
7
7
|
"homepage": "https://elementor.com/",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"react": "^18.3.1"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@elementor/ui": "1.
|
|
39
|
+
"@elementor/ui": "1.26.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"tsup": "^8.3.5"
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
import { Tooltip } from '@elementor/ui';
|
|
4
|
+
|
|
5
|
+
type EditableFieldProps< T extends React.ElementType > = {
|
|
6
|
+
value: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
as?: T;
|
|
9
|
+
} & React.ComponentPropsWithRef< T >;
|
|
10
|
+
|
|
11
|
+
export const EditableField = forwardRef(
|
|
12
|
+
< T extends React.ElementType >(
|
|
13
|
+
{ value, error, as: Component = 'span', ...props }: EditableFieldProps< T >,
|
|
14
|
+
ref: unknown
|
|
15
|
+
) => {
|
|
16
|
+
return (
|
|
17
|
+
<Tooltip title={ error } open={ !! error } placement="top">
|
|
18
|
+
<Component ref={ ref } { ...props }>
|
|
19
|
+
{ value }
|
|
20
|
+
</Component>
|
|
21
|
+
</Tooltip>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
|
|
3
|
+
import { useEditable } from '../use-editable';
|
|
4
|
+
|
|
5
|
+
describe( 'useEditable', () => {
|
|
6
|
+
it( 'should return editable content attributes and handlers', () => {
|
|
7
|
+
// Arrange & Act.
|
|
8
|
+
const { result } = renderHook( () =>
|
|
9
|
+
useEditable( {
|
|
10
|
+
value: '',
|
|
11
|
+
onSubmit: jest.fn(),
|
|
12
|
+
} )
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const props = result.current.getProps();
|
|
16
|
+
|
|
17
|
+
// Assert.
|
|
18
|
+
expect( props ).toEqual( {
|
|
19
|
+
value: '',
|
|
20
|
+
role: 'textbox',
|
|
21
|
+
contentEditable: false,
|
|
22
|
+
suppressContentEditableWarning: undefined,
|
|
23
|
+
onBlur: expect.any( Function ),
|
|
24
|
+
onClick: expect.any( Function ),
|
|
25
|
+
onInput: expect.any( Function ),
|
|
26
|
+
onKeyDown: expect.any( Function ),
|
|
27
|
+
} );
|
|
28
|
+
} );
|
|
29
|
+
|
|
30
|
+
it( 'should set editable to true', () => {
|
|
31
|
+
// Arrange & Act.
|
|
32
|
+
const { result } = renderHook( () => useEditable( { value: '', onSubmit: jest.fn() } ) );
|
|
33
|
+
|
|
34
|
+
// Assert.
|
|
35
|
+
expect( result.current.isEditing ).toBe( false );
|
|
36
|
+
|
|
37
|
+
// Act.
|
|
38
|
+
act( result.current.openEditMode );
|
|
39
|
+
|
|
40
|
+
// Assert.
|
|
41
|
+
expect( result.current.isEditing ).toBe( true );
|
|
42
|
+
expect( result.current.getProps() ).toMatchObject( {
|
|
43
|
+
contentEditable: true,
|
|
44
|
+
suppressContentEditableWarning: true,
|
|
45
|
+
} );
|
|
46
|
+
} );
|
|
47
|
+
|
|
48
|
+
it( 'should call onSubmit with the new value on enter', async () => {
|
|
49
|
+
// Arrange.
|
|
50
|
+
const onSubmit = jest.fn();
|
|
51
|
+
const value = 'Some value';
|
|
52
|
+
const newValue = 'New value';
|
|
53
|
+
const validation = jest.fn();
|
|
54
|
+
|
|
55
|
+
// Act.
|
|
56
|
+
const { result } = renderHook( () =>
|
|
57
|
+
useEditable( {
|
|
58
|
+
value,
|
|
59
|
+
onSubmit,
|
|
60
|
+
validation,
|
|
61
|
+
} )
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
act( () => {
|
|
65
|
+
result.current.openEditMode();
|
|
66
|
+
result.current.getProps().onInput( { target: { innerText: newValue } } as never );
|
|
67
|
+
} );
|
|
68
|
+
|
|
69
|
+
// Assert.
|
|
70
|
+
expect( validation ).toHaveBeenCalledWith( newValue );
|
|
71
|
+
|
|
72
|
+
// Act.
|
|
73
|
+
const { onKeyDown } = result.current.getProps();
|
|
74
|
+
|
|
75
|
+
act( () => {
|
|
76
|
+
onKeyDown( {
|
|
77
|
+
key: 'Enter',
|
|
78
|
+
stopPropagation: jest.fn(),
|
|
79
|
+
preventDefault: jest.fn(),
|
|
80
|
+
target: { innerText: newValue },
|
|
81
|
+
} as never );
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
expect( onSubmit ).toHaveBeenCalledWith( newValue );
|
|
85
|
+
} );
|
|
86
|
+
|
|
87
|
+
it( 'should remove the editable content attribute on blur', () => {
|
|
88
|
+
// Arrange
|
|
89
|
+
const { result } = renderHook( () =>
|
|
90
|
+
useEditable( {
|
|
91
|
+
value: '',
|
|
92
|
+
onSubmit: jest.fn(),
|
|
93
|
+
} )
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Act.
|
|
97
|
+
act( result.current.openEditMode );
|
|
98
|
+
|
|
99
|
+
// Assert.
|
|
100
|
+
expect( result.current.isEditing ).toBe( true );
|
|
101
|
+
|
|
102
|
+
// Act.
|
|
103
|
+
act( result.current.getProps().onBlur );
|
|
104
|
+
|
|
105
|
+
// Assert.
|
|
106
|
+
expect( result.current.isEditing ).toBe( false );
|
|
107
|
+
} );
|
|
108
|
+
|
|
109
|
+
it( 'should set error message id validation fails', () => {
|
|
110
|
+
// Arrange.
|
|
111
|
+
const newValue = 'invalid-value';
|
|
112
|
+
const value = 'Some value';
|
|
113
|
+
const onSubmit = jest.fn();
|
|
114
|
+
|
|
115
|
+
const validation = ( v: string ) => {
|
|
116
|
+
if ( v === newValue ) {
|
|
117
|
+
return 'Nope';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const { result } = renderHook( () =>
|
|
124
|
+
useEditable( {
|
|
125
|
+
value,
|
|
126
|
+
onSubmit,
|
|
127
|
+
validation,
|
|
128
|
+
} )
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Act.
|
|
132
|
+
act( result.current.openEditMode );
|
|
133
|
+
|
|
134
|
+
// Assert.
|
|
135
|
+
expect( result.current.error ).toBeNull();
|
|
136
|
+
|
|
137
|
+
// Act.
|
|
138
|
+
act( () => {
|
|
139
|
+
result.current.getProps().onInput( { target: { innerText: newValue } } as never );
|
|
140
|
+
} );
|
|
141
|
+
|
|
142
|
+
// Assert.
|
|
143
|
+
expect( result.current.error ).toBe( 'Nope' );
|
|
144
|
+
|
|
145
|
+
// Act.
|
|
146
|
+
act( () => {
|
|
147
|
+
result.current.getProps().onKeyDown( {
|
|
148
|
+
key: 'Enter',
|
|
149
|
+
stopPropagation: jest.fn(),
|
|
150
|
+
preventDefault: jest.fn(),
|
|
151
|
+
target: { innerText: newValue },
|
|
152
|
+
} as never );
|
|
153
|
+
} );
|
|
154
|
+
|
|
155
|
+
// Assert.
|
|
156
|
+
expect( onSubmit ).not.toHaveBeenCalled();
|
|
157
|
+
} );
|
|
158
|
+
} );
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
type UseEditableParams = {
|
|
4
|
+
value: string;
|
|
5
|
+
onSubmit: ( value: string ) => unknown;
|
|
6
|
+
validation?: ( value: string ) => string | undefined | null;
|
|
7
|
+
onClick?: ( event: React.MouseEvent< HTMLDivElement > ) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const useEditable = ( { value, onSubmit, validation, onClick }: UseEditableParams ) => {
|
|
11
|
+
const [ isEditing, setIsEditing ] = useState( false );
|
|
12
|
+
const [ error, setError ] = useState< string | null | undefined >( null );
|
|
13
|
+
|
|
14
|
+
const ref = useSelection( isEditing );
|
|
15
|
+
|
|
16
|
+
const openEditMode = () => {
|
|
17
|
+
setIsEditing( true );
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const closeEditMode = () => {
|
|
21
|
+
ref.current?.blur();
|
|
22
|
+
|
|
23
|
+
setError( null );
|
|
24
|
+
setIsEditing( false );
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const submit = ( newValue: string ) => {
|
|
28
|
+
if ( ! error ) {
|
|
29
|
+
try {
|
|
30
|
+
onSubmit( newValue );
|
|
31
|
+
} finally {
|
|
32
|
+
closeEditMode();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const onChange = ( event: React.ChangeEvent< HTMLSpanElement > ) => {
|
|
38
|
+
const { innerText: newValue } = event.target;
|
|
39
|
+
|
|
40
|
+
if ( validation ) {
|
|
41
|
+
setError( validation( newValue ) );
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleKeyDown = ( event: React.KeyboardEvent ) => {
|
|
46
|
+
event.stopPropagation();
|
|
47
|
+
|
|
48
|
+
if ( [ 'Escape' ].includes( event.key ) ) {
|
|
49
|
+
return closeEditMode();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ( [ 'Enter' ].includes( event.key ) ) {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
return submit( ( event.target as HTMLElement ).innerText );
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handleClick = ( event: React.MouseEvent< HTMLDivElement > ) => {
|
|
59
|
+
if ( isEditing ) {
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onClick?.( event );
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const listeners = {
|
|
67
|
+
onClick: handleClick,
|
|
68
|
+
onKeyDown: handleKeyDown,
|
|
69
|
+
onInput: onChange,
|
|
70
|
+
onBlur: closeEditMode,
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
const attributes = {
|
|
74
|
+
value,
|
|
75
|
+
role: 'textbox',
|
|
76
|
+
contentEditable: isEditing,
|
|
77
|
+
...( isEditing && {
|
|
78
|
+
suppressContentEditableWarning: true,
|
|
79
|
+
} ),
|
|
80
|
+
} as const;
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
ref,
|
|
84
|
+
isEditing,
|
|
85
|
+
openEditMode,
|
|
86
|
+
closeEditMode,
|
|
87
|
+
value,
|
|
88
|
+
error,
|
|
89
|
+
getProps: () => ( { ...listeners, ...attributes } ),
|
|
90
|
+
} as const;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const useSelection = ( isEditing: boolean ) => {
|
|
94
|
+
const ref = useRef< HTMLElement | null >( null );
|
|
95
|
+
|
|
96
|
+
useEffect( () => {
|
|
97
|
+
if ( isEditing ) {
|
|
98
|
+
selectAll( ref.current );
|
|
99
|
+
}
|
|
100
|
+
}, [ isEditing ] );
|
|
101
|
+
|
|
102
|
+
return ref;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const selectAll = ( el: HTMLElement | null ) => {
|
|
106
|
+
const selection = getSelection();
|
|
107
|
+
|
|
108
|
+
if ( ! selection || ! el ) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const range = document.createRange();
|
|
113
|
+
range.selectNodeContents( el );
|
|
114
|
+
|
|
115
|
+
selection.removeAllRanges();
|
|
116
|
+
selection.addRange( range );
|
|
117
|
+
};
|