@elementor/editor-ui 0.1.0 → 0.3.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.3.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 5.11 KB
14
+ ESM dist/index.mjs.map 10.24 KB
15
+ ESM ⚡️ Build success in 224ms
16
+ CJS dist/index.js 7.00 KB
17
+ CJS dist/index.js.map 10.53 KB
18
+ CJS ⚡️ Build success in 214ms
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 36327ms
21
+ DTS dist/index.d.mts 1.77 KB
22
+ DTS dist/index.d.ts 1.77 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @elementor/editor-ui
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - dd8654a: Add introduction modal to class manager panel
8
+
9
+ ### Patch Changes
10
+
11
+ - 158d092: Set `EditableField` component to full width.
12
+
13
+ ## 0.2.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 910044c: update `@elementor/icons` and `@elementor/ui` packages.
18
+ - 4ed562a: Support renaming label in classes manager.
19
+ - 5caf78d: update `@elementor/ui` package.
20
+
3
21
  ## 0.1.0
4
22
 
5
23
  ### Minor Changes
package/dist/index.d.mts CHANGED
@@ -1,10 +1,45 @@
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 IntroductionModalProps = {
13
+ open: boolean;
14
+ handleClose: (shouldShowAgain: boolean) => void;
15
+ title: string;
16
+ content: React$1.ReactNode;
17
+ };
18
+ declare const IntroductionModal: ({ open, handleClose, title, content }: IntroductionModalProps) => React$1.JSX.Element;
19
+
20
+ type UseEditableParams = {
21
+ value: string;
22
+ onSubmit: (value: string) => unknown;
23
+ validation?: (value: string) => string | undefined | null;
24
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
25
+ };
26
+ declare const useEditable: ({ value, onSubmit, validation, onClick }: UseEditableParams) => {
27
+ readonly ref: React$1.MutableRefObject<HTMLElement | null>;
28
+ readonly isEditing: boolean;
29
+ readonly openEditMode: () => void;
30
+ readonly closeEditMode: () => void;
31
+ readonly value: string;
32
+ readonly error: string | null | undefined;
33
+ readonly getProps: () => {
34
+ suppressContentEditableWarning?: boolean | undefined;
35
+ value: string;
36
+ role: "textbox";
37
+ contentEditable: boolean;
38
+ onClick: (event: React.MouseEvent<HTMLDivElement>) => void;
39
+ onKeyDown: (event: React.KeyboardEvent) => void;
40
+ onInput: (event: React.ChangeEvent<HTMLSpanElement>) => void;
41
+ onBlur: () => void;
42
+ };
43
+ };
44
+
45
+ export { EditableField, EllipsisWithTooltip, IntroductionModal, useEditable };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,45 @@
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 IntroductionModalProps = {
13
+ open: boolean;
14
+ handleClose: (shouldShowAgain: boolean) => void;
15
+ title: string;
16
+ content: React$1.ReactNode;
17
+ };
18
+ declare const IntroductionModal: ({ open, handleClose, title, content }: IntroductionModalProps) => React$1.JSX.Element;
19
+
20
+ type UseEditableParams = {
21
+ value: string;
22
+ onSubmit: (value: string) => unknown;
23
+ validation?: (value: string) => string | undefined | null;
24
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
25
+ };
26
+ declare const useEditable: ({ value, onSubmit, validation, onClick }: UseEditableParams) => {
27
+ readonly ref: React$1.MutableRefObject<HTMLElement | null>;
28
+ readonly isEditing: boolean;
29
+ readonly openEditMode: () => void;
30
+ readonly closeEditMode: () => void;
31
+ readonly value: string;
32
+ readonly error: string | null | undefined;
33
+ readonly getProps: () => {
34
+ suppressContentEditableWarning?: boolean | undefined;
35
+ value: string;
36
+ role: "textbox";
37
+ contentEditable: boolean;
38
+ onClick: (event: React.MouseEvent<HTMLDivElement>) => void;
39
+ onKeyDown: (event: React.KeyboardEvent) => void;
40
+ onInput: (event: React.ChangeEvent<HTMLSpanElement>) => void;
41
+ onBlur: () => void;
42
+ };
43
+ };
44
+
45
+ export { EditableField, EllipsisWithTooltip, IntroductionModal, useEditable };
package/dist/index.js CHANGED
@@ -30,7 +30,10 @@ 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
+ IntroductionModal: () => IntroductionModal,
36
+ useEditable: () => useEditable
34
37
  });
