@qoretechnologies/reqraft 0.10.61 → 0.10.62-pr.137.g86641aa
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/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
- package/dist/components/dpqlEditor/DpqlEditor.js +8 -3
- package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
- package/dist/components/dpqlEditor/types.d.ts +5 -0
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +17 -8
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +65 -15
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +12 -1
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/compactRowContext.d.ts +2 -0
- package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowContext.js.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +5 -1
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/expressions/ExpressionField.d.ts +21 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +72 -14
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +16 -3
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/expressionOperands.d.ts +16 -0
- package/dist/components/form/expressions/expressionOperands.d.ts.map +1 -0
- package/dist/components/form/expressions/expressionOperands.js +97 -0
- package/dist/components/form/expressions/expressionOperands.js.map +1 -0
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +15 -0
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts +2 -0
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +24 -5
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/expressions/textOfExpression.d.ts +18 -0
- package/dist/components/form/expressions/textOfExpression.d.ts.map +1 -0
- package/dist/components/form/expressions/textOfExpression.js +37 -0
- package/dist/components/form/expressions/textOfExpression.js.map +1 -0
- package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.js +7 -7
- package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +8 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +10 -10
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +6 -0
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +280 -55
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/fields/template/writtenAsText.d.ts +57 -0
- package/dist/components/form/fields/template/writtenAsText.d.ts.map +1 -0
- package/dist/components/form/fields/template/writtenAsText.js +203 -0
- package/dist/components/form/fields/template/writtenAsText.js.map +1 -0
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +7 -1
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/smartEditor/useLspSession.d.ts +6 -0
- package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
- package/dist/components/smartEditor/useLspSession.js +7 -6
- package/dist/components/smartEditor/useLspSession.js.map +1 -1
- package/dist/helpers/templates.d.ts +18 -0
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +30 -1
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +98 -18
- package/dist/helpers/validations.js.map +1 -1
- package/package.json +1 -1
- package/src/components/dpqlEditor/DpqlEditor.tsx +11 -18
- package/src/components/dpqlEditor/types.ts +5 -0
- package/src/components/form/engine/CompactRow.tsx +31 -10
- package/src/components/form/engine/FormEngine.stories.tsx +656 -18
- package/src/components/form/engine/FormEngine.tsx +64 -22
- package/src/components/form/engine/FormEngineRemote.stories.tsx +5 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +12 -1
- package/src/components/form/engine/compactRowContext.ts +2 -0
- package/src/components/form/engine/readFirst.ts +7 -1
- package/src/components/form/expressions/ExpressionField.stories.tsx +57 -9
- package/src/components/form/expressions/ExpressionField.tsx +179 -60
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +8 -1
- package/src/components/form/expressions/builder/index.tsx +16 -3
- package/src/components/form/expressions/dpqlMockLanguage.ts +7 -2
- package/src/components/form/expressions/expressionOperands.ts +74 -0
- package/src/components/form/expressions/mockExpressions.ts +16 -4
- package/src/components/form/expressions/renderExpressionToText.ts +26 -9
- package/src/components/form/expressions/textOfExpression.ts +39 -0
- package/src/components/form/expressions/useRenderExpression.ts +9 -1
- package/src/components/form/fields/number/Number.stories.tsx +8 -7
- package/src/components/form/fields/rich-text/RichText.tsx +18 -10
- package/src/components/form/fields/template/TemplateField.stories.tsx +486 -18
- package/src/components/form/fields/template/TemplateField.tsx +506 -62
- package/src/components/form/fields/template/writtenAsText.ts +217 -0
- package/src/components/form/index.tsx +6 -0
- package/src/components/smartEditor/useLspSession.ts +15 -10
- package/src/helpers/templates.ts +35 -0
- package/src/helpers/validations.ts +83 -12
|
@@ -254,6 +254,46 @@ export const NumberComponent: StoryObj<typeof meta> = {
|
|
|
254
254
|
},
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
+
export const NumberTypedFreely: StoryObj<typeof meta> = {
|
|
258
|
+
args: {
|
|
259
|
+
value: 25,
|
|
260
|
+
type: 'int',
|
|
261
|
+
allowTemplates: true,
|
|
262
|
+
componentFromType: true,
|
|
263
|
+
},
|
|
264
|
+
parameters: {
|
|
265
|
+
docs: {
|
|
266
|
+
description: {
|
|
267
|
+
story:
|
|
268
|
+
'Renders TemplateField for a whole number that takes templates. A click in the field lists the templates of its type at once; what is typed is kept as typed (12abc is flagged by the form, not cut to 12); a template chosen from the list is the value, shown by its name as a chip in the text field it is written in, where text can be written around it.',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
async play({ canvasElement }) {
|
|
273
|
+
const canvas = within(canvasElement);
|
|
274
|
+
const input = (await canvas.findByDisplayValue('25')) as HTMLInputElement;
|
|
275
|
+
// the first click lists the templates
|
|
276
|
+
await userEvent.click(input);
|
|
277
|
+
await waitFor(() => expect(document.querySelector('.reqore-popover-content .reqore-menu-item'), 'the list at the first click').toBeTruthy());
|
|
278
|
+
// typed freely
|
|
279
|
+
await userEvent.clear(input);
|
|
280
|
+
await userEvent.type(input, '12abc');
|
|
281
|
+
await expect(input).toHaveValue('12abc');
|
|
282
|
+
// a template from the list: a chip in the text field the value is written in, named by the template
|
|
283
|
+
await userEvent.clear(input);
|
|
284
|
+
const first = await waitFor(() => {
|
|
285
|
+
const item = document.querySelector<HTMLElement>('.reqore-popover-content .reqore-menu-item');
|
|
286
|
+
expect(item, 'the list again').toBeTruthy();
|
|
287
|
+
return item as HTMLElement;
|
|
288
|
+
});
|
|
289
|
+
const name = first.querySelector('.reqore-menu-item-label, [data-reqore-marquee="text"]')?.textContent ?? first.textContent ?? '';
|
|
290
|
+
await userEvent.click(first);
|
|
291
|
+
// the template is the value, named as the catalogue names it, a chip in the text field
|
|
292
|
+
await waitFor(() => expect(canvasElement.querySelector('[data-slate-editor]')).toBeTruthy());
|
|
293
|
+
await expect(canvasElement.textContent).toContain(name.trim());
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
|
|
257
297
|
export const AutoComponent: StoryObj<typeof meta> = {
|
|
258
298
|
args: {
|
|
259
299
|
defaultType: 'auto',
|
|
@@ -350,16 +390,16 @@ export const BracedTemplateValue: StoryObj<typeof meta> = {
|
|
|
350
390
|
docs: {
|
|
351
391
|
description: {
|
|
352
392
|
story:
|
|
353
|
-
'Renders TemplateField for a string field whose saved value is a braced state-output reference ($data:{id.filename}) — the field shows the resolved
|
|
393
|
+
'Renders TemplateField for a string field whose saved value is a braced state-output reference ($data:{id.filename}) — the field shows the resolved chip ("Filename") in the text it is written in, never the raw token, which is what rehydrated drafts degraded to before the token grammar learned braces.',
|
|
354
394
|
},
|
|
355
395
|
},
|
|
356
396
|
},
|
|
357
397
|
play: async () => {
|
|
358
398
|
await waitFor(() => {
|
|
359
|
-
const chip = Array.from(document.querySelectorAll('
|
|
399
|
+
const chip = Array.from(document.querySelectorAll('[data-slate-editor] [contenteditable="false"]')).find((button) =>
|
|
360
400
|
button.textContent?.includes('Filename')
|
|
361
401
|
);
|
|
362
|
-
expect(chip, 'the braced value renders as the labeled
|
|
402
|
+
expect(chip, 'the braced value renders as the labeled chip in the text field').toBeTruthy();
|
|
363
403
|
});
|
|
364
404
|
const rawTextareas = Array.from(document.querySelectorAll('textarea')).filter((textarea) =>
|
|
365
405
|
textarea.value.includes('$data:{')
|
|
@@ -409,12 +449,12 @@ export const AliasedStateTemplateValue: StoryObj<typeof meta> = {
|
|
|
409
449
|
},
|
|
410
450
|
play: async () => {
|
|
411
451
|
await waitFor(() => {
|
|
412
|
-
const chip = Array.from(document.querySelectorAll('
|
|
452
|
+
const chip = Array.from(document.querySelectorAll('[data-slate-editor] [contenteditable="false"]')).find((button) =>
|
|
413
453
|
button.textContent?.includes('Choices')
|
|
414
454
|
);
|
|
415
|
-
expect(chip, 'the aliased value resolves to the labeled
|
|
455
|
+
expect(chip, 'the aliased value resolves to the labeled chip').toBeTruthy();
|
|
416
456
|
});
|
|
417
|
-
const rawChips = Array.from(document.querySelectorAll('
|
|
457
|
+
const rawChips = Array.from(document.querySelectorAll('[data-slate-editor] [contenteditable="false"]')).filter((button) =>
|
|
418
458
|
button.textContent?.includes('$data:{')
|
|
419
459
|
);
|
|
420
460
|
expect(rawChips, 'no chip prints the raw token').toHaveLength(0);
|
|
@@ -458,12 +498,12 @@ export const ExtendedPathTemplateValue: StoryObj<typeof meta> = {
|
|
|
458
498
|
},
|
|
459
499
|
play: async () => {
|
|
460
500
|
await waitFor(() => {
|
|
461
|
-
const chip = Array.from(document.querySelectorAll('
|
|
501
|
+
const chip = Array.from(document.querySelectorAll('[data-slate-editor] [contenteditable="false"]')).find((button) =>
|
|
462
502
|
button.textContent?.includes('Choices[0].message.content')
|
|
463
503
|
);
|
|
464
504
|
expect(chip, 'the extended path is named after its ancestor').toBeTruthy();
|
|
465
505
|
});
|
|
466
|
-
const rawChips = Array.from(document.querySelectorAll('
|
|
506
|
+
const rawChips = Array.from(document.querySelectorAll('[data-slate-editor] [contenteditable="false"]')).filter((button) =>
|
|
467
507
|
button.textContent?.includes('$data:{')
|
|
468
508
|
);
|
|
469
509
|
expect(rawChips, 'no chip prints the raw token').toHaveLength(0);
|
|
@@ -569,16 +609,27 @@ export const TemplateValueCanBeRemoved: StoryObj<typeof meta> = {
|
|
|
569
609
|
docs: {
|
|
570
610
|
description: {
|
|
571
611
|
story:
|
|
572
|
-
'Renders TemplateField holding a $config:boolean template value.
|
|
612
|
+
'Renders TemplateField holding a $config:boolean template value, a chip in the text field the value is written in. Deleting the chip clears the value and leaves the text field, the cursor in it, for what is written next; the yes/no checkbox is one menu item away: "Use Custom Value" in the ⋮ menu.',
|
|
573
613
|
},
|
|
574
614
|
},
|
|
575
615
|
},
|
|
576
|
-
play: async () => {
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
await
|
|
580
|
-
|
|
581
|
-
|
|
616
|
+
play: async ({ canvasElement }) => {
|
|
617
|
+
// the template is a chip in the text field the yes/no value is written in; deleted, the field is empty
|
|
618
|
+
// and still the text field
|
|
619
|
+
const editor = await waitFor(() => {
|
|
620
|
+
const el = document.querySelector<HTMLElement>('[data-slate-editor]');
|
|
621
|
+
expect(el).toBeTruthy();
|
|
622
|
+
return el as HTMLElement;
|
|
623
|
+
});
|
|
624
|
+
await userEvent.type(editor, '{Backspace}{Backspace}');
|
|
625
|
+
await waitFor(() => expect(editor.textContent?.replace(/\uFEFF/g, '').trim()).toBe(''));
|
|
626
|
+
expect(editor.isConnected).toBe(true);
|
|
627
|
+
expect(document.querySelector('.reqore-checkbox')).toBeNull();
|
|
628
|
+
// the checkbox, from the menu
|
|
629
|
+
const buttons = canvasElement.querySelectorAll<HTMLElement>('button');
|
|
630
|
+
await userEvent.click(buttons[buttons.length - 1]);
|
|
631
|
+
await userEvent.click(await screen.findByText('Use Custom Value'));
|
|
632
|
+
await waitFor(() => expect(document.querySelector('.reqore-checkbox')).toBeInTheDocument());
|
|
582
633
|
},
|
|
583
634
|
};
|
|
584
635
|
|
|
@@ -712,7 +763,7 @@ export const ValueIsResetWhenChangingToCustom: StoryObj<typeof meta> = {
|
|
|
712
763
|
docs: {
|
|
713
764
|
description: {
|
|
714
765
|
story:
|
|
715
|
-
'Renders TemplateField over a number type holding a $config:something template value.
|
|
766
|
+
'Renders TemplateField over a number type holding a $config:something template value, a chip in the text field. Deleting the chip resets the value to undefined and fires onChange with the cleared value.',
|
|
716
767
|
},
|
|
717
768
|
},
|
|
718
769
|
},
|
|
@@ -744,8 +795,10 @@ export const ValueIsResetWhenChangingToCustom: StoryObj<typeof meta> = {
|
|
|
744
795
|
{ timeout: 10000 }
|
|
745
796
|
);
|
|
746
797
|
|
|
747
|
-
|
|
748
|
-
|
|
798
|
+
// the template is a chip in the text field; deleted, the value is cleared
|
|
799
|
+
const editor = document.querySelector<HTMLElement>('[data-slate-editor]') as HTMLElement;
|
|
800
|
+
await userEvent.type(editor, '{Backspace}{Backspace}');
|
|
801
|
+
await waitFor(() => expect(args.onChange).toHaveBeenLastCalledWith('Test Field', undefined));
|
|
749
802
|
},
|
|
750
803
|
};
|
|
751
804
|
|
|
@@ -1119,3 +1172,418 @@ export const AnyWithLiteralOpensOnTheValue: StoryObj<typeof meta> = {
|
|
|
1119
1172
|
});
|
|
1120
1173
|
},
|
|
1121
1174
|
};
|
|
1175
|
+
|
|
1176
|
+
// --- written as text (qorus#646) ----------------------------------------------
|
|
1177
|
+
// A field that takes templates and custom values is ONE text field: a template chosen into it is a chip,
|
|
1178
|
+
// text is written around it, and what the field holds is read from the text.
|
|
1179
|
+
|
|
1180
|
+
/** The row's fields as templates, each with its type, as a form's record templates carry them. */
|
|
1181
|
+
const recordTemplates = {
|
|
1182
|
+
items: [
|
|
1183
|
+
{
|
|
1184
|
+
label: 'Fields of the row',
|
|
1185
|
+
items: [
|
|
1186
|
+
{ label: 'pos', value: '$record:{pos}', badge: 'int', description: 'Position' },
|
|
1187
|
+
{ label: 'bezeichnung', value: '$record:{bezeichnung}', badge: 'string', description: 'Description' },
|
|
1188
|
+
{ label: 'liefertermin', value: '$record:{liefertermin}', badge: 'date', description: 'Delivery date' },
|
|
1189
|
+
],
|
|
1190
|
+
},
|
|
1191
|
+
],
|
|
1192
|
+
};
|
|
1193
|
+
|
|
1194
|
+
/** The field, and what it holds: its value and that value's type, as the form receives them. */
|
|
1195
|
+
const WrittenAsText = (args: any) => {
|
|
1196
|
+
const [held, setHeld] = useState<{ value: unknown; type?: string }>({ value: args.value, type: args.type });
|
|
1197
|
+
return (
|
|
1198
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 520 }}>
|
|
1199
|
+
<TemplateField
|
|
1200
|
+
{...args}
|
|
1201
|
+
value={held.value}
|
|
1202
|
+
onChange={(name: string, value: unknown, type?: string) => {
|
|
1203
|
+
args.onChange?.(name, value, type);
|
|
1204
|
+
setHeld({ value, type });
|
|
1205
|
+
}}
|
|
1206
|
+
/>
|
|
1207
|
+
{/* no value at all (an emptied field) is told from null, which is a value */}
|
|
1208
|
+
<code
|
|
1209
|
+
className='held-value'
|
|
1210
|
+
data-value={held.value === undefined ? '' : JSON.stringify(held.value)}
|
|
1211
|
+
data-type={held.type ?? ''}
|
|
1212
|
+
>
|
|
1213
|
+
{held.value === undefined ? '(no value)' : `${JSON.stringify(held.value)} : ${held.type ?? ''}`}
|
|
1214
|
+
</code>
|
|
1215
|
+
</div>
|
|
1216
|
+
);
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
const held = (canvasElement: HTMLElement) => {
|
|
1220
|
+
const el = canvasElement.querySelector('.held-value') as HTMLElement;
|
|
1221
|
+
return { value: el.dataset.value ? JSON.parse(el.dataset.value) : undefined, type: el.dataset.type };
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
/** The text field of the template editor. */
|
|
1225
|
+
const textField = (canvasElement: HTMLElement) =>
|
|
1226
|
+
waitFor(() => {
|
|
1227
|
+
const el = canvasElement.querySelector<HTMLElement>('[data-slate-editor]');
|
|
1228
|
+
expect(el, 'the field written as text').toBeTruthy();
|
|
1229
|
+
return el as HTMLElement;
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
/** Choose a template from the list the text field offers. */
|
|
1233
|
+
const chooseTemplate = async (editor: HTMLElement, name: string) => {
|
|
1234
|
+
await userEvent.click(editor);
|
|
1235
|
+
const item = await waitFor(() => {
|
|
1236
|
+
const el = [...document.querySelectorAll<HTMLElement>('.reqore-popover-content .reqore-menu-item')].find((i) =>
|
|
1237
|
+
i.textContent?.includes(name)
|
|
1238
|
+
);
|
|
1239
|
+
expect(el, `the template ${name} on offer`).toBeTruthy();
|
|
1240
|
+
return el as HTMLElement;
|
|
1241
|
+
});
|
|
1242
|
+
await userEvent.click(item);
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
export const WrittenAsTextTemplateAlone: StoryObj<typeof meta> = {
|
|
1246
|
+
args: { type: 'any', allowTemplates: true, allowCustomValues: true, templates: recordTemplates as any, filterTemplatesByType: false },
|
|
1247
|
+
render: WrittenAsText,
|
|
1248
|
+
parameters: {
|
|
1249
|
+
docs: {
|
|
1250
|
+
description: {
|
|
1251
|
+
story:
|
|
1252
|
+
'Renders an untyped (any) field written as text. A template chosen into it is a chip, and while it stands alone the value is that template, of the template\'s own type: the whole-number field pos makes the value a whole number.',
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
async play({ canvasElement }) {
|
|
1257
|
+
const editor = await textField(canvasElement);
|
|
1258
|
+
await chooseTemplate(editor, 'pos');
|
|
1259
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: '$record:{pos}', type: 'int' }));
|
|
1260
|
+
expect(editor.textContent).toContain('pos');
|
|
1261
|
+
},
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
export const WrittenAsTextTextAroundTheTemplate: StoryObj<typeof meta> = {
|
|
1265
|
+
args: { ...WrittenAsTextTemplateAlone.args, value: '$record:{pos}' },
|
|
1266
|
+
render: WrittenAsText,
|
|
1267
|
+
parameters: {
|
|
1268
|
+
docs: {
|
|
1269
|
+
description: {
|
|
1270
|
+
story:
|
|
1271
|
+
'Renders an untyped field holding the template pos. Text written around it makes the value text ($record:{pos} Stk.); deleting that text again leaves the template alone, and the value is a whole number again.',
|
|
1272
|
+
},
|
|
1273
|
+
},
|
|
1274
|
+
},
|
|
1275
|
+
async play({ canvasElement }) {
|
|
1276
|
+
const editor = await textField(canvasElement);
|
|
1277
|
+
await waitFor(() => expect(editor.textContent).toContain('pos'));
|
|
1278
|
+
// text after the chip
|
|
1279
|
+
await userEvent.type(editor, ' Stk.');
|
|
1280
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: '$record:{pos} Stk.', type: 'string' }));
|
|
1281
|
+
// and gone again: the template alone, of its own type
|
|
1282
|
+
await userEvent.keyboard('{Backspace}{Backspace}{Backspace}{Backspace}{Backspace}');
|
|
1283
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: '$record:{pos}', type: 'int' }));
|
|
1284
|
+
},
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
export const WrittenAsTextNumberWithATemplate: StoryObj<typeof meta> = {
|
|
1288
|
+
args: {
|
|
1289
|
+
type: 'int',
|
|
1290
|
+
value: '$record:{pos}',
|
|
1291
|
+
allowTemplates: true,
|
|
1292
|
+
allowCustomValues: true,
|
|
1293
|
+
componentFromType: true,
|
|
1294
|
+
templates: recordTemplates as any,
|
|
1295
|
+
},
|
|
1296
|
+
render: WrittenAsText,
|
|
1297
|
+
parameters: {
|
|
1298
|
+
docs: {
|
|
1299
|
+
description: {
|
|
1300
|
+
story:
|
|
1301
|
+
'Renders a whole-number field holding the template pos: the template is a chip in a text field, not a picker that holds it and nothing else. A number written in its place is the number (12, a whole number); text that is not one is kept as written for the form to flag.',
|
|
1302
|
+
},
|
|
1303
|
+
},
|
|
1304
|
+
},
|
|
1305
|
+
async play({ canvasElement }) {
|
|
1306
|
+
const editor = await textField(canvasElement);
|
|
1307
|
+
await waitFor(() => expect(editor.textContent).toContain('pos'));
|
|
1308
|
+
expect(held(canvasElement)).toEqual({ value: '$record:{pos}', type: 'int' });
|
|
1309
|
+
// a literal of the field's type
|
|
1310
|
+
// the template deleted (it is one chip) and a number written instead
|
|
1311
|
+
await userEvent.type(editor, '{Backspace}{Backspace}12');
|
|
1312
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: 12, type: 'int' }));
|
|
1313
|
+
},
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
export const WrittenAsTextNumberEmptiedKeepsItsField: StoryObj<typeof meta> = {
|
|
1317
|
+
args: {
|
|
1318
|
+
type: 'int',
|
|
1319
|
+
value: '$record:{pos}',
|
|
1320
|
+
allowTemplates: true,
|
|
1321
|
+
allowCustomValues: true,
|
|
1322
|
+
componentFromType: true,
|
|
1323
|
+
templates: recordTemplates as any,
|
|
1324
|
+
},
|
|
1325
|
+
render: WrittenAsText,
|
|
1326
|
+
parameters: {
|
|
1327
|
+
docs: {
|
|
1328
|
+
description: {
|
|
1329
|
+
story:
|
|
1330
|
+
'Renders a whole-number field holding the template pos, whose chip is deleted: the field is empty, and it is still the text field the author is writing in, with the cursor in it, so the number typed next is the value.',
|
|
1331
|
+
},
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
async play({ canvasElement }) {
|
|
1335
|
+
const editor = await textField(canvasElement);
|
|
1336
|
+
await waitFor(() => expect(editor.textContent).toContain('pos'));
|
|
1337
|
+
await userEvent.click(editor);
|
|
1338
|
+
await userEvent.keyboard('{Backspace}{Backspace}');
|
|
1339
|
+
await waitFor(() => expect(held(canvasElement).value).toBeUndefined());
|
|
1340
|
+
await sleep(300);
|
|
1341
|
+
// the same text field, the cursor still in it: no other editor was put in its place
|
|
1342
|
+
expect(editor.isConnected).toBe(true);
|
|
1343
|
+
expect(editor.contains(document.activeElement) || editor === document.activeElement).toBe(true);
|
|
1344
|
+
await userEvent.keyboard('7');
|
|
1345
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: 7, type: 'int' }));
|
|
1346
|
+
},
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
export const WrittenAsTextChipOfAnotherType: StoryObj<typeof meta> = {
|
|
1350
|
+
args: {
|
|
1351
|
+
type: 'bool',
|
|
1352
|
+
value: '$record:{pos} Stk.',
|
|
1353
|
+
allowTemplates: true,
|
|
1354
|
+
allowCustomValues: true,
|
|
1355
|
+
componentFromType: true,
|
|
1356
|
+
templates: recordTemplates as any,
|
|
1357
|
+
},
|
|
1358
|
+
render: WrittenAsText,
|
|
1359
|
+
parameters: {
|
|
1360
|
+
docs: {
|
|
1361
|
+
description: {
|
|
1362
|
+
story:
|
|
1363
|
+
'Renders a yes/no field holding the whole-number field pos with text after it. Its chip reads "pos", as the catalogue names it, though a yes/no field offers only yes/no fields: chips used to be named from that offered list, and a field not on it read as its reference ("record: pos").',
|
|
1364
|
+
},
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
async play({ canvasElement }) {
|
|
1368
|
+
const editor = await textField(canvasElement);
|
|
1369
|
+
await waitFor(() => expect(editor.querySelector('.reqore-tag')).toBeTruthy());
|
|
1370
|
+
const chip = editor.querySelector('.reqore-tag')?.textContent?.replace(/\uFEFF/g, '').trim();
|
|
1371
|
+
expect(chip).toBe('pos');
|
|
1372
|
+
},
|
|
1373
|
+
};
|
|
1374
|
+
|
|
1375
|
+
/** The field with a host's own "Clear value", as a form's card has. */
|
|
1376
|
+
const WrittenAsTextWithHostClear = (args: any) => {
|
|
1377
|
+
const [held, setHeld] = useState<{ value: unknown; type?: string }>({ value: args.value, type: args.type });
|
|
1378
|
+
return (
|
|
1379
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, maxWidth: 520 }}>
|
|
1380
|
+
<TemplateField
|
|
1381
|
+
{...args}
|
|
1382
|
+
value={held.value}
|
|
1383
|
+
onChange={(_name: string, value: unknown, type?: string) => setHeld({ value, type })}
|
|
1384
|
+
/>
|
|
1385
|
+
<button type='button' className='host-clear' onClick={() => setHeld({ value: undefined, type: args.type })}>
|
|
1386
|
+
Clear value
|
|
1387
|
+
</button>
|
|
1388
|
+
<code className='held-value' data-value={held.value === undefined ? '' : JSON.stringify(held.value)} data-type={held.type ?? ''}>
|
|
1389
|
+
{held.value === undefined ? '(no value)' : `${JSON.stringify(held.value)} : ${held.type ?? ''}`}
|
|
1390
|
+
</code>
|
|
1391
|
+
</div>
|
|
1392
|
+
);
|
|
1393
|
+
};
|
|
1394
|
+
|
|
1395
|
+
export const WrittenAsTextClearedByItsHost: StoryObj<typeof meta> = {
|
|
1396
|
+
args: {
|
|
1397
|
+
type: 'bool',
|
|
1398
|
+
value: '$record:{pos}',
|
|
1399
|
+
allowTemplates: true,
|
|
1400
|
+
allowCustomValues: true,
|
|
1401
|
+
componentFromType: true,
|
|
1402
|
+
templates: recordTemplates as any,
|
|
1403
|
+
},
|
|
1404
|
+
render: WrittenAsTextWithHostClear,
|
|
1405
|
+
parameters: {
|
|
1406
|
+
docs: {
|
|
1407
|
+
description: {
|
|
1408
|
+
story:
|
|
1409
|
+
'Renders a yes/no field holding the field pos, beside its host\'s "Clear value" (a form card has one). Cleared by the host, the field is empty: it used to go on showing pos, because the text field kept its own copy of what it held.',
|
|
1410
|
+
},
|
|
1411
|
+
},
|
|
1412
|
+
},
|
|
1413
|
+
async play({ canvasElement }) {
|
|
1414
|
+
const editor = await textField(canvasElement);
|
|
1415
|
+
await waitFor(() => expect(editor.textContent).toContain('pos'));
|
|
1416
|
+
await userEvent.click(canvasElement.querySelector('.host-clear') as HTMLElement);
|
|
1417
|
+
await waitFor(() => expect(held(canvasElement).value).toBeUndefined());
|
|
1418
|
+
await waitFor(() => expect(canvasElement.textContent).not.toContain('pos'));
|
|
1419
|
+
},
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
export const WrittenAsTextExpressionDetected: StoryObj<typeof meta> = {
|
|
1423
|
+
args: {
|
|
1424
|
+
type: 'any',
|
|
1425
|
+
allowTemplates: true,
|
|
1426
|
+
allowCustomValues: true,
|
|
1427
|
+
allowFunctions: true,
|
|
1428
|
+
allowTextExpressions: true,
|
|
1429
|
+
expressions: storyExpressions as any,
|
|
1430
|
+
templates: recordTemplates as any,
|
|
1431
|
+
filterTemplatesByType: false,
|
|
1432
|
+
},
|
|
1433
|
+
render: WrittenAsText,
|
|
1434
|
+
parameters: {
|
|
1435
|
+
docs: {
|
|
1436
|
+
description: {
|
|
1437
|
+
story:
|
|
1438
|
+
'Renders an untyped field written as text. An expression written in it (1 + 2) is detected and offered: it is also valid text here, so nothing changes until the author takes it as an expression.',
|
|
1439
|
+
},
|
|
1440
|
+
},
|
|
1441
|
+
},
|
|
1442
|
+
async play({ canvasElement }) {
|
|
1443
|
+
const editor = await textField(canvasElement);
|
|
1444
|
+
await userEvent.click(editor);
|
|
1445
|
+
await userEvent.keyboard('1 + 2');
|
|
1446
|
+
await waitFor(() => expect(canvasElement.querySelector('.dpql-detected-offer')).toBeTruthy(), { timeout: 10000 });
|
|
1447
|
+
expect(held(canvasElement)).toEqual({ value: '1 + 2', type: 'string' });
|
|
1448
|
+
},
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
export const WrittenAsTextNullStaysAValue: StoryObj<typeof meta> = {
|
|
1452
|
+
args: { type: 'any', value: null, allowTemplates: true, allowCustomValues: true, templates: recordTemplates as any, filterTemplatesByType: false },
|
|
1453
|
+
render: WrittenAsText,
|
|
1454
|
+
parameters: {
|
|
1455
|
+
docs: {
|
|
1456
|
+
description: {
|
|
1457
|
+
story:
|
|
1458
|
+
'Renders an untyped field holding null, the literal that says "no value". It is a value, not an empty field: the field shows null, written as text, where it used to look exactly like a field holding nothing. Deleting it leaves the field empty - no value at all - and writing null again makes it null again.',
|
|
1459
|
+
},
|
|
1460
|
+
},
|
|
1461
|
+
},
|
|
1462
|
+
async play({ canvasElement }) {
|
|
1463
|
+
const editor = await textField(canvasElement);
|
|
1464
|
+
const shown = () => editor.textContent?.replace(/\uFEFF/g, '').trim();
|
|
1465
|
+
await waitFor(() => expect(shown()).toBe('null'));
|
|
1466
|
+
await sleep(300);
|
|
1467
|
+
expect(held(canvasElement).value).toBeNull();
|
|
1468
|
+
expect(canvasElement.textContent).not.toContain('Value is empty');
|
|
1469
|
+
// deleted, the field is empty: no value, which is not null
|
|
1470
|
+
await userEvent.click(editor);
|
|
1471
|
+
await userEvent.keyboard('{Backspace}{Backspace}{Backspace}{Backspace}');
|
|
1472
|
+
await waitFor(() => expect(held(canvasElement).value).toBeUndefined());
|
|
1473
|
+
await waitFor(() => expect(shown()).toBe(''));
|
|
1474
|
+
// null written again is null
|
|
1475
|
+
await userEvent.keyboard('null');
|
|
1476
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: null, type: 'any' }));
|
|
1477
|
+
},
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
export const WrittenAsTextAllowedValues: StoryObj<typeof meta> = {
|
|
1481
|
+
args: {
|
|
1482
|
+
type: 'string',
|
|
1483
|
+
component: auto,
|
|
1484
|
+
allowTemplates: true,
|
|
1485
|
+
allowCustomValues: true,
|
|
1486
|
+
templates: recordTemplates as any,
|
|
1487
|
+
allowed_values: [
|
|
1488
|
+
// as a form's allowed values carry them: each value with its type
|
|
1489
|
+
{ name: 'open', display_name: 'Open', value: { type: 'string', value: 'open' } },
|
|
1490
|
+
{ name: 'closed', display_name: 'Closed', value: { type: 'string', value: 'closed' } },
|
|
1491
|
+
] as any,
|
|
1492
|
+
},
|
|
1493
|
+
render: WrittenAsText,
|
|
1494
|
+
parameters: {
|
|
1495
|
+
docs: {
|
|
1496
|
+
description: {
|
|
1497
|
+
story:
|
|
1498
|
+
'Renders a field with fixed choices (Open, Closed) that also takes templates: it keeps its list of choices, each named - it is not written as text, since anything else written would not be one of them. Choosing Closed gives closed.',
|
|
1499
|
+
},
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
async play({ canvasElement }) {
|
|
1503
|
+
const closed = await within(canvasElement).findByText('Closed');
|
|
1504
|
+
expect(within(canvasElement).getByText('Open')).toBeTruthy();
|
|
1505
|
+
expect(canvasElement.querySelector('[data-slate-editor]')).toBeNull();
|
|
1506
|
+
await userEvent.click(closed.closest('.reqore-checkbox') as HTMLElement);
|
|
1507
|
+
await waitFor(() => expect(held(canvasElement).value).toBe('closed'));
|
|
1508
|
+
},
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
export const WrittenAsTextDateKeepsItsControl: StoryObj<typeof meta> = {
|
|
1512
|
+
args: {
|
|
1513
|
+
type: 'date',
|
|
1514
|
+
allowTemplates: true,
|
|
1515
|
+
allowCustomValues: true,
|
|
1516
|
+
componentFromType: true,
|
|
1517
|
+
templates: recordTemplates as any,
|
|
1518
|
+
filterTemplatesByType: false,
|
|
1519
|
+
},
|
|
1520
|
+
render: WrittenAsText,
|
|
1521
|
+
parameters: {
|
|
1522
|
+
docs: {
|
|
1523
|
+
description: {
|
|
1524
|
+
story:
|
|
1525
|
+
'Renders a date field that takes templates. It keeps its date control, with the templates offered beside it ($): chosen, the template liefertermin is the value, shown in the template control, whose × returns to the date.',
|
|
1526
|
+
},
|
|
1527
|
+
},
|
|
1528
|
+
},
|
|
1529
|
+
async play({ canvasElement }) {
|
|
1530
|
+
const pick = await waitFor(() => {
|
|
1531
|
+
const el = canvasElement.querySelector<HTMLElement>('[aria-label="Use a template"]');
|
|
1532
|
+
expect(el, 'templates offered beside the date').toBeTruthy();
|
|
1533
|
+
return el as HTMLElement;
|
|
1534
|
+
});
|
|
1535
|
+
expect(canvasElement.querySelector('[data-slate-editor]')).toBeNull();
|
|
1536
|
+
await userEvent.click(pick);
|
|
1537
|
+
const item = await waitFor(() => {
|
|
1538
|
+
const el = [...document.querySelectorAll<HTMLElement>('.reqore-popover-content .reqore-menu-item')].find((i) =>
|
|
1539
|
+
i.textContent?.includes('liefertermin')
|
|
1540
|
+
);
|
|
1541
|
+
expect(el).toBeTruthy();
|
|
1542
|
+
return el as HTMLElement;
|
|
1543
|
+
});
|
|
1544
|
+
await userEvent.click(item);
|
|
1545
|
+
await waitFor(() => expect(held(canvasElement).value).toBe('$record:{liefertermin}'));
|
|
1546
|
+
await waitFor(() => expect(canvasElement.textContent).toContain('liefertermin'));
|
|
1547
|
+
},
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
export const WrittenAsTextNumberPicksATemplate: StoryObj<typeof meta> = {
|
|
1551
|
+
args: {
|
|
1552
|
+
type: 'int',
|
|
1553
|
+
allowTemplates: true,
|
|
1554
|
+
allowCustomValues: true,
|
|
1555
|
+
componentFromType: true,
|
|
1556
|
+
templates: recordTemplates as any,
|
|
1557
|
+
},
|
|
1558
|
+
render: WrittenAsText,
|
|
1559
|
+
parameters: {
|
|
1560
|
+
docs: {
|
|
1561
|
+
description: {
|
|
1562
|
+
story:
|
|
1563
|
+
'Renders an empty whole-number field that takes templates. It is typed freely; the template pos chosen from the list its first click opens is the value, a chip in the text field the value is then written in, and text written after it is written around it.',
|
|
1564
|
+
},
|
|
1565
|
+
},
|
|
1566
|
+
},
|
|
1567
|
+
async play({ canvasElement }) {
|
|
1568
|
+
const input = await waitFor(() => {
|
|
1569
|
+
const el = canvasElement.querySelector<HTMLInputElement>('input');
|
|
1570
|
+
expect(el).toBeTruthy();
|
|
1571
|
+
return el as HTMLInputElement;
|
|
1572
|
+
});
|
|
1573
|
+
await userEvent.click(input);
|
|
1574
|
+
const item = await waitFor(() => {
|
|
1575
|
+
const el = [...document.querySelectorAll<HTMLElement>('.reqore-popover-content .reqore-menu-item')].find((i) =>
|
|
1576
|
+
i.textContent?.includes('pos')
|
|
1577
|
+
);
|
|
1578
|
+
expect(el).toBeTruthy();
|
|
1579
|
+
return el as HTMLElement;
|
|
1580
|
+
});
|
|
1581
|
+
await userEvent.click(item);
|
|
1582
|
+
const editor = await textField(canvasElement);
|
|
1583
|
+
await waitFor(() => expect(held(canvasElement)).toEqual({ value: '$record:{pos}', type: 'int' }));
|
|
1584
|
+
await waitFor(() => expect(editor.textContent).toContain('pos'));
|
|
1585
|
+
await sleep(500);
|
|
1586
|
+
// nothing took it away again
|
|
1587
|
+
expect(held(canvasElement)).toEqual({ value: '$record:{pos}', type: 'int' });
|
|
1588
|
+
},
|
|
1589
|
+
};
|