@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.
@@ -1,22 +1,22 @@
1
1
 
2
- > @elementor/editor-ui@0.1.0 build
2
+ > @elementor/editor-ui@0.2.0 build
3
3
  > tsup --config=../../tsup.build.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: ../../../tsconfig.json
7
- CLI tsup v8.3.5
7
+ CLI tsup v8.4.0
8
8
  CLI Using tsup config: /home/runner/work/elementor-packages/elementor-packages/tsup.build.ts
9
9
  CLI Target: esnext
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.mjs 1.38 KB
14
- ESM dist/index.mjs.map 2.97 KB
15
- ESM ⚡️ Build success in 116ms
16
- CJS dist/index.js 3.05 KB
17
- CJS dist/index.js.map 3.10 KB
18
- CJS ⚡️ Build success in 121ms
13
+ ESM dist/index.mjs 3.87 KB
14
+ ESM dist/index.mjs.map 8.21 KB
15
+ ESM ⚡️ Build success in 134ms
16
+ CJS dist/index.js 5.65 KB
17
+ CJS dist/index.js.map 8.45 KB
18
+ CJS ⚡️ Build success in 135ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 32739ms
21
- DTS dist/index.d.mts 388.00 B
22
- DTS dist/index.d.ts 388.00 B
20
+ DTS ⚡️ Build success in 40577ms
21
+ DTS dist/index.d.mts 1.48 KB
22
+ DTS dist/index.d.ts 1.48 KB
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
- export { EllipsisWithTooltip };
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
- export { EllipsisWithTooltip };
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
- EllipsisWithTooltip: () => EllipsisWithTooltip
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
- EllipsisWithTooltip
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":["export { EllipsisWithTooltip } from './components/ellipsis-with-tooltip';\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,YAAuB;AACvB,mBAAoC;AACpC,gBAA6B;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,qBAAQ,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,eAAK,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,QAAI,uBAAgC,IAAK;AAC3D,QAAM,CAAE,eAAe,cAAe,QAAI,uBAAU,KAAM;AAE1D,8BAAW,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/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
- EllipsisWithTooltip
144
+ EditableField,
145
+ EllipsisWithTooltip,
146
+ useEditable
46
147
  };
47
148
  //# sourceMappingURL=index.mjs.map
@@ -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.1.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.24.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
+ };
package/src/index.ts CHANGED
@@ -1 +1,6 @@
1
+ // components
1
2
  export { EllipsisWithTooltip } from './components/ellipsis-with-tooltip';
3
+ export { EditableField } from './components/editable-field';
4
+
5
+ // hooks
6
+ export { useEditable } from './hooks/use-editable';