35
38
  module.exports = __toCommonJS(index_exports);
36
39
 
@@ -77,8 +80,133 @@ var useIsOverflowing = () => {
77
80
  }, [el]);
78
81
  return [setEl, isOverflowing];
79
82
  };
83
+
84
+ // src/components/editable-field.tsx
85
+ var React2 = __toESM(require("react"));
86
+ var import_react2 = require("react");
87
+ var import_ui2 = require("@elementor/ui");
88
+ var EditableField = (0, import_react2.forwardRef)(
89
+ ({ value, error, as: Component = "span", sx, ...props }, ref) => {
90
+ return /* @__PURE__ */ React2.createElement(import_ui2.Tooltip, { title: error, open: !!error, placement: "top" }, /* @__PURE__ */ React2.createElement(Component, { ref, sx: { width: "100%", ...sx }, ...props }, value));
91
+ }
92
+ );
93
+
94
+ // src/components/introduction-modal.tsx
95
+ var React3 = __toESM(require("react"));
96
+ var import_react3 = require("react");
97
+ var import_ui3 = require("@elementor/ui");
98
+ var import_i18n = require("@wordpress/i18n");
99
+ var IntroductionModal = ({ open, handleClose, title, content }) => {
100
+ const [shouldShowAgain, setShouldShowAgain] = (0, import_react3.useState)(true);
101
+ return /* @__PURE__ */ React3.createElement(import_ui3.Dialog, { open, onClose: handleClose, maxWidth: "sm" }, /* @__PURE__ */ React3.createElement(import_ui3.DialogHeader, { logo: false }, /* @__PURE__ */ React3.createElement(import_ui3.DialogTitle, null, title)), content, /* @__PURE__ */ React3.createElement(import_ui3.DialogActions, null, /* @__PURE__ */ React3.createElement(
102
+ import_ui3.FormControlLabel,
103
+ {
104
+ sx: { marginRight: "auto" },
105
+ control: /* @__PURE__ */ React3.createElement(
106
+ import_ui3.Checkbox,
107
+ {
108
+ checked: !shouldShowAgain,
109
+ onChange: () => setShouldShowAgain(!shouldShowAgain)
110
+ }
111
+ ),
112
+ label: (0, import_i18n.__)("Don't show this again")
113
+ }
114
+ ), /* @__PURE__ */ React3.createElement(import_ui3.Button, { size: "medium", onClick: () => handleClose(shouldShowAgain), variant: "contained" }, (0, import_i18n.__)("Got it"))));
115
+ };
116
+
117
+ // src/hooks/use-editable.ts
118
+ var import_react4 = require("react");
119
+ var useEditable = ({ value, onSubmit, validation, onClick }) => {
120
+ const [isEditing, setIsEditing] = (0, import_react4.useState)(false);
121
+ const [error, setError] = (0, import_react4.useState)(null);
122
+ const ref = useSelection(isEditing);
123
+ const openEditMode = () => {
124
+ setIsEditing(true);
125
+ };
126
+ const closeEditMode = () => {
127
+ ref.current?.blur();
128
+ setError(null);
129
+ setIsEditing(false);
130
+ };
131
+ const submit = (newValue) => {
132
+ if (!error) {
133
+ try {
134
+ onSubmit(newValue);
135
+ } finally {
136
+ closeEditMode();
137
+ }
138
+ }
139
+ };
140
+ const onChange = (event) => {
141
+ const { innerText: newValue } = event.target;
142
+ if (validation) {
143
+ setError(validation(newValue));
144
+ }
145
+ };
146
+ const handleKeyDown = (event) => {
147
+ event.stopPropagation();
148
+ if (["Escape"].includes(event.key)) {
149
+ return closeEditMode();
150
+ }
151
+ if (["Enter"].includes(event.key)) {
152
+ event.preventDefault();
153
+ return submit(event.target.innerText);
154
+ }
155
+ };
156
+ const handleClick = (event) => {
157
+ if (isEditing) {
158
+ event.stopPropagation();
159
+ }
160
+ onClick?.(event);
161
+ };
162
+ const listeners = {
163
+ onClick: handleClick,
164
+ onKeyDown: handleKeyDown,
165
+ onInput: onChange,
166
+ onBlur: closeEditMode
167
+ };
168
+ const attributes = {
169
+ value,
170
+ role: "textbox",
171
+ contentEditable: isEditing,
172
+ ...isEditing && {
173
+ suppressContentEditableWarning: true
174
+ }
175
+ };
176
+ return {
177
+ ref,
178
+ isEditing,
179
+ openEditMode,
180
+ closeEditMode,
181
+ value,
182
+ error,
183
+ getProps: () => ({ ...listeners, ...attributes })
184
+ };
185
+ };
186
+ var useSelection = (isEditing) => {
187
+ const ref = (0, import_react4.useRef)(null);
188
+ (0, import_react4.useEffect)(() => {
189
+ if (isEditing) {
190
+ selectAll(ref.current);
191
+ }
192
+ }, [isEditing]);
193
+ return ref;
194
+ };
195
+ var selectAll = (el) => {
196
+ const selection = getSelection();
197
+ if (!selection || !el) {
198
+ return;
199
+ }
200
+ const range = document.createRange();
201
+ range.selectNodeContents(el);
202
+ selection.removeAllRanges();
203
+ selection.addRange(range);
204
+ };
80
205
  // Annotate the CommonJS export names for ESM import in node:
