Skip to content

Commit

Permalink
feat: onEdit event function returns namespace`. (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
jaywcjlove committed Jul 17, 2023
1 parent 9f655f0 commit a9628fc
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 128 deletions.
6 changes: 6 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,11 @@ export default function Demo() {
}}
onEdit={(opts) => {
console.log('opts:', opts)
// opts.namespace: ['object', 'child', 'last']
// opts.oldValue: null
// opts.type: "value"
// opts.value: "NULL3"
return true;
}}
components={{
objectKey: ObjectKey
Expand Down Expand Up @@ -756,6 +761,7 @@ export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T>
oldValue: unknown;
keyName?: string | number;
parentName?: string | number;
namespace?: Array<string | number>;
type?: 'value' | 'key';
}) => boolean;
/**
Expand Down
29 changes: 19 additions & 10 deletions core/src/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import type { CountInfoExtraProps } from './countInfoExtra';

export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T> {
/**
* When a callback function is passed in, edit functionality is enabled. The callback is invoked before edits are completed.
* When a callback function is passed in, edit functionality is enabled. The callback is invoked before edits are completed.
* @returns {boolean} Returning false from onEdit will prevent the change from being made.
*/
onEdit?: (option: {
value: unknown;
oldValue: unknown;
keyName?: string | number;
parentName?: string | number;
namespace?: Array<string | number>;
type?: 'value' | 'key';
}) => boolean;
/**
Expand All @@ -24,8 +25,8 @@ export interface JsonViewEditorProps<T extends object> extends JsonViewProps<T>
*/
onAdd?: CountInfoExtraProps<T>['onAdd'];
/**
* When a callback function is passed in, delete functionality is enabled. The callback is invoked before deletions are completed.
* @returns Returning false from onDelete will prevent the change from being made.
* When a callback function is passed in, delete functionality is enabled. The callback is invoked before deletions are completed.
* @returns Returning false from onDelete will prevent the change from being made.
*/
onDelete?: CountInfoExtraProps<T>['onDelete'];
/** Whether enable edit feature. @default true */
Expand All @@ -37,14 +38,22 @@ const JsonViewEditor = forwardRef<HTMLDivElement, JsonViewEditorProps<object>>((
const comps: JsonViewEditorProps<object>['components'] = {
...components,
countInfoExtra: (reprops) => <CountInfoExtra {...reprops} editable={editable} onAdd={onAdd} onDelete={onDelete} />,
objectKey: (reprops) => <ObjectKey {...reprops} editableValue={editable} onEdit={onEdit} render={components?.objectKey} />,
objectKey: (reprops) => (
<ObjectKey {...reprops} editableValue={editable} onEdit={onEdit} render={components?.objectKey} />
),
value: (reprops) => {
return <ReValue {...reprops} editableValue={editable} displayDataTypes={displayDataTypes} onDelete={onDelete} onEdit={onEdit} />
}
}
return (
<JsonView {...reset} displayDataTypes={false} components={comps} ref={ref} />
);
return (
<ReValue
{...reprops}
editableValue={editable}
displayDataTypes={displayDataTypes}
onDelete={onDelete}
onEdit={onEdit}
/>
);
},
};
return <JsonView {...reset} displayDataTypes={false} components={comps} ref={ref} />;
});

export default JsonViewEditor;
68 changes: 50 additions & 18 deletions core/src/editor/objectKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,21 @@ export interface ObjectKeyProps<T extends object> extends SemicolonProps {
editableValue?: boolean;
}

export const ObjectKey: FC<ObjectKeyProps<object>>= (props) => {
const { className, value, keyName, parentName, quotes, label, editableValue, onEdit, highlightUpdates = true, render, ...reset } = props;
export const ObjectKey: FC<ObjectKeyProps<object>> = (props) => {
const {
className,
value,
keyName,
parentName,
quotes,
label,
namespace,
editableValue,
onEdit,
highlightUpdates = true,
render,
...reset
} = props;
const [editable, setEditable] = useState(false);
const [curentLabel, setCurentLabel] = useState(label);
useEffect(() => setCurentLabel(label), [label]);
Expand All @@ -27,37 +40,50 @@ export const ObjectKey: FC<ObjectKeyProps<object>>= (props) => {
$edit.current!.contentEditable = 'true';
$edit.current?.focus();
}
}
};

const blur = () => {
setEditable(false);
const blur = async () => {
if ($edit.current) {
if (onEdit) {
const result = await onEdit({
value: $edit.current.innerText,
oldValue: curentLabel as string,
namespace,
keyName,
parentName,
type: 'key',
});
if (result) {
setCurentLabel($edit.current.innerText);
} else {
$edit.current.innerText = curentLabel as string;
}
}
$edit.current.contentEditable = 'false';
$edit.current?.focus();
setCurentLabel($edit.current.innerText);
onEdit && onEdit({ value: $edit.current.innerText, oldValue: curentLabel as string, keyName, parentName, type: 'key' });
}
}
setEditable(false);
};
const focus = () => {
if ($edit.current) {
$edit.current.contentEditable = 'true';
$edit.current?.focus();
}
}
};
const keyDown = (evn: React.KeyboardEvent<HTMLSpanElement>) => {
if (evn.key === 'Enter') {
evn.stopPropagation();
evn.preventDefault();
$edit.current!.contentEditable = 'false';
}
}
};
useEffect(() => {
if ($edit.current) {
$edit.current.addEventListener("paste", (e) => {
$edit.current.addEventListener('paste', (e) => {
e.preventDefault();
// @ts-ignore
const text = e.clipboardData.getData("text/plain");
document.execCommand("insertHTML", false, text);
const text = e.clipboardData.getData('text/plain');
document.execCommand('insertHTML', false, text);
});
}
}, [$edit]);
Expand All @@ -75,15 +101,21 @@ export const ObjectKey: FC<ObjectKeyProps<object>>= (props) => {
autoFocus: editable,
suppressContentEditableWarning: true,
children: editable ? curentLabel : content,
}
};
if (render) {
spanProps.value = value;
spanProps.keyName = keyName;
spanProps.parentName = parentName;
return render({ className, ...reset, ...spanProps, parentName, label: curentLabel as string, children: editable ? curentLabel : content, ref: $edit });
return render({
className,
...reset,
...spanProps,
parentName,
label: curentLabel as string,
children: editable ? curentLabel : content,
ref: $edit,
});
}

return <Label className={className} {...reset} autoFocus={editable} {...spanProps} ref={$edit} />;
}


};
69 changes: 48 additions & 21 deletions core/src/editor/value.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import { EditIcon } from './icon/edit';
import { DeleteIcon } from './icon/delete';
import type { JsonViewEditorProps } from './';

const Quotes: FC<PropsWithChildren<React.HTMLAttributes<HTMLSpanElement> & { quotes?: JsonViewEditorProps<object>['quotes']; show?: boolean; }>> = ({ show, style, quotes }) => {
const Quotes: FC<
PropsWithChildren<
React.HTMLAttributes<HTMLSpanElement> & { quotes?: JsonViewEditorProps<object>['quotes']; show?: boolean }
>
> = ({ show, style, quotes }) => {
if (!quotes || !show) return;
return <span style={style}>{quotes}</span>;
}
};

export interface ReValueProps<T extends object> extends React.HTMLAttributes<HTMLSpanElement> {
onEdit?: JsonViewEditorProps<T>['onEdit'];
Expand All @@ -19,14 +23,31 @@ export interface ReValueProps<T extends object> extends React.HTMLAttributes<HTM
value?: unknown;
data?: T;
visible?: boolean;
namespace?: Array<string | number>;
editableValue?: boolean;
displayDataTypes?: boolean;
setValue?: React.Dispatch<React.SetStateAction<T>>;
onDelete?: JsonViewEditorProps<T>['onDelete'];
}

export function ReValue<T extends object>(props: ReValueProps<T>) {
const { type, value, setValue, data, keyName, visible, quotes, style, children, displayDataTypes, editableValue, onDelete, onEdit, ...reset } = props;
const {
type,
value,
setValue,
data,
keyName,
visible,
quotes,
style,
children,
namespace,
displayDataTypes,
editableValue,
onDelete,
onEdit,
...reset
} = props;
const [editable, setEditable] = useState(false);
const $edit = useRef<HTMLSpanElement>(null);
const [curentType, setCurentType] = useState(type);
Expand All @@ -40,7 +61,7 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
$edit.current.setAttribute('contentEditable', 'true');
$edit.current?.focus();
}
}
};
const keyDown = (evn: React.KeyboardEvent<HTMLSpanElement>) => {
if (!editableValue) return;
if (evn.key === 'Enter') {
Expand All @@ -51,7 +72,7 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
$edit.current.setAttribute('contentEditable', 'false');
}
}
}
};
const blur = async () => {
if (!editableValue) return;
setEditable(false);
Expand All @@ -70,29 +91,29 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
if (Number.isNaN(text)) {
typeStr = 'number';
}
if (typeof text === 'string' && /^(true|false)$/ig.test(text)) {
text = /^(true)$/ig.test(text) ? true : false;
if (typeof text === 'string' && /^(true|false)$/gi.test(text)) {
text = /^(true)$/gi.test(text) ? true : false;
typeStr = 'boolean';
} else if (typeof text === 'string' && /^[\d]+n$/ig.test(text)) {
text = BigInt(text.replace(/n$/ig, ''));
} else if (typeof text === 'string' && /^[\d]+n$/gi.test(text)) {
text = BigInt(text.replace(/n$/gi, ''));
typeStr = 'bigint';
} else if (typeof text === 'string' && /^(null)$/ig.test(text)) {
} else if (typeof text === 'string' && /^(null)$/gi.test(text)) {
text = null;
typeStr = 'null';
} else if (typeof text === 'string' && /^(undefined)$/ig.test(text)) {
} else if (typeof text === 'string' && /^(undefined)$/gi.test(text)) {
text = undefined;
typeStr = 'undefined';
} else if (typeof text === 'string') {
try {
if(text && text.length > 10 && !isNaN(Date.parse(text))){
if (text && text.length > 10 && !isNaN(Date.parse(text))) {
const dt = new Date(text);
text = dt;
typeStr = 'date';
}
} catch (error) {}
}
if (onEdit) {
const result = await onEdit({ type: 'value', value: text, oldValue: curentChild });
const result = await onEdit({ type: 'value', value: text, oldValue: curentChild, namespace });
if (result) {
setCurentType(typeStr);
setCurentChild(text);
Expand All @@ -102,17 +123,22 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
}
}
}
}
const defaultStyle = { minWidth: 34, minHeight: 18, paddingInline: 3, display: 'inline-block' } as React.CSSProperties;
};
const defaultStyle = {
minWidth: 34,
minHeight: 18,
paddingInline: 3,
display: 'inline-block',
} as React.CSSProperties;
const { type: typeStr, content: childStr } = getValueString(curentChild);
const color = typeMap[typeStr]?.color || '';
const spanProps: React.HTMLAttributes<HTMLSpanElement> = {
...reset,
onBlur: blur,
onKeyDown: keyDown,
spellCheck: false,
style: editable ? {...style,...defaultStyle, color } : {...style, color},
}
style: editable ? { ...style, ...defaultStyle, color } : { ...style, color },
};
let typeView = <Type type={typeStr} />;
if (typeStr === 'null' || typeStr === 'undefined') {
typeView = <Fragment />;
Expand All @@ -123,16 +149,17 @@ export function ReValue<T extends object>(props: ReValueProps<T>) {
delete (data as Record<string, any>)[keyName as string];
setValue({ ...data } as T);
}
}
};
return (
<Fragment>
{displayDataTypes && typeView}
<Quotes style={style} quotes={quotes} show={typeStr === 'string'} />
<span {...spanProps} ref={$edit} data-value={childStr}>{typeof curentChild === 'string' ? curentChild : childStr}</span>
<span {...spanProps} ref={$edit} data-value={childStr}>
{typeof curentChild === 'string' ? curentChild : childStr}
</span>
<Quotes style={style} quotes={quotes} show={typeStr === 'string'} />
{visible && editableValue && onEdit && <EditIcon onClick={click} />}
{visible && editableValue && onDelete && <DeleteIcon onClick={deleteHandle} />}
</Fragment>
);

}
}
Loading

0 comments on commit a9628fc

Please sign in to comment.