@arquimedes.co/eureka-forms 3.0.49-test → 3.0.51-test
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App/App.stories.d.ts +30 -0
- package/dist/App/App.stories.js +298 -0
- package/dist/App/App.stories.test.d.ts +1 -0
- package/dist/App/App.stories.test.js +56 -0
- package/dist/FormSteps/DatePickerRangeStep/MaterialDatePickerRangeStep/MaterialDatePickerRangeStep.js +1 -1
- package/dist/FormSteps/FileUploadStep/MaterialFileUploadStep/FileComponent/FileComponent.js +1 -1
- package/dist/FormSteps/FileUploadStep/MaterialFileUploadStep/FileComponent/FileComponent.module.css +1 -1
- package/dist/FormSteps/FileUploadStep/MaterialFileUploadStep/MaterialFileUploadStep.js +48 -17
- package/dist/FormSteps/FileUploadStep/MaterialFileUploadStep/MaterialFileUploadStep.module.css +16 -0
- package/dist/FormSteps/TitleStep/MaterialTitleStep/MaterialTitleStep.js +3 -1
- package/dist/Shared/ErkAutocompleteOption.d.ts +12 -0
- package/dist/Shared/ErkAutocompleteOption.js +37 -0
- package/dist/Shared/ErkDatePicker/ErkDatePicker.js +8 -3
- package/dist/Shared/ErkDateRangePicker/ErkDateRangePicker.js +8 -3
- package/dist/Shared/ErkSmartSelect/ErkSmartSelect.js +2 -1
- package/dist/Shared/ErkSmartSelect/ErkSmartSelect.test.d.ts +1 -0
- package/dist/Shared/ErkSmartSelect/ErkSmartSelect.test.js +33 -0
- package/dist/Shared/SmartDraftRenderer/SmartDraftRenderer.d.ts +7 -1
- package/dist/Shared/SmartDraftRenderer/SmartDraftRenderer.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import App from './App';
|
|
4
|
+
declare function FullHeight(Story: React.ComponentType): JSX.Element;
|
|
5
|
+
declare const meta: {
|
|
6
|
+
title: string;
|
|
7
|
+
component: typeof App;
|
|
8
|
+
decorators: (typeof FullHeight)[];
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: string;
|
|
11
|
+
};
|
|
12
|
+
args: {
|
|
13
|
+
isWidget: false;
|
|
14
|
+
idOrganization: string;
|
|
15
|
+
valuesData: {};
|
|
16
|
+
customSubmit: (values: Record<string, unknown>) => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof meta>;
|
|
21
|
+
/** Parsed form, as the widget receives it from the API. */
|
|
22
|
+
export declare const Parseado: Story;
|
|
23
|
+
/** TITLE with an INTEGRATION entity depending on a TEXTINPUT step. */
|
|
24
|
+
export declare const IntegracionConStepDependencia: Story;
|
|
25
|
+
/** Parsed form pre-filled with values. */
|
|
26
|
+
export declare const ParseadoConValores: Story;
|
|
27
|
+
/** Raw builder form: EUREKA entities resolved locally via mapDraftEntities. */
|
|
28
|
+
export declare const PreParseado: Story;
|
|
29
|
+
/** Final view (postview): what the user submitted, rendered read-only. */
|
|
30
|
+
export declare const Postview: Story;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import App from './App';
|
|
3
|
+
/**
|
|
4
|
+
* Stories to test full forms from a `{ form, values }` payload:
|
|
5
|
+
*
|
|
6
|
+
* - `Parseado`: a parsed form (what the widget receives, drafts are plain text).
|
|
7
|
+
* - `PreParseado`: a raw builder form (EUREKA/ENTITYVALUES entities still in the
|
|
8
|
+
* drafts, `rootSteps` instead of section steps). Entities are resolved locally
|
|
9
|
+
* with a stubbed `mapDraftEntities`, so no backend is needed.
|
|
10
|
+
*
|
|
11
|
+
* Paste any form JSON into `formData`/`valuesData` from the Controls panel.
|
|
12
|
+
*/
|
|
13
|
+
/** Raw builder form (pre-parsed): title draft still has EUREKA entities. */
|
|
14
|
+
const preParsedForm = {
|
|
15
|
+
steps: {
|
|
16
|
+
'TITLE-NkSFXOcH-z': {
|
|
17
|
+
id: 'TITLE-NkSFXOcH-z',
|
|
18
|
+
type: 'TITLE',
|
|
19
|
+
title: {
|
|
20
|
+
blocks: [
|
|
21
|
+
{
|
|
22
|
+
key: '2ri0w',
|
|
23
|
+
type: 'unstyled',
|
|
24
|
+
text: 'Razón social (Proveedor) ',
|
|
25
|
+
depth: 0,
|
|
26
|
+
inlineStyleRanges: [{ offset: 0, length: 24, style: 'BOLD' }],
|
|
27
|
+
entityRanges: [{ offset: 0, length: 24, key: 0 }],
|
|
28
|
+
data: {},
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
entityMap: {
|
|
32
|
+
'0': {
|
|
33
|
+
type: 'EUREKA',
|
|
34
|
+
mutability: 'IMMUTABLE',
|
|
35
|
+
data: {
|
|
36
|
+
type: 'ENTITYVALUES',
|
|
37
|
+
idEntity: '6400adf810f16b8b68c24e72',
|
|
38
|
+
renderType: 'INLINE',
|
|
39
|
+
block: {
|
|
40
|
+
key: '2522g',
|
|
41
|
+
type: 'unstyled',
|
|
42
|
+
text: 'Razón social (Proveedor)',
|
|
43
|
+
depth: 0,
|
|
44
|
+
inlineStyleRanges: [{ offset: 0, length: 24, style: 'BOLD' }],
|
|
45
|
+
entityRanges: [{ offset: 0, length: 24, key: 0 }],
|
|
46
|
+
data: {},
|
|
47
|
+
},
|
|
48
|
+
entityMap: {
|
|
49
|
+
'0': {
|
|
50
|
+
type: 'EUREKA',
|
|
51
|
+
mutability: 'IMMUTABLE',
|
|
52
|
+
data: {
|
|
53
|
+
type: 'ENTITYVALUE_MAPPING',
|
|
54
|
+
idEntity: '6400adf810f16b8b68c24e72',
|
|
55
|
+
idProperty: 'NombreCompleto',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
'TEXTINPUT-lCmhOv5hYA': {
|
|
65
|
+
id: 'TEXTINPUT-lCmhOv5hYA',
|
|
66
|
+
type: 'TEXTINPUT',
|
|
67
|
+
label: 'name',
|
|
68
|
+
description: '',
|
|
69
|
+
required: false,
|
|
70
|
+
size: 2,
|
|
71
|
+
isSubject: false,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
size: {
|
|
75
|
+
blockNum: 4,
|
|
76
|
+
blockSize: 210,
|
|
77
|
+
spacingSize: 20,
|
|
78
|
+
},
|
|
79
|
+
rootSteps: ['TITLE-NkSFXOcH-z', 'TEXTINPUT-lCmhOv5hYA'],
|
|
80
|
+
label: 'Enviar',
|
|
81
|
+
firstSection: 'FIRST',
|
|
82
|
+
sections: {
|
|
83
|
+
FIRST: {
|
|
84
|
+
id: 'FIRST',
|
|
85
|
+
steps: ['TITLE-NkSFXOcH-z', 'TEXTINPUT-lCmhOv5hYA'],
|
|
86
|
+
name: '',
|
|
87
|
+
nextSection: null,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
/** The same form already parsed: drafts are plain text, no pending entities. */
|
|
92
|
+
const parsedForm = {
|
|
93
|
+
...preParsedForm,
|
|
94
|
+
steps: {
|
|
95
|
+
...preParsedForm.steps,
|
|
96
|
+
'TITLE-NkSFXOcH-z': {
|
|
97
|
+
id: 'TITLE-NkSFXOcH-z',
|
|
98
|
+
type: 'TITLE',
|
|
99
|
+
title: {
|
|
100
|
+
blocks: [
|
|
101
|
+
{
|
|
102
|
+
key: '2ri0w',
|
|
103
|
+
type: 'unstyled',
|
|
104
|
+
text: 'Razón social (Proveedor)',
|
|
105
|
+
depth: 0,
|
|
106
|
+
inlineStyleRanges: [{ offset: 0, length: 24, style: 'BOLD' }],
|
|
107
|
+
entityRanges: [],
|
|
108
|
+
data: {},
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
entityMap: {},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const exampleValues = {
|
|
117
|
+
'TEXTINPUT-lCmhOv5hYA': 'Capta S.A.S.',
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Local replacement for the backend `/draft/mapFormEntities` call: resolves a
|
|
121
|
+
* draft by dropping its EUREKA entities and keeping the literal text, so
|
|
122
|
+
* pre-parsed forms render in Storybook without a server.
|
|
123
|
+
*/
|
|
124
|
+
function makeStoryMapDraftEntities(form) {
|
|
125
|
+
return (property) => {
|
|
126
|
+
const path = property.split('.');
|
|
127
|
+
const stepProperty = path.pop();
|
|
128
|
+
const idStep = path[0];
|
|
129
|
+
const step = form.steps[idStep];
|
|
130
|
+
const draft = stepProperty ? step?.[stepProperty] : undefined;
|
|
131
|
+
if (!draft)
|
|
132
|
+
return Promise.reject(new Error(`Storybook mapDraftEntities: draft not found for "${property}"`));
|
|
133
|
+
return Promise.resolve({
|
|
134
|
+
blocks: draft.blocks.map((block) => ({ ...block, entityRanges: [] })),
|
|
135
|
+
entityMap: {},
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function FullHeight(Story) {
|
|
140
|
+
return (_jsx("div", { style: { height: '100vh', display: 'flex', flexDirection: 'column' }, children: _jsx(Story, {}) }));
|
|
141
|
+
}
|
|
142
|
+
const meta = {
|
|
143
|
+
title: 'Form/FormRenderer',
|
|
144
|
+
component: App,
|
|
145
|
+
decorators: [FullHeight],
|
|
146
|
+
parameters: { layout: 'fullscreen' },
|
|
147
|
+
args: {
|
|
148
|
+
isWidget: false,
|
|
149
|
+
idOrganization: 'storybook',
|
|
150
|
+
valuesData: {},
|
|
151
|
+
customSubmit: (values) => {
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.log('Storybook submit:', values);
|
|
154
|
+
return Promise.resolve();
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
export default meta;
|
|
159
|
+
/** Parsed form, as the widget receives it from the API. */
|
|
160
|
+
export const Parseado = {
|
|
161
|
+
args: {
|
|
162
|
+
formData: parsedForm,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Regression: TITLE whose title draft has an INTEGRATION entity parameterized by a
|
|
167
|
+
* TEXTINPUT step (`dependencies: [idStep]`). Mimics the backend contract of
|
|
168
|
+
* `/draft/mapFormEntities` (`keepNotMapped`): with the dependency unset the entity
|
|
169
|
+
* can't resolve and comes back untouched; with a value the text is resolved and the
|
|
170
|
+
* entity dropped. Production crashed with React #185 (max update depth) on render.
|
|
171
|
+
*/
|
|
172
|
+
const integrationTitleForm = {
|
|
173
|
+
steps: {
|
|
174
|
+
'TITLE-w4yCyiB1LM': {
|
|
175
|
+
id: 'TITLE-w4yCyiB1LM',
|
|
176
|
+
type: 'TITLE',
|
|
177
|
+
dependencies: ['TEXTINPUT-bwKwg_jKzD'],
|
|
178
|
+
description: {
|
|
179
|
+
blocks: [
|
|
180
|
+
{
|
|
181
|
+
depth: 0,
|
|
182
|
+
data: {},
|
|
183
|
+
inlineStyleRanges: [],
|
|
184
|
+
text: '',
|
|
185
|
+
type: 'unstyled',
|
|
186
|
+
key: '3hfq7',
|
|
187
|
+
entityRanges: [],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
entityMap: {},
|
|
191
|
+
},
|
|
192
|
+
title: {
|
|
193
|
+
blocks: [
|
|
194
|
+
{
|
|
195
|
+
depth: 0,
|
|
196
|
+
data: {},
|
|
197
|
+
inlineStyleRanges: [{ offset: 7, length: 31, style: 'BOLD' }],
|
|
198
|
+
text: 'Título Test Draft Integration Con Step ',
|
|
199
|
+
type: 'unstyled',
|
|
200
|
+
key: '0',
|
|
201
|
+
entityRanges: [{ offset: 7, length: 31, key: 0 }],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
entityMap: {
|
|
205
|
+
'0': {
|
|
206
|
+
type: 'EUREKA',
|
|
207
|
+
mutability: 'IMMUTABLE',
|
|
208
|
+
data: {
|
|
209
|
+
type: 'INTEGRATION',
|
|
210
|
+
idIntegration: '6960368e749b73499854bcac',
|
|
211
|
+
values: { param: { type: 'STEP', idStep: 'TEXTINPUT-bwKwg_jKzD' } },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
'TEXTINPUT-bwKwg_jKzD': {
|
|
218
|
+
id: 'TEXTINPUT-bwKwg_jKzD',
|
|
219
|
+
type: 'TEXTINPUT',
|
|
220
|
+
label: 'Campo',
|
|
221
|
+
description: '',
|
|
222
|
+
required: false,
|
|
223
|
+
size: 2,
|
|
224
|
+
isSubject: false,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
size: { blockNum: 4, blockSize: 210, spacingSize: 20 },
|
|
228
|
+
rootSteps: ['TITLE-w4yCyiB1LM', 'TEXTINPUT-bwKwg_jKzD'],
|
|
229
|
+
label: 'Enviar',
|
|
230
|
+
firstSection: 'FIRST',
|
|
231
|
+
sections: {
|
|
232
|
+
FIRST: {
|
|
233
|
+
id: 'FIRST',
|
|
234
|
+
steps: ['TITLE-w4yCyiB1LM', 'TEXTINPUT-bwKwg_jKzD'],
|
|
235
|
+
name: '',
|
|
236
|
+
nextSection: null,
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
function makeIntegrationMapDraftEntities(form) {
|
|
241
|
+
return (property, dependencies) => {
|
|
242
|
+
const path = property.split('.');
|
|
243
|
+
const stepProperty = path.pop();
|
|
244
|
+
const idStep = path[0];
|
|
245
|
+
const step = form.steps[idStep];
|
|
246
|
+
const draft = stepProperty ? step?.[stepProperty] : undefined;
|
|
247
|
+
if (!draft)
|
|
248
|
+
return Promise.reject(new Error(`Storybook mapDraftEntities: draft not found for "${property}"`));
|
|
249
|
+
const depValue = dependencies['TEXTINPUT-bwKwg_jKzD'];
|
|
250
|
+
// Unresolvable integration (dependency null): keepNotMapped — return the draft as-is.
|
|
251
|
+
if (depValue == null || depValue === '')
|
|
252
|
+
return Promise.resolve(JSON.parse(JSON.stringify(draft)));
|
|
253
|
+
// Resolved: replace each entity range with the integration result, drop the entities.
|
|
254
|
+
return Promise.resolve({
|
|
255
|
+
blocks: draft.blocks.map((block) => {
|
|
256
|
+
let text = block.text;
|
|
257
|
+
for (const range of [...block.entityRanges].sort((a, b) => b.offset - a.offset)) {
|
|
258
|
+
const chars = [...text];
|
|
259
|
+
chars.splice(range.offset, range.length, ...`[${depValue}]`);
|
|
260
|
+
text = chars.join('');
|
|
261
|
+
}
|
|
262
|
+
return { ...block, text, entityRanges: [], inlineStyleRanges: [] };
|
|
263
|
+
}),
|
|
264
|
+
entityMap: {},
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** TITLE with an INTEGRATION entity depending on a TEXTINPUT step. */
|
|
269
|
+
export const IntegracionConStepDependencia = {
|
|
270
|
+
args: {
|
|
271
|
+
formData: integrationTitleForm,
|
|
272
|
+
mapDraftEntities: makeIntegrationMapDraftEntities(integrationTitleForm),
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
/** Parsed form pre-filled with values. */
|
|
276
|
+
export const ParseadoConValores = {
|
|
277
|
+
args: {
|
|
278
|
+
formData: parsedForm,
|
|
279
|
+
valuesData: exampleValues,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
/** Raw builder form: EUREKA entities resolved locally via mapDraftEntities. */
|
|
283
|
+
export const PreParseado = {
|
|
284
|
+
args: {
|
|
285
|
+
formData: preParsedForm,
|
|
286
|
+
mapDraftEntities: makeStoryMapDraftEntities(preParsedForm),
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
/** Final view (postview): what the user submitted, rendered read-only. */
|
|
290
|
+
export const Postview = {
|
|
291
|
+
args: {
|
|
292
|
+
formData: parsedForm,
|
|
293
|
+
valuesData: exampleValues,
|
|
294
|
+
postview: true,
|
|
295
|
+
internal: true,
|
|
296
|
+
editable: false,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { composeStories } from '@storybook/react-vite';
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import * as stories from './App.stories';
|
|
8
|
+
const { Parseado, ParseadoConValores, PreParseado, Postview, IntegracionConStepDependencia } = composeStories(stories);
|
|
9
|
+
describe('FormRenderer stories', () => {
|
|
10
|
+
it('renders the parsed form with its title and input', async () => {
|
|
11
|
+
const { container } = render(_jsx(Parseado, {}));
|
|
12
|
+
await waitFor(() => {
|
|
13
|
+
expect(container.textContent).toContain('Razón social');
|
|
14
|
+
});
|
|
15
|
+
expect(screen.getAllByText('name').length).toBeGreaterThan(0);
|
|
16
|
+
});
|
|
17
|
+
it('renders the parsed form pre-filled with values', async () => {
|
|
18
|
+
render(_jsx(ParseadoConValores, {}));
|
|
19
|
+
await waitFor(() => {
|
|
20
|
+
expect(screen.getByDisplayValue('Capta S.A.S.')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
it('renders the pre-parsed form resolving EUREKA entities locally', async () => {
|
|
24
|
+
const { container } = render(_jsx(PreParseado, {}));
|
|
25
|
+
await waitFor(() => {
|
|
26
|
+
expect(container.textContent).toContain('Razón social');
|
|
27
|
+
});
|
|
28
|
+
expect(screen.getAllByText('name').length).toBeGreaterThan(0);
|
|
29
|
+
});
|
|
30
|
+
it('renders the postview with the submitted value', async () => {
|
|
31
|
+
render(_jsx(Postview, {}));
|
|
32
|
+
await waitFor(() => {
|
|
33
|
+
expect(screen.getByDisplayValue('Capta S.A.S.')).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('renders a title with an INTEGRATION entity depending on a step and reacts to typing', async () => {
|
|
37
|
+
const { container } = render(_jsx(IntegracionConStepDependencia, {}));
|
|
38
|
+
await waitFor(() => {
|
|
39
|
+
expect(container.textContent).toContain('Título');
|
|
40
|
+
});
|
|
41
|
+
const input = await screen.findByRole('textbox');
|
|
42
|
+
await userEvent.type(input, 'hola');
|
|
43
|
+
// The dependency dispatch is debounced (1s), so give the refetch room to land.
|
|
44
|
+
await waitFor(() => {
|
|
45
|
+
expect(container.textContent).toContain('[hola]');
|
|
46
|
+
}, { timeout: 5000 });
|
|
47
|
+
// Changing the dependency AFTER the first resolution must re-map the title:
|
|
48
|
+
// the stored value is a mapped copy with its entities stripped, so mapping
|
|
49
|
+
// has to keep keying off the original step draft.
|
|
50
|
+
await userEvent.clear(input);
|
|
51
|
+
await userEvent.type(input, 'chao');
|
|
52
|
+
await waitFor(() => {
|
|
53
|
+
expect(container.textContent).toContain('[chao]');
|
|
54
|
+
}, { timeout: 5000 });
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -29,6 +29,6 @@ function MaterialDatePickerRangeStep({ step, editable, defaultValue }) {
|
|
|
29
29
|
}, [onChange]);
|
|
30
30
|
const [minDate, maxDate] = [safeDate(step.minDate), safeDate(step.maxDate)];
|
|
31
31
|
const dateRangeValue = value?.length === 2 ? [safeDate(value[0]) ?? null, safeDate(value[1]) ?? null] : [null, null];
|
|
32
|
-
return (_jsx(MaterialInputContainer, { step: step, editable: editable, children: _jsx(ErkDateRangePicker, { ...field, value: dateRangeValue, inputRef: ref, error: !!error, minDate: minDate, maxDate: maxDate, labelMargin: 0, onChange: handleChange, required: step.required, readOnly: !editable || postview, helperText: error?.message ?? step.description }) }));
|
|
32
|
+
return (_jsx(MaterialInputContainer, { step: step, editable: editable, children: _jsx(ErkDateRangePicker, { ...field, value: dateRangeValue, inputRef: ref, error: !!error, minDate: minDate, maxDate: maxDate, labelMargin: 0, label: step.label, onChange: handleChange, required: step.required, readOnly: !editable || postview, helperText: error?.message ?? step.description }) }));
|
|
33
33
|
}
|
|
34
34
|
export default MaterialDatePickerRangeStep;
|
|
@@ -64,6 +64,6 @@ function FileUploadComponent({ file, formStyle, error, editable, handleRemove, f
|
|
|
64
64
|
}
|
|
65
65
|
}, children: file.file?.name ?? file.fileName }), editable && (_jsx("div", { className: styles.deletBtn, onClick: () => {
|
|
66
66
|
handleRemove();
|
|
67
|
-
}, children: _jsx(CloseIcon, {
|
|
67
|
+
}, children: _jsx(CloseIcon, { size: 24 }) }))] }));
|
|
68
68
|
}
|
|
69
69
|
export default FileUploadComponent;
|
|
@@ -23,7 +23,23 @@ function FileUploadStep({ step, editable }) {
|
|
|
23
23
|
const { clearErrors } = useFormContext();
|
|
24
24
|
const [error, setError] = useState(undefined);
|
|
25
25
|
const [fileChange, setFileChange] = useState([]);
|
|
26
|
+
const [dragging, setDragging] = useState(false);
|
|
26
27
|
const inputRef = useRef(null);
|
|
28
|
+
const handleFiles = useCallback((files) => {
|
|
29
|
+
const maxFiles = files.filter((file) => file.size > maxSize);
|
|
30
|
+
if (maxFiles.length > 0) {
|
|
31
|
+
setError('El tamaño máximo de carga es de 30Mb.');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
setFileChange(files
|
|
35
|
+
.filter((file) => value.find((val) => val.file === file) === undefined)
|
|
36
|
+
.map((file) => ({
|
|
37
|
+
state: 'STARTING',
|
|
38
|
+
file,
|
|
39
|
+
postInfo: null,
|
|
40
|
+
})));
|
|
41
|
+
}
|
|
42
|
+
}, [value]);
|
|
27
43
|
const getLinks = useCallback(async (pFiles) => {
|
|
28
44
|
try {
|
|
29
45
|
const fileLinks = await getUploadUrls(pFiles.map((file) => file.file), idOrganization);
|
|
@@ -122,25 +138,40 @@ function FileUploadStep({ step, editable }) {
|
|
|
122
138
|
}
|
|
123
139
|
return '';
|
|
124
140
|
};
|
|
125
|
-
return (_jsxs("div", { className: styles.container +
|
|
141
|
+
return (_jsxs("div", { className: styles.container +
|
|
142
|
+
(error || !!fieldError ? ' Erk-error' : '') +
|
|
143
|
+
(dragging ? ' ' + styles.dragging : ''), style: {
|
|
126
144
|
minHeight: editable === false || postview ? undefined : '100px',
|
|
127
|
-
}, "data-testid": step.id,
|
|
145
|
+
}, "data-testid": step.id, onDragOver: (e) => {
|
|
146
|
+
if (!editable || postview)
|
|
147
|
+
return;
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
}, onDragEnter: (e) => {
|
|
150
|
+
if (!editable || postview)
|
|
151
|
+
return;
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
setDragging(true);
|
|
154
|
+
}, onDragLeave: (e) => {
|
|
155
|
+
if (!editable || postview)
|
|
156
|
+
return;
|
|
157
|
+
if (e.currentTarget.contains(e.relatedTarget))
|
|
158
|
+
return;
|
|
159
|
+
setDragging(false);
|
|
160
|
+
}, onDrop: (e) => {
|
|
161
|
+
if (!editable || postview)
|
|
162
|
+
return;
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
setDragging(false);
|
|
165
|
+
clearErrors(step.id);
|
|
166
|
+
setError(undefined);
|
|
167
|
+
const files = e.dataTransfer.files;
|
|
168
|
+
if (files && files.length > 0) {
|
|
169
|
+
handleFiles(Array.from(files));
|
|
170
|
+
}
|
|
171
|
+
}, children: [_jsx("div", { className: styles.labelLabel, children: step.label }), step.description && (_jsx("div", { className: styles.stepDescriptionLabel, style: { color: formStyle.descriptionTextColor }, children: step.description })), _jsx("input", { type: "file", ref: inputRef, className: styles.filesInput, onChange: (e) => {
|
|
128
172
|
const files = e.target.files;
|
|
129
173
|
if (files) {
|
|
130
|
-
|
|
131
|
-
const maxFiles = filesArray.filter((file) => file.size > maxSize);
|
|
132
|
-
if (maxFiles.length > 0) {
|
|
133
|
-
setError('El tamaño máximo de carga es de 30Mb.');
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
setFileChange(filesArray
|
|
137
|
-
.filter((file) => value.find((val) => val.file === file) === undefined)
|
|
138
|
-
.map((file) => ({
|
|
139
|
-
state: 'STARTING',
|
|
140
|
-
file,
|
|
141
|
-
postInfo: null,
|
|
142
|
-
})));
|
|
143
|
-
}
|
|
174
|
+
handleFiles(Array.from(files));
|
|
144
175
|
inputRef.current.value = '';
|
|
145
176
|
}
|
|
146
177
|
}, multiple: true, accept: getAcceptedExtensions() }), _jsxs("div", { className: styles.btnContainer, children: [_jsx("input", { ref: ref, className: 'hidden-input' }), _jsx(ErkButton, { disabled: !editable || postview, text: 'Examinar' + (step.required ? ' *' : ''), onClick: () => {
|
|
@@ -152,7 +183,7 @@ function FileUploadStep({ step, editable }) {
|
|
|
152
183
|
input.click();
|
|
153
184
|
}
|
|
154
185
|
}
|
|
155
|
-
} })] }), _jsx("div", { className: styles.filesContainer, children: value.map((file, index) => (_jsx(FileComponent, { formStyle: formStyle, file: file, error: (!!fieldError && file.state !== 'DONE') ||
|
|
186
|
+
} })] }), editable && !postview && (_jsx("div", { className: styles.dragHint, style: { color: formStyle.descriptionTextColor }, children: "o arrastra y suelta los archivos aqu\u00ED" })), _jsx("div", { className: styles.filesContainer, children: value.map((file, index) => (_jsx(FileComponent, { formStyle: formStyle, file: file, error: (!!fieldError && file.state !== 'DONE') ||
|
|
156
187
|
file.state === 'ERROR', editable: editable && !postview, handleRemove: () => {
|
|
157
188
|
if (value.filter((val) => val.state === 'ERROR').length === 1) {
|
|
158
189
|
clearErrors(step.id);
|
package/dist/FormSteps/FileUploadStep/MaterialFileUploadStep/MaterialFileUploadStep.module.css
CHANGED
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
text-overflow: ellipsis;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
.dragging {
|
|
36
|
+
border: 2px dashed currentColor;
|
|
37
|
+
border-radius: 10px;
|
|
38
|
+
opacity: 0.85;
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
.filesInput {
|
|
36
42
|
display: none;
|
|
37
43
|
}
|
|
@@ -41,6 +47,16 @@
|
|
|
41
47
|
margin-bottom: 3px;
|
|
42
48
|
width: fit-content;
|
|
43
49
|
}
|
|
50
|
+
.dragHint {
|
|
51
|
+
font-size: 0.75rem;
|
|
52
|
+
margin-left: 14px;
|
|
53
|
+
margin-top: 4px;
|
|
54
|
+
opacity: 0.8;
|
|
55
|
+
-moz-user-select: none;
|
|
56
|
+
-webkit-user-select: none;
|
|
57
|
+
-ms-user-select: none;
|
|
58
|
+
user-select: none;
|
|
59
|
+
}
|
|
44
60
|
.errorMsg {
|
|
45
61
|
font-size: 0.75rem;
|
|
46
62
|
margin-top: 5px;
|
|
@@ -19,8 +19,10 @@ function TitleStep({ step }) {
|
|
|
19
19
|
const size = step.size ?? form.size.blockNum;
|
|
20
20
|
const title = useMemo(() => calcBaseDraft(value?.title ?? step.title), //Usa por defecto lo que venga en el value, pero no escucha cambios
|
|
21
21
|
[step.title, value?.title]);
|
|
22
|
+
const sourceTitle = useMemo(() => calcBaseDraft(step.title), [step.title]);
|
|
22
23
|
const description = useMemo(() => calcBaseDraft(value?.description ?? step.description), //Usa por defecto lo que venga en el value, pero no escucha cambios
|
|
23
24
|
[step.description, value?.description]);
|
|
25
|
+
const sourceDescription = useMemo(() => calcBaseDraft(step.description), [step.description]);
|
|
24
26
|
const dependencies = useAppSelector((state) => selectDependencies(state, step.dependencies));
|
|
25
27
|
const stableDepsValues = useAppSelector((state) => (step.dependencies ?? []).map((id) => state.site.dependencies[id]?.value), shallowEqual);
|
|
26
28
|
const values = useMemo(() => {
|
|
@@ -37,7 +39,7 @@ function TitleStep({ step }) {
|
|
|
37
39
|
width: widthStats.currentBreakPoint <= size ? '100%' : calcStepWidth(size, form.size),
|
|
38
40
|
}, children: [_jsx("div", { className: styles.titleLbl, style: {
|
|
39
41
|
textAlign: widthStats.isMobile && widthStats.currentBreakPoint <= size ? 'center' : 'start',
|
|
40
|
-
}, children: _jsx(SmartDraftRenderer, { draft: title, dependencies: values, property: `${path}.title`, onChange: (title) => onChange({ ...value, title }) }) }), _jsx("div", { className: styles.descriptionPar, children: _jsx(SmartDraftRenderer, { draft: description, dependencies: values, margin: title ? '10px 0px' : '0px 0px 5px 0px', property: `${path}.description`, onChange: (description) => onChange({ ...value, description }) }) })] }));
|
|
42
|
+
}, children: _jsx(SmartDraftRenderer, { draft: title, sourceDraft: sourceTitle, dependencies: values, property: `${path}.title`, onChange: (title) => onChange({ ...value, title }) }) }), _jsx("div", { className: styles.descriptionPar, children: _jsx(SmartDraftRenderer, { draft: description, sourceDraft: sourceDescription, dependencies: values, margin: title ? '10px 0px' : '0px 0px 5px 0px', property: `${path}.description`, onChange: (description) => onChange({ ...value, description }) }) })] }));
|
|
41
43
|
}
|
|
42
44
|
export default TitleStep;
|
|
43
45
|
function calcBaseDraft(value) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Option element for the searchable `ErkSmartSelect` (MUI `Autocomplete`). Replaces MUI `MenuItem`
|
|
3
|
+
* in `renderOption`: v9's `MenuItem` requires a surrounding `MenuList` context (it throws without
|
|
4
|
+
* one), but an Autocomplete's listbox is a plain `<ul>`, so a styled `<li>` is used instead.
|
|
5
|
+
* Hover/focus styling comes from MUI's own `.Erk-MuiAutocomplete-option` rules (the option props
|
|
6
|
+
* carry that class); this just ports the `ErkMenuItem` look (padding, wrapping, leading-icon layout)
|
|
7
|
+
* and the selected background.
|
|
8
|
+
*/
|
|
9
|
+
declare const ErkAutocompleteOption: import("@emotion/styled").StyledComponent<import("@mui/system").MUIStyledCommonProps<import("@mui/material").Theme> & {
|
|
10
|
+
selected?: boolean;
|
|
11
|
+
}, import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>, {}>;
|
|
12
|
+
export default ErkAutocompleteOption;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { styled } from '@mui/material';
|
|
2
|
+
/**
|
|
3
|
+
* Option element for the searchable `ErkSmartSelect` (MUI `Autocomplete`). Replaces MUI `MenuItem`
|
|
4
|
+
* in `renderOption`: v9's `MenuItem` requires a surrounding `MenuList` context (it throws without
|
|
5
|
+
* one), but an Autocomplete's listbox is a plain `<ul>`, so a styled `<li>` is used instead.
|
|
6
|
+
* Hover/focus styling comes from MUI's own `.Erk-MuiAutocomplete-option` rules (the option props
|
|
7
|
+
* carry that class); this just ports the `ErkMenuItem` look (padding, wrapping, leading-icon layout)
|
|
8
|
+
* and the selected background.
|
|
9
|
+
*/
|
|
10
|
+
const ErkAutocompleteOption = styled('li', {
|
|
11
|
+
// `ownerState` is injected by MUI v9 into the option props (spread here via Autocomplete's
|
|
12
|
+
// renderOption); a custom shouldForwardProp drops MUI's default exclusion, so filter it too or it
|
|
13
|
+
// leaks onto the <li> (React "unrecognized prop" warning).
|
|
14
|
+
shouldForwardProp: (prop) => prop !== 'selected' && prop !== 'ownerState',
|
|
15
|
+
})(({ theme, selected }) => ({
|
|
16
|
+
display: 'flex',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
position: 'relative',
|
|
19
|
+
listStyle: 'none',
|
|
20
|
+
padding: '6px 12px',
|
|
21
|
+
whiteSpace: 'normal',
|
|
22
|
+
cursor: 'pointer',
|
|
23
|
+
backgroundColor: selected ? theme.palette.action.selected : undefined,
|
|
24
|
+
'&:has(.ErkIcon)': {
|
|
25
|
+
paddingLeft: '40px',
|
|
26
|
+
'& .ErkIcon': {
|
|
27
|
+
left: 12,
|
|
28
|
+
top: '50%',
|
|
29
|
+
position: 'absolute',
|
|
30
|
+
transform: 'translateY(-50%)',
|
|
31
|
+
fill: theme.palette.primary.main,
|
|
32
|
+
width: '20px',
|
|
33
|
+
height: '20px',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
export default ErkAutocompleteOption;
|
|
@@ -167,11 +167,16 @@ function CustomPickerField({ error, required, helperText, size = 'small', labelM
|
|
|
167
167
|
},
|
|
168
168
|
} }));
|
|
169
169
|
}
|
|
170
|
-
function CustomDatePicker({ error, required, disabled, readOnly, helperText, size = 'small', labelMargin = 5, pickTime = false, inputRef,
|
|
170
|
+
function CustomDatePicker({ error, required, disabled, readOnly, helperText, size = 'small', labelMargin = 5, pickTime = false, inputRef, label,
|
|
171
171
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
172
172
|
orientation: _orientation, ...others }) {
|
|
173
|
+
// MUI X v9 no longer forwards `required` to the field-slot label when the field is a Material
|
|
174
|
+
// TextField (the legacy `slots.field` shape used here), so the required asterisk never renders.
|
|
175
|
+
// The picker's `label` does propagate to the field via the picker context, so bake the marker
|
|
176
|
+
// into the label to keep the required indicator visible.
|
|
177
|
+
const displayLabel = required && typeof label === 'string' ? `${label} *` : label;
|
|
173
178
|
if (pickTime) {
|
|
174
|
-
return (_jsx(StyledDateTimePicker, { ampm: true, size: size, reduceAnimations: true, ...others, disabled: disabled, readOnly: readOnly, defaultValue: required ? new Date() : undefined, showDaysOutsideCurrentMonth: true, slotProps: {
|
|
179
|
+
return (_jsx(StyledDateTimePicker, { ampm: true, size: size, reduceAnimations: true, ...others, label: displayLabel, disabled: disabled, readOnly: readOnly, defaultValue: required ? new Date() : undefined, showDaysOutsideCurrentMonth: true, slotProps: {
|
|
175
180
|
actionBar: {
|
|
176
181
|
actions: ['clear', 'accept'],
|
|
177
182
|
},
|
|
@@ -231,7 +236,7 @@ orientation: _orientation, ...others }) {
|
|
|
231
236
|
} }));
|
|
232
237
|
}
|
|
233
238
|
else {
|
|
234
|
-
return (_jsx(StyledDatePicker, { ...others, size: size, reduceAnimations: true, readOnly: readOnly, disabled: disabled, defaultValue: required ? new Date() : undefined, showDaysOutsideCurrentMonth: true, views: ['year', 'month', 'day'], slotProps: {
|
|
239
|
+
return (_jsx(StyledDatePicker, { ...others, label: displayLabel, size: size, reduceAnimations: true, readOnly: readOnly, disabled: disabled, defaultValue: required ? new Date() : undefined, showDaysOutsideCurrentMonth: true, views: ['year', 'month', 'day'], slotProps: {
|
|
235
240
|
actionBar: {
|
|
236
241
|
actions: ['clear', 'accept'],
|
|
237
242
|
},
|
|
@@ -124,7 +124,12 @@ const formatDate = (date, showYear) => {
|
|
|
124
124
|
return '';
|
|
125
125
|
return format(date, showYear ? 'PP' : 'dd MMM');
|
|
126
126
|
};
|
|
127
|
-
function CustomDatePickerRange({ error, disabled, readOnly, helperText, size = 'small', labelMargin = 5, value, onChange, inputRef, shortcuts, ...others }) {
|
|
127
|
+
function CustomDatePickerRange({ error, disabled, readOnly, helperText, size = 'small', labelMargin = 5, value, onChange, inputRef, shortcuts, label, required, ...others }) {
|
|
128
|
+
// MUI X v9 no longer forwards `required` to the field-slot label when the field is a Material
|
|
129
|
+
// TextField (the legacy `slots.field` shape used here), so the required asterisk never renders.
|
|
130
|
+
// The picker's `label` does propagate to the field via the picker context, so bake the marker
|
|
131
|
+
// into the label to keep the required indicator visible.
|
|
132
|
+
const displayLabel = required && typeof label === 'string' ? `${label} *` : label;
|
|
128
133
|
const [startDate, endDate] = value ?? [null, null];
|
|
129
134
|
const hasCompleteRange = Boolean(startDate) && Boolean(endDate);
|
|
130
135
|
const showYear = startDate &&
|
|
@@ -135,7 +140,7 @@ function CustomDatePickerRange({ error, disabled, readOnly, helperText, size = '
|
|
|
135
140
|
const formattedStartDate = formatDate(startDate, Boolean(showYear));
|
|
136
141
|
const formattedEndDate = formatDate(endDate, Boolean(showYear));
|
|
137
142
|
const toolbarText = hasCompleteRange ? `${formattedStartDate} – ${formattedEndDate}` : '– –';
|
|
138
|
-
return (_jsx(StyledDateRangePicker, { ...others, value: value, onChange: onChange, size: size, disabled: disabled, readOnly: readOnly, reduceAnimations: true, showDaysOutsideCurrentMonth: true, slots: {
|
|
143
|
+
return (_jsx(StyledDateRangePicker, { ...others, label: displayLabel, value: value, onChange: onChange, size: size, disabled: disabled, readOnly: readOnly, reduceAnimations: true, showDaysOutsideCurrentMonth: true, slots: {
|
|
139
144
|
field: CustomRangePickerField,
|
|
140
145
|
day: StyledDay,
|
|
141
146
|
dialog: StyledDialog,
|
|
@@ -178,7 +183,7 @@ function CustomDatePickerRange({ error, disabled, readOnly, helperText, size = '
|
|
|
178
183
|
field: {
|
|
179
184
|
size,
|
|
180
185
|
error,
|
|
181
|
-
required
|
|
186
|
+
required,
|
|
182
187
|
helperText,
|
|
183
188
|
labelMargin,
|
|
184
189
|
fullWidth: true,
|
|
@@ -3,6 +3,7 @@ import { createFilterOptions, CircularProgress, Autocomplete as MuiAutocomplete,
|
|
|
3
3
|
import ErkSelect from '../ErkSelect/ErkSelect';
|
|
4
4
|
import { StyledTextField } from '../ErkTextField/ErkTextField';
|
|
5
5
|
import ErkMenuItem from '../ErkMenuItem';
|
|
6
|
+
import ErkAutocompleteOption from '../ErkAutocompleteOption';
|
|
6
7
|
const filter = createFilterOptions();
|
|
7
8
|
const StyledAutoComplete = styled(MuiAutocomplete)(({ theme, readOnly, ...props }) => ({
|
|
8
9
|
'& .Erk-MuiInputBase-root': {
|
|
@@ -34,7 +35,7 @@ export default function ErkSmartSelect({ options, value = '', onChange, label, g
|
|
|
34
35
|
else {
|
|
35
36
|
return filter(options, params);
|
|
36
37
|
}
|
|
37
|
-
}, renderOption: ({ key, ...props }, option) => (_jsx(
|
|
38
|
+
}, renderOption: ({ key, ...props }, option, state) => (_jsx(ErkAutocompleteOption, { ...props, selected: state.selected, children: option.label }, key)), getOptionKey: (option) => getValueString(option), getOptionLabel: (option) => {
|
|
38
39
|
// Value selected with enter, right from the input
|
|
39
40
|
if (typeof option === 'string') {
|
|
40
41
|
return option;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import '@testing-library/jest-dom';
|
|
6
|
+
import ErkSmartSelect from './ErkSmartSelect';
|
|
7
|
+
import MaterialProviders from '../../Utils/MaterialProviders';
|
|
8
|
+
import InternalFormStyle from '../../constants/InternalFormStyle';
|
|
9
|
+
const options = [
|
|
10
|
+
{ label: 'Uno', value: '1' },
|
|
11
|
+
{ label: 'Dos', value: '2' },
|
|
12
|
+
];
|
|
13
|
+
describe('ErkSmartSelect (searchable)', () => {
|
|
14
|
+
it('renders the options as listbox <li> items when opened, with no prop leaking to the DOM', async () => {
|
|
15
|
+
const user = userEvent.setup();
|
|
16
|
+
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
17
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkSmartSelect, { searchable: true, label: "Selector", options: options, value: "", onChange: () => { } }) }));
|
|
18
|
+
await user.click(screen.getByRole('combobox'));
|
|
19
|
+
const option = await screen.findByRole('option', { name: 'Uno' });
|
|
20
|
+
expect(option.tagName).toBe('LI');
|
|
21
|
+
expect(screen.getByRole('option', { name: 'Dos' })).toBeInTheDocument();
|
|
22
|
+
// `ownerState`/`selected` must not reach the <li> (styled shouldForwardProp filters them).
|
|
23
|
+
expect(option).not.toHaveAttribute('ownerState');
|
|
24
|
+
expect(consoleError).not.toHaveBeenCalled();
|
|
25
|
+
consoleError.mockRestore();
|
|
26
|
+
});
|
|
27
|
+
it('marks the selected option', async () => {
|
|
28
|
+
const user = userEvent.setup();
|
|
29
|
+
render(_jsx(MaterialProviders, { formStyle: InternalFormStyle, children: _jsx(ErkSmartSelect, { searchable: true, label: "Selector", options: options, value: options[0], onChange: () => { } }) }));
|
|
30
|
+
await user.click(screen.getByRole('combobox'));
|
|
31
|
+
expect(await screen.findByRole('option', { name: 'Uno' })).toHaveAttribute('aria-selected', 'true');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -3,9 +3,15 @@ import { EurekaDraft } from '../../@Types/Draft/Draft';
|
|
|
3
3
|
interface SmartDraftRendererProps {
|
|
4
4
|
property: string;
|
|
5
5
|
draft: EurekaDraft;
|
|
6
|
+
/**
|
|
7
|
+
* The unmapped draft from the form definition. `draft` may be a previously mapped copy
|
|
8
|
+
* (entities already resolved and stripped), so whether mapping runs — and keeps re-running
|
|
9
|
+
* when `dependencies` change — must be decided from the original.
|
|
10
|
+
*/
|
|
11
|
+
sourceDraft?: EurekaDraft;
|
|
6
12
|
margin?: React.CSSProperties['margin'];
|
|
7
13
|
dependencies: Record<string, any>;
|
|
8
14
|
onChange?: (value: EurekaDraft) => void;
|
|
9
15
|
}
|
|
10
|
-
declare function SmartDraftRenderer({ draft, margin, onChange, property, dependencies }: SmartDraftRendererProps): JSX.Element;
|
|
16
|
+
declare function SmartDraftRenderer({ draft, sourceDraft, margin, onChange, property, dependencies, }: SmartDraftRendererProps): JSX.Element;
|
|
11
17
|
export default SmartDraftRenderer;
|
|
@@ -11,11 +11,11 @@ import { skipToken } from '@reduxjs/toolkit/query';
|
|
|
11
11
|
import { useAppSelector } from '../../hooks';
|
|
12
12
|
import { IdFormContext } from '../../Contexts/FormContext';
|
|
13
13
|
import { DraftLoadingContext } from './DraftLoadingContext';
|
|
14
|
-
function SmartDraftRenderer({ draft, margin, onChange, property, dependencies }) {
|
|
14
|
+
function SmartDraftRenderer({ draft, sourceDraft, margin, onChange, property, dependencies, }) {
|
|
15
15
|
const idForm = useContext(IdFormContext);
|
|
16
16
|
const mapDraftEntities = useContext(CustomContext).mapDraftEntities;
|
|
17
17
|
const postview = useAppSelector((state) => state.global.postview);
|
|
18
|
-
const hasEntities = useMemo(() => Object.keys(draft.entityMap).length > 0, [draft]);
|
|
18
|
+
const hasEntities = useMemo(() => Object.keys((sourceDraft ?? draft).entityMap).length > 0, [sourceDraft, draft]);
|
|
19
19
|
const { data: mapped, isFetching } = DraftApi.useMapDraftQuery(!hasEntities || postview
|
|
20
20
|
? skipToken
|
|
21
21
|
: {
|
package/package.json
CHANGED