81
206
  0 && (module.exports = {
82
- EllipsisWithTooltip
207
+ EditableField,
208
+ EllipsisWithTooltip,
209
+ IntroductionModal,
210
+ useEditable
83
211
  });
84
212
  //# 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/components/introduction-modal.tsx","../src/hooks/use-editable.ts"],"sourcesContent":["// components\nexport { EllipsisWithTooltip } from './components/ellipsis-with-tooltip';\nexport { EditableField } from './components/editable-field';\nexport { IntroductionModal } from './components/introduction-modal';\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', sx, ...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 } sx={ { width: '100%', ...sx } } { ...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 * as React from 'react';\nimport { useState } from 'react';\nimport { Button, Checkbox, Dialog, DialogActions, DialogHeader, DialogTitle, FormControlLabel } from '@elementor/ui';\nimport { __ } from '@wordpress/i18n';\n\ntype IntroductionModalProps = {\n\topen: boolean;\n\thandleClose: ( shouldShowAgain: boolean ) => void;\n\ttitle: string;\n\tcontent: React.ReactNode;\n};\n\nexport const IntroductionModal = ( { open, handleClose, title, content }: IntroductionModalProps ) => {\n\tconst [ shouldShowAgain, setShouldShowAgain ] = useState( true );\n\n\treturn (\n\t\t<Dialog open={ open } onClose={ handleClose } maxWidth={ 'sm' }>\n\t\t\t<DialogHeader logo={ false }>\n\t\t\t\t<DialogTitle>{ title }</DialogTitle>\n\t\t\t</DialogHeader>\n\t\t\t{ content }\n\t\t\t<DialogActions>\n\t\t\t\t<FormControlLabel\n\t\t\t\t\tsx={ { marginRight: 'auto' } }\n\t\t\t\t\tcontrol={\n\t\t\t\t\t\t<Checkbox\n\t\t\t\t\t\t\tchecked={ ! shouldShowAgain }\n\t\t\t\t\t\t\tonChange={ () => setShouldShowAgain( ! shouldShowAgain ) }\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\tlabel={ __( \"Don't show this again\" ) }\n\t\t\t\t/>\n\t\t\t\t<Button size={ 'medium' } onClick={ () => handleClose( shouldShowAgain ) } variant=\"contained\">\n\t\t\t\t\t{ __( 'Got it' ) }\n\t\t\t\t</Button>\n\t\t\t</DialogActions>\n\t\t</Dialog>\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","React","import_react","import_ui","import_react"]}
