diff --git a/__tests__/tree.test.tsx b/__tests__/tree.test.tsx
index 53743549..5b26b79d 100644
--- a/__tests__/tree.test.tsx
+++ b/__tests__/tree.test.tsx
@@ -13,7 +13,7 @@ test('Renders basic properly', () => {
);
- expect(document.querySelectorAll('.reqore-tree-toggle').length).toBe(7);
+ expect(document.querySelectorAll('.reqore-tree-toggle').length).toBe(8);
});
test(' items can be expanded and collapsed', () => {
diff --git a/package.json b/package.json
index 3efa0ecd..07cecad4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@qoretechnologies/reqore",
- "version": "0.46.9",
+ "version": "0.47.0",
"description": "ReQore is a highly theme-able and modular UI library for React",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/src/components/Drawer/index.tsx b/src/components/Drawer/index.tsx
index d087b802..e2be97ff 100644
--- a/src/components/Drawer/index.tsx
+++ b/src/components/Drawer/index.tsx
@@ -26,6 +26,7 @@ export interface IReqoreDrawerProps extends Omit void;
hasBackdrop?: boolean;
size?: string | 'auto';
+ panelSize?: IReqorePanelProps['size'];
maxSize?: string;
minSize?: string;
opacity?: number;
@@ -157,6 +158,7 @@ export const ReqoreDrawer: React.FC = ({
height,
actions = [],
customZIndex,
+ panelSize,
...rest
}: IReqoreDrawerProps) => {
const animations = useReqoreProperty('animations');
@@ -337,6 +339,7 @@ export const ReqoreDrawer: React.FC = ({
{!_isHidden && (
{
- const theme = useReqoreTheme('main', customTheme, intent);
+ forwardRef(
+ ({ size, children, customTheme, intent, className, ...props }: IReqoreParagraphProps, ref) => {
+ const theme = useReqoreTheme('main', customTheme, intent);
- return (
-
- {children}
-
- );
- }
+ return (
+
+ {children}
+
+ );
+ }
+ )
);
diff --git a/src/components/Tree/index.tsx b/src/components/Tree/index.tsx
index 15045293..475a4f57 100644
--- a/src/components/Tree/index.tsx
+++ b/src/components/Tree/index.tsx
@@ -1,19 +1,36 @@
-import { cloneDeep, size as lodashSize } from 'lodash';
+import {
+ cloneDeep,
+ get,
+ isArray,
+ isBoolean,
+ isNumber,
+ size as lodashSize,
+ set,
+ unset,
+} from 'lodash';
import { useMemo, useState } from 'react';
import styled from 'styled-components';
-import { ReqoreHorizontalSpacer, ReqoreIcon, ReqoreP, ReqorePanel, useReqoreProperty } from '../..';
+import {
+ ReqoreHorizontalSpacer,
+ ReqoreP,
+ ReqorePanel,
+ ReqorePopover,
+ useReqoreProperty,
+} from '../..';
import { GAP_FROM_SIZE, TSizes } from '../../constants/sizes';
import { IReqoreTheme } from '../../constants/theme';
-import { getOneLessSize, getTypeFromValue } from '../../helpers/utils';
+import { getTypeFromValue, parseInputValue } from '../../helpers/utils';
import { IWithReqoreSize } from '../../types/global';
import ReqoreButton, { IReqoreButtonProps } from '../Button';
import ReqoreControlGroup from '../ControlGroup';
import { ReqoreExportModal } from '../ExportModal';
import { IReqorePanelAction, IReqorePanelProps } from '../Panel';
import { getExportActions, getZoomActions, sizeToZoom, zoomToSize } from '../Table/helpers';
+import { IReqoreTreeManagementDialog, ReqoreTreeManagementDialog } from './modal';
export interface IReqoreTreeProps extends IReqorePanelProps, IWithReqoreSize {
data: Record | Array;
+ onDataChange?: (data: Record | Array) => void;
mode?: 'tree' | 'copy';
expanded?: boolean;
showTypes?: boolean;
@@ -23,6 +40,7 @@ export interface IReqoreTreeProps extends IReqorePanelProps, IWithReqoreSize {
zoomable?: boolean;
exportable?: boolean;
defaultZoom?: 0 | 0.5 | 1 | 1.5 | 2;
+ editable?: boolean;
}
export interface ITreeStyle {
@@ -53,10 +71,11 @@ export const ReqoreTree = ({
onItemClick,
withLabelCopy,
showControls = true,
-
+ editable,
exportable,
zoomable,
defaultZoom,
+ onDataChange,
...rest
}: IReqoreTreeProps) => {
const [items, setItems] = useState({});
@@ -65,6 +84,9 @@ export const ReqoreTree = ({
const addNotification = useReqoreProperty('addNotification');
const [zoom, setZoom] = useState(defaultZoom || sizeToZoom[size]);
const [showExportModal, setShowExportModal] = useState<'full' | 'current' | undefined>(undefined);
+ const [managementDialog, setManagementDialog] = useState({
+ open: false,
+ });
const handleTypesClick = () => {
setShowTypes(!_showTypes);
@@ -91,24 +113,27 @@ export const ReqoreTree = ({
const isDeep = () =>
Object.keys(data).some((key: string): boolean => typeof data[key] === 'object');
- const renderTree = (data, k?: any, level = 1, path: string[] = []) => {
- return Object.keys(data).map((key, index) => {
- const dataType: string = getTypeFromValue(data[key]);
+ const renderTree = (_data, k?: any, level = 1, path: string[] = []) => {
+ return Object.keys(_data).map((key, index) => {
+ const dataType: string = getTypeFromValue(_data[key]);
const displayKey: string = key;
- const stateKey = k ? `${k}_${key}` : key;
+ const stateKey = k ? `${k}.${key}` : key;
- let isObject = typeof data[key] === 'object' && data[key] !== null;
- let isExpandable =
- typeof data[key] !== 'object' ||
+ const isObject = typeof _data[key] === 'object' && _data[key] !== null;
+ const isExpandable =
+ typeof _data[key] !== 'object' ||
items[stateKey] ||
(allExpanded && items[stateKey] !== false);
- if (isObject && lodashSize(data[key]) === 0) {
- isObject = false;
- isExpandable = false;
- }
-
- const badges: IReqoreButtonProps['badge'] = [`{...} ${lodashSize(data[key])} items`];
+ const badges: IReqoreButtonProps['badge'] = [
+ {
+ label: `${isArray(_data[key]) ? '[...]' : '{...}'} ${lodashSize(_data[key])} items`,
+ minimal: true,
+ labelEffect: {
+ weight: 'thin',
+ },
+ },
+ ];
if (_showTypes) {
badges.push({
@@ -117,42 +142,167 @@ export const ReqoreTree = ({
});
}
+ const renderEditButton = () => {
+ if (!editable || (isArray(_data) && isObject)) {
+ return null;
+ }
+
+ return (
+ {
+ setManagementDialog({
+ open: true,
+ parentPath: k,
+ path: stateKey,
+ parentType: isArray(_data) ? 'array' : 'object',
+ type: isArray(_data[key]) ? 'array' : 'object',
+ data: {
+ key,
+ value:
+ typeof _data[key] === 'string' &&
+ (isBoolean(parseInputValue(_data[key])) ||
+ isNumber(parseInputValue(_data[key])))
+ ? `"${_data[key]}"`
+ : _data[key],
+ },
+ });
+ }}
+ />
+ );
+ };
+
+ const renderDeleteButton = () => {
+ if (!editable) {
+ return null;
+ }
+
+ return (
+ {
+ let modifiedData = cloneDeep(_data);
+ // Remove the item from the data
+ delete modifiedData[key];
+
+ if (isArray(modifiedData)) {
+ modifiedData = modifiedData.filter((item) => item);
+ }
+ // Update the data
+ const updatedData = k ? set(cloneDeep(data), k, modifiedData) : modifiedData;
+
+ onDataChange?.(updatedData);
+ }}
+ />
+ );
+ };
+
return (
-
+
{isObject ? (
-
+
{level !== 1 && }
handleItemClick(stateKey, isExpandable)}
- flat={false}
badge={badges}
>
{displayKey}
+
+ {editable && isObject ? (
+
+ {
+ setManagementDialog({
+ open: true,
+ path: stateKey,
+ parentType: isArray(_data) ? 'array' : 'object',
+ type: isArray(_data[key]) ? 'array' : 'object',
+ });
+ }}
+ leftIconColor='info'
+ minimal
+ compact
+ />
+
+ ) : null}
+ {renderEditButton()}
+ {renderDeleteButton()}
) : (
{level !== 1 && }
-
{displayKey}:
-
+
+ onItemClick?.(_data[key], [...path, key])}
+ className='reqore-tree-label'
+ customTheme={{
+ text: {
+ color: isBoolean(_data[key])
+ ? _data[key]
+ ? 'success:lighten:5'
+ : 'danger:lighten:10'
+ : isNumber(_data[key])
+ ? 'info:lighten:10'
+ : undefined,
+ },
+ }}
+ size={zoomToSize[zoom]}
+ >
+ {JSON.stringify(_data[key])}
+
{withLabelCopy && (
- {
try {
- navigator.clipboard.writeText(JSON.stringify(data[key]));
+ navigator.clipboard.writeText(JSON.stringify(_data[key]));
addNotification({
content: 'Successfuly copied to clipboard',
id: Date.now().toString(),
@@ -165,18 +315,12 @@ export const ReqoreTree = ({
}}
/>
)}
- onItemClick?.(data[key], [...path, key])}
- className='reqore-tree-label'
- size={zoomToSize[zoom]}
- >
- {JSON.stringify(data[key])}
-
+ {renderEditButton()}
+ {renderDeleteButton()}
)}
{isExpandable && isObject
- ? renderTree(data[key], stateKey, level + 1, [...path, key])
+ ? renderTree(_data[key], stateKey, level + 1, [...path, key])
: null}
);
@@ -250,6 +394,78 @@ export const ReqoreTree = ({
{showExportModal && (
setShowExportModal(undefined)} />
)}
+ {managementDialog.open && (
+ setManagementDialog({ open: false })}
+ onSave={({ key, value, originalData }) => {
+ const modifiedValue =
+ value === '{}'
+ ? {}
+ : value === '[]'
+ ? []
+ : typeof value === 'object'
+ ? value
+ : parseInputValue(value);
+
+ // If there is no path, it means we are adding a new item at the root level
+ if (!managementDialog.path) {
+ const updatedData = cloneDeep(data);
+
+ if (originalData) {
+ unset(updatedData, originalData.key);
+ } else {
+ if (isArray(updatedData)) {
+ updatedData.push(modifiedValue);
+ } else {
+ updatedData[key] = modifiedValue;
+ }
+ }
+
+ onDataChange?.(updatedData);
+
+ setManagementDialog({ open: false });
+
+ return;
+ }
+
+ const modifiedData = cloneDeep(get(data, managementDialog.path));
+
+ let updatedData;
+
+ if (originalData) {
+ updatedData = set(
+ cloneDeep(data),
+ `${managementDialog.parentPath ? `${managementDialog.parentPath}.` : ''}${key}`,
+ modifiedValue
+ );
+
+ if (key !== originalData.key) {
+ unset(
+ updatedData,
+ `${managementDialog.parentPath ? `${managementDialog.parentPath}.` : ''}${
+ originalData.key
+ }`
+ );
+ }
+ } else {
+ // Add the item to the data
+ if (isArray(modifiedData)) {
+ modifiedData.push(modifiedValue);
+ } else {
+ modifiedData[key] = modifiedValue;
+ }
+
+ // Update the data
+ updatedData = set(cloneDeep(data), managementDialog.path, modifiedData);
+ }
+
+ onDataChange?.(updatedData);
+
+ setManagementDialog({ open: false });
+ }}
+ />
+ )}
- {renderTree(data, true)}
+ {renderTree(data)}
+
+ {editable && (
+
+ {
+ setManagementDialog({
+ open: true,
+ path: '',
+ parentType: isArray(data) ? 'array' : 'object',
+ type: isArray(data) ? 'array' : 'object',
+ });
+ }}
+ leftIconColor='info'
+ minimal
+ compact
+ />
+
+ )}
>
);
diff --git a/src/components/Tree/modal.tsx b/src/components/Tree/modal.tsx
new file mode 100644
index 00000000..470cedd3
--- /dev/null
+++ b/src/components/Tree/modal.tsx
@@ -0,0 +1,108 @@
+import { useState } from 'react';
+import { ReqoreTextarea } from '../..';
+import ReqoreButton from '../Button';
+import ReqoreControlGroup from '../ControlGroup';
+import ReqoreInput from '../Input';
+import { IReqoreModalProps, ReqoreModal } from '../Modal';
+import ReqoreTag from '../Tag';
+
+export interface IReqoreTreeManagementDialog extends IReqoreModalProps {
+ open?: boolean;
+ path?: string;
+ parentPath?: string;
+ type?: 'object' | 'array';
+ parentType?: 'object' | 'array';
+ data?: { key: string; value: any };
+ onSave?: (data: {
+ key: string;
+ value: any;
+ originalData?: { key?: string; value?: any };
+ }) => void;
+}
+
+export const ReqoreTreeManagementDialog = ({
+ path,
+ parentType,
+ type,
+ data,
+ onClose,
+ onSave,
+}: IReqoreTreeManagementDialog) => {
+ const [key, setKey] = useState(data?.key);
+ const [value, setValue] = useState(data?.value);
+
+ return (
+ {
+ onSave({
+ key,
+ value,
+ originalData: data,
+ });
+ onClose();
+ },
+ },
+ ]}
+ >
+
+ {type === 'object' || (data?.key && parentType !== 'array') ? (
+
+
+ setKey(e.target.value)}
+ placeholder='Key'
+ fluid
+ />
+
+ ) : null}
+ {typeof data?.value !== 'object' && (
+
+
+
+ setValue(e.target.value)}
+ placeholder='Value'
+ fluid
+ disabled={value === '[]' || value === '{}'}
+ />
+ (value === '[]' ? setValue('') : setValue('[]'))}
+ intent={value === '[]' ? 'info' : undefined}
+ compact
+ textAlign='center'
+ >
+ [...]
+
+ (value === '{}' ? setValue('') : setValue('{}'))}
+ intent={value === '{}' ? 'info' : undefined}
+ compact
+ textAlign='center'
+ label='{...}'
+ />
+
+ )}
+
+
+ );
+};
diff --git a/src/helpers/colors.ts b/src/helpers/colors.ts
index 8badd557..a5f81d0d 100644
--- a/src/helpers/colors.ts
+++ b/src/helpers/colors.ts
@@ -140,6 +140,10 @@ export const buildTheme = (theme: IReqoreTheme): IReqoreTheme => {
// Add the original theme main color to the theme
newTheme.originalMain = newTheme.main;
+ // Build the muted intent
+ const readableColor = getReadableColorFrom(newTheme.main, true);
+ newTheme.intents.muted = `${readableColor}30`;
+
if (!newTheme.notifications?.info) {
newTheme.notifications.info = newTheme.intents.info || DEFAULT_INTENTS.info;
}
@@ -161,7 +165,7 @@ export const buildTheme = (theme: IReqoreTheme): IReqoreTheme => {
}
if (!newTheme.notifications?.muted) {
- newTheme.notifications.muted = `${newTheme.intents.muted || DEFAULT_INTENTS.muted}30`;
+ newTheme.notifications.muted = newTheme.intents.muted || `${DEFAULT_INTENTS.muted}30`;
}
return newTheme;
diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts
index 9d4243d4..089823a8 100644
--- a/src/helpers/utils.ts
+++ b/src/helpers/utils.ts
@@ -150,3 +150,30 @@ export const stringifyAndDecycleObject = (obj: any): string => {
return value;
});
};
+
+export function parseInputValue(input) {
+ if (isBoolean(input)) {
+ return input;
+ }
+
+ // Check for explicit quotes to save as a string
+ if (input.startsWith('"') && input.endsWith('"')) {
+ return input.slice(1, -1); // Remove the quotes
+ }
+
+ // Attempt to parse as a number
+ const parsedNumber = Number(input);
+ if (!isNaN(parsedNumber)) {
+ return parsedNumber;
+ }
+
+ // Attempt to parse as a boolean
+ if (input === 'true') {
+ return true;
+ } else if (input === 'false') {
+ return false;
+ }
+
+ // Default to a string
+ return input;
+}
diff --git a/src/mock/object.json b/src/mock/object.json
index 19a8a9cc..5020b506 100644
--- a/src/mock/object.json
+++ b/src/mock/object.json
@@ -18,15 +18,7 @@
"registered": "2015-03-19T05:00:22 -01:00",
"latitude": -8.97737,
"longitude": 110.576471,
- "tags": [
- "aute",
- "qui",
- "ut",
- "mollit",
- "culpa",
- "qui",
- "irure"
- ],
+ "tags": ["aute", "qui", "ut", "mollit", "culpa", "qui", "irure"],
"friends": [
{
"id": 0,
@@ -63,15 +55,7 @@
"registered": "2014-11-25T11:58:45 -01:00",
"latitude": 32.968241,
"longitude": 21.905937,
- "tags": [
- "occaecat",
- "id",
- "sit",
- "eiusmod",
- "tempor",
- "adipisicing",
- "sint"
- ],
+ "tags": ["occaecat", "id", "sit", "eiusmod", "tempor", "adipisicing", "sint"],
"friends": [
{
"id": 0,
@@ -108,15 +92,7 @@
"registered": "2019-06-05T08:58:50 -02:00",
"latitude": -41.239498,
"longitude": 11.237437,
- "tags": [
- "ut",
- "consectetur",
- "enim",
- "deserunt",
- "anim",
- "adipisicing",
- "irure"
- ],
+ "tags": ["ut", "consectetur", "enim", "deserunt", "anim", "adipisicing", "irure"],
"friends": [
{
"id": 0,
@@ -153,15 +129,7 @@
"registered": "2020-02-24T06:26:21 -01:00",
"latitude": -79.669108,
"longitude": 19.666959,
- "tags": [
- "magna",
- "nisi",
- "eiusmod",
- "elit",
- "eu",
- "in",
- "anim"
- ],
+ "tags": ["magna", "nisi", "eiusmod", "elit", "eu", "in", "anim"],
"friends": [
{
"id": 0,
@@ -198,15 +166,7 @@
"registered": "2018-05-30T10:39:01 -02:00",
"latitude": 39.987328,
"longitude": -18.121186,
- "tags": [
- "amet",
- "reprehenderit",
- "elit",
- "nostrud",
- "sunt",
- "consequat",
- "fugiat"
- ],
+ "tags": ["amet", "reprehenderit", "elit", "nostrud", "sunt", "consequat", "fugiat"],
"friends": [
{
"id": 0,
@@ -243,15 +203,8 @@
"registered": "2015-09-29T09:39:54 -02:00",
"latitude": -18.968669,
"longitude": -33.485271,
- "tags": [
- "mollit",
- "officia",
- "ipsum",
- "veniam",
- "laborum",
- "laboris",
- "consectetur"
- ],
+ "emptyList": [],
+ "tags": ["mollit", "officia", "ipsum", "veniam", "laborum", "laboris", "consectetur"],
"friends": [
{
"id": 0,
@@ -288,15 +241,7 @@
"registered": "2014-12-29T03:11:31 -01:00",
"latitude": 55.032528,
"longitude": 178.669012,
- "tags": [
- "sit",
- "non",
- "et",
- "labore",
- "consectetur",
- "voluptate",
- "ea"
- ],
+ "tags": ["sit", "non", "et", "labore", "consectetur", "voluptate", "ea"],
"friends": [
{
"id": 0,
@@ -313,5 +258,6 @@
],
"greeting": "Hello, Levine Bray! You have 5 unread messages.",
"favoriteFruit": "strawberry"
- }
-]
\ No newline at end of file
+ },
+ {}
+]
diff --git a/src/stories/Tree/Tree.stories.tsx b/src/stories/Tree/Tree.stories.tsx
index 71e4e44b..0d7d2a44 100644
--- a/src/stories/Tree/Tree.stories.tsx
+++ b/src/stories/Tree/Tree.stories.tsx
@@ -1,5 +1,9 @@
+import { expect } from '@storybook/jest';
import { StoryObj } from '@storybook/react';
+import { fireEvent } from '@storybook/testing-library';
import { noop } from 'lodash';
+import { useState } from 'react';
+import { _testsClickButton, _testsWaitForText } from '../../../__tests__/utils';
import { IReqoreTreeProps, ReqoreTree } from '../../components/Tree';
import MockObject from '../../mock/object.json';
import { StoryMeta } from '../utils';
@@ -70,6 +74,84 @@ export const Exportable: Story = {
},
};
+export const EditableArray: Story = {
+ render: (args) => {
+ const [data, setData] = useState(args.data);
+
+ return (
+ {
+ setData(() => newData);
+ }}
+ />
+ );
+ },
+ args: {
+ showTypes: false,
+ editable: true,
+ },
+};
+
+export const EditableObject: Story = {
+ render: (args) => {
+ const [data, setData] = useState(args.data);
+
+ return (
+ {
+ setData(() => newData);
+ }}
+ />
+ );
+ },
+ args: {
+ showTypes: false,
+ editable: true,
+ data: {
+ _id: '606d4c955f96372fd1b8bcd1',
+ index: 0,
+ guid: 'd08cde5f-e18e-4d7f-80a9-6541848ab830',
+ isActive: false,
+ balance: '$3,219.32',
+ picture: 'http://placehold.it/32x32',
+ age: 25,
+ eyeColor: 'brown',
+ name: 'Zelma Short',
+ gender: 'female',
+ company: 'LINGOAGE',
+ email: 'zelmashort@lingoage.com',
+ phone: '+1 (840) 429-2274',
+ address: '757 Woodside Avenue, Winchester, Marshall Islands, 2032',
+ about:
+ 'Ipsum est ex nisi veniam proident adipisicing. Occaecat Lorem minim amet aliqua laboris excepteur sint eu mollit laborum sunt. Duis aliquip nulla cillum labore culpa ullamco labore non in nostrud. Cupidatat nisi enim ullamco quis voluptate Lorem voluptate minim dolore esse irure eiusmod aliquip amet. Dolore laboris Lorem laboris irure magna sint dolor. In irure adipisicing minim ullamco commodo ad dolore elit occaecat dolor. Cillum commodo est commodo dolor enim velit.\r\n',
+ registered: '2015-03-19T05:00:22 -01:00',
+ latitude: -8.97737,
+ longitude: 110.576471,
+ tags: ['aute', 'qui', 'ut', 'mollit', 'culpa', 'qui', 'irure'],
+ friends: [
+ {
+ id: 0,
+ name: 'Angel Gallagher',
+ },
+ {
+ id: 1,
+ name: 'Rose Farmer',
+ },
+ {
+ id: 2,
+ name: 'Jaclyn Keith',
+ },
+ ],
+ greeting: 'Hello, Zelma Short! You have 5 unread messages.',
+ favoriteFruit: 'banana',
+ },
+ },
+};
+
export const Object: Story = {
args: {
exportable: true,
@@ -122,3 +204,81 @@ export const WithDefaultZoom: Story = {
defaultZoom: 2,
},
};
+
+export const NewArrayItemCanBeAdded: Story = {
+ ...EditableArray,
+ play: async () => {
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(8);
+ await _testsClickButton({ selector: '.reqore-tree-add' });
+ await _testsWaitForText('Adding new item');
+ await expect(document.querySelector('.reqore-tree-save')).toBeDisabled();
+ await fireEvent.change(document.querySelector('.reqore-textarea'), {
+ target: { value: 'New item' },
+ });
+ await expect(document.querySelector('.reqore-tree-save')).toBeEnabled();
+ await _testsClickButton({ selector: '.reqore-tree-save' });
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(9);
+ await _testsWaitForText('"New item"');
+ },
+};
+
+export const NewArrayItemCanBeEdited: Story = {
+ ...NewArrayItemCanBeAdded,
+ play: async (args) => {
+ await NewArrayItemCanBeAdded.play(args);
+ await _testsClickButton({ selector: '.reqore-tree-edit' });
+ await fireEvent.change(document.querySelectorAll('.reqore-textarea')[0], {
+ target: { value: 'New item edited' },
+ });
+ await expect(document.querySelector('.reqore-tree-save')).toBeEnabled();
+ await _testsClickButton({ selector: '.reqore-tree-save' });
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(9);
+ await _testsWaitForText('"New item edited"');
+ },
+};
+
+export const NewObjectItemCanBeAdded: Story = {
+ ...EditableObject,
+ play: async () => {
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(22);
+ await _testsClickButton({ selector: '.reqore-tree-add' });
+ await _testsWaitForText('Adding new item');
+ await expect(document.querySelector('.reqore-tree-save')).toBeDisabled();
+ await fireEvent.change(document.querySelector('.reqore-input'), {
+ target: { value: 'New item' },
+ });
+ await fireEvent.change(document.querySelectorAll('.reqore-textarea')[0], {
+ target: { value: 'New item value' },
+ });
+ await expect(document.querySelector('.reqore-tree-save')).toBeEnabled();
+ await _testsClickButton({ selector: '.reqore-tree-save' });
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(23);
+ await _testsWaitForText('New item:');
+ await _testsWaitForText('"New item value"');
+ },
+};
+
+export const ObjectItemCanBeEdited: Story = {
+ ...EditableObject,
+ play: async () => {
+ await _testsClickButton({ selector: '.reqore-tree-edit', nth: 19 });
+ await fireEvent.change(document.querySelectorAll('.reqore-input')[0], {
+ target: { value: 'updated item key' },
+ });
+ await expect(document.querySelector('.reqore-tree-save')).toBeEnabled();
+ await _testsClickButton({ selector: '.reqore-tree-save' });
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(22);
+ await _testsWaitForText('updated item key');
+ await _testsClickButton({ label: 'updated item key' });
+ await _testsClickButton({ label: '1' });
+ },
+};
+
+export const ItemsCanBeDeleted: Story = {
+ ...EditableObject,
+ play: async () => {
+ await _testsClickButton({ selector: '.reqore-tree-delete', nth: 4 });
+ await _testsClickButton({ selector: '.reqore-tree-delete', nth: 18 });
+ await expect(document.querySelectorAll('.reqore-tree-item').length).toBe(20);
+ },
+};