package/dist/index.mjs CHANGED
@@ -41,7 +41,132 @@ 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", sx, ...props }, ref) => {
51
+ return /* @__PURE__ */ React2.createElement(Tooltip2, { title: error, open: !!error, placement: "top" }, /* @__PURE__ */ React2.createElement(Component, { ref, sx: { width: "100%", ...sx }, ...props }, value));
52
+ }
53
+ );
54
+
55
+ // src/components/introduction-modal.tsx
56
+ import * as React3 from "react";
57
+ import { useState as useState2 } from "react";
58
+ import { Button, Checkbox, Dialog, DialogActions, DialogHeader, DialogTitle, FormControlLabel } from "@elementor/ui";
59
+ import { __ } from "@wordpress/i18n";
60
+ var IntroductionModal = ({ open, handleClose, title, content }) => {
61
+ const [shouldShowAgain, setShouldShowAgain] = useState2(true);
62
+ return /* @__PURE__ */ React3.createElement(Dialog, { open, onClose: handleClose, maxWidth: "sm" }, /* @__PURE__ */ React3.createElement(DialogHeader, { logo: false }, /* @__PURE__ */ React3.createElement(DialogTitle, null, title)), content, /* @__PURE__ */ React3.createElement(DialogActions, null, /* @__PURE__ */ React3.createElement(
63
+ FormControlLabel,
64
+ {
65
+ sx: { marginRight: "auto" },
66
+ control: /* @__PURE__ */ React3.createElement(
67
+ Checkbox,
68
+ {
69
+ checked: !shouldShowAgain,
70
+ onChange: () => setShouldShowAgain(!shouldShowAgain)
71
+ }
72
+ ),
73
+ label: __("Don't show this again")
74
+ }
75
+ ), /* @__PURE__ */ React3.createElement(Button, { size: "medium", onClick: () => handleClose(shouldShowAgain), variant: "contained" }, __("Got it"))));
76
+ };
77
+
78
+ // src/hooks/use-editable.ts
79
+ import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
80
+ var useEditable = ({ value, onSubmit, validation, onClick }) => {
81
+ const [isEditing, setIsEditing] = useState3(false);
82
+ const [error, setError] = useState3(null);
83
+ const ref = useSelection(isEditing);
84
+ const openEditMode = () => {
85
+ setIsEditing(true);
86
+ };
87
+ const closeEditMode = () => {
88
+ ref.current?.blur();
89
+ setError(null);
90
+ setIsEditing(false);
91
+ };
92
+ const submit = (newValue) => {
93
+ if (!error) {
94
+ try {
95
+ onSubmit(newValue);
96
+ } finally {
97
+ closeEditMode();
98
+ }
99
+ }
100
+ };
101
+ const onChange = (event) => {
102
+ const { innerText: newValue } = event.target;
103
+ if (validation) {
104
+ setError(validation(newValue));
105
+ }
106
+ };
107
+ const handleKeyDown = (event) => {
108
+ event.stopPropagation();
109
+ if (["Escape"].includes(event.key)) {
110
+ return closeEditMode();
111
+ }
112
+ if (["Enter"].includes(event.key)) {
113
+ event.preventDefault();
114
+ return submit(event.target.innerText);
115
+ }
116
+ };
117
+ const handleClick = (event) => {
118
+ if (isEditing) {
119
+ event.stopPropagation();
120
+ }
121
+ onClick?.(event);
122
+ };
123
+ const listeners = {
124
+ onClick: handleClick,
125
+ onKeyDown: handleKeyDown,
126
+ onInput: onChange,
127
+ onBlur: closeEditMode
128
+ };
129
+ const attributes = {
130
+ value,
131
+ role: "textbox",
132
+ contentEditable: isEditing,
133
+ ...isEditing && {
134
+ suppressContentEditableWarning: true
135
+ }
136
+ };
137
+ return {
138
+ ref,
139
+ isEditing,
140
+ openEditMode,
141
+ closeEditMode,
142
+ value,
143
+ error,
144
+ getProps: () => ({ ...listeners, ...attributes })
145
+ };
146
+ };
147
+ var useSelection = (isEditing) => {
148
+ const ref = useRef(null);
149
+ useEffect2(() => {
150
+ if (isEditing) {
151
+ selectAll(ref.current);
152
+ }
153
+ }, [isEditing]);
154
+ return ref;
155
+ };
156
+ var selectAll = (el) => {
157
+ const selection = getSelection();
158
+ if (!selection || !el) {
159
+ return;
160
+ }
161
+ const range = document.createRange();
162
+ range.selectNodeContents(el);
163
+ selection.removeAllRanges();
164
+ selection.addRange(range);
165
+ };
44
166
  export {
45
- EllipsisWithTooltip
167
+ EditableField,
168
+ EllipsisWithTooltip,
169
+ IntroductionModal,
170
+ useEditable
46
171
  };
47
172
  //# 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/components/introduction-modal.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', sx, ...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 } sx={ { width: '100%', ...sx } } { ...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 * as React from 'react';\nimport { useState } from 'react';\nimport { Button, Checkbox, Dialog, DialogActions, DialogHeader, DialogTitle, FormControlLabel } from '@elementor/ui';\nimport { __ } from '@wordpress/i18n';\n\ntype IntroductionModalProps = {\n\topen: boolean;\n\thandleClose: ( shouldShowAgain: boolean ) => void;\n\ttitle: string;\n\tcontent: React.ReactNode;\n};\n\nexport const IntroductionModal = ( { open, handleClose, title, content }: IntroductionModalProps ) => {\n\tconst [ shouldShowAgain, setShouldShowAgain ] = useState( true );\n\n\treturn (\n\t\t<Dialog open={ open } onClose={ handleClose } maxWidth={ 'sm' }>\n\t\t\t<DialogHeader logo={ false }>\n\t\t\t\t<DialogTitle>{ title }</DialogTitle>\n\t\t\t</DialogHeader>\n\t\t\t{ content }\n\t\t\t<DialogActions>\n\t\t\t\t<FormControlLabel\n\t\t\t\t\tsx={ { marginRight: 'auto' } }\n\t\t\t\t\tcontrol={\n\t\t\t\t\t\t<Checkbox\n\t\t\t\t\t\t\tchecked={ ! shouldShowAgain }\n\t\t\t\t\t\t\tonChange={ () => setShouldShowAgain( ! shouldShowAgain ) }\n\t\t\t\t\t\t/>\n\t\t\t\t\t}\n\t\t\t\t\tlabel={ __( \"Don't show this again\" ) }\n\t\t\t\t/>\n\t\t\t\t<Button size={ 'medium' } onClick={ () => handleClose( shouldShowAgain ) } variant=\"contained\">\n\t\t\t\t\t{ __( 'Got it' ) }\n\t\t\t\t</Button>\n\t\t\t</DialogActions>\n\t\t</Dialog>\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","React","useState","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.3.0",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -36,6 +36,10 @@
36
36
  "react": "^18.3.1"
37
37
  },
38
38
  "dependencies": {
39
- "@elementor/ui": "1.24.1"
39
+ "@elementor/ui": "1.26.0",
40
+ "@wordpress/i18n": "^5.13.0"
41
+ },
42
+ "devDependencies": {
43
+ "tsup": "^8.3.5"
40
44
  }
41
45
  }
@@ -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', sx, ...props }: EditableFieldProps< T >,
14
+ ref: unknown
15
+ ) => {
16
+ return (
17
+ <Tooltip title={ error } open={ !! error } placement="top">
18
+ <Component ref={ ref } sx={ { width: '100%', ...sx } } { ...props }>
19
+ { value }
20
+ </Component>
21
+ </Tooltip>
22
+ );
23
+ }
24
+ );
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import { useState } from 'react';
3
+ import { Button, Checkbox, Dialog, DialogActions, DialogHeader, DialogTitle, FormControlLabel } from '@elementor/ui';
4
+ import { __ } from '@wordpress/i18n';
5
+
6
+ type IntroductionModalProps = {
7
+ open: boolean;
8
+ handleClose: ( shouldShowAgain: boolean ) => void;
9
+ title: string;
10
+ content: React.ReactNode;
11
+ };
12
+
13
+ export const IntroductionModal = ( { open, handleClose, title, content }: IntroductionModalProps ) => {
14
+ const [ shouldShowAgain, setShouldShowAgain ] = useState( true );
15
+
16
+ return (
17
+ <Dialog open={ open } onClose={ handleClose } maxWidth={ 'sm' }>
18
+ <DialogHeader logo={ false }>
19
+ <DialogTitle>{ title }</DialogTitle>
20
+ </DialogHeader>
21
+ { content }
22
+ <DialogActions>
23
+ <FormControlLabel
24
+ sx={ { marginRight: 'auto' } }
25
+ control={
26
+ <Checkbox
27
+ checked={ ! shouldShowAgain }
28
+ onChange={ () => setShouldShowAgain( ! shouldShowAgain ) }
29
+ />
30
+ }
31
+ label={ __( "Don't show this again" ) }
32
+ />
33
+ <Button size={ 'medium' } onClick={ () => handleClose( shouldShowAgain ) } variant="contained">
34
+ { __( 'Got it' ) }
35
+ </Button>
36
+ </DialogActions>
37
+ </Dialog>
38
+ );
39
+ };
@@ -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,7 @@
1
+ // components
1
2
  export { EllipsisWithTooltip } from './components/ellipsis-with-tooltip';
3
+ export { EditableField } from './components/editable-field';
4
+ export { IntroductionModal } from './components/introduction-modal';
5
+
6
+ // hooks
7
+ export { useEditable } from './hooks/use-editable';