@qoretechnologies/reqraft 0.10.13-pr.74.gf718c08 → 0.10.14
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/.claude/CLAUDE.md +8 -11
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +2 -2
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +15 -3
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +15 -20
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +3 -8
- package/dist/components/form/engine/compactRowStyles.js.map +1 -1
- package/dist/helpers/common.d.ts +0 -5
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +1 -20
- package/dist/helpers/common.js.map +1 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -7
- package/dist/index.js.map +1 -1
- package/dist/providers/StorageProvider.d.ts +13 -0
- package/dist/providers/StorageProvider.d.ts.map +1 -1
- package/dist/providers/StorageProvider.js +29 -5
- package/dist/providers/StorageProvider.js.map +1 -1
- package/package.json +2 -2
- package/src/components/form/engine/CompactToolbar.tsx +117 -110
- package/src/components/form/engine/FormEngine.stories.tsx +27 -0
- package/src/components/form/engine/FormEngine.tsx +44 -25
- package/src/components/form/engine/compactRowStyles.ts +3 -5
- package/src/helpers/common.ts +0 -19
- package/src/hooks/useStorage/useStorage.stories.tsx +52 -0
- package/src/index.tsx +0 -5
- package/src/providers/StorageProvider.tsx +30 -4
- package/dist/components/composer/Composer.d.ts +0 -108
- package/dist/components/composer/Composer.d.ts.map +0 -1
- package/dist/components/composer/Composer.js +0 -238
- package/dist/components/composer/Composer.js.map +0 -1
- package/dist/components/composer/helpers.d.ts +0 -21
- package/dist/components/composer/helpers.d.ts.map +0 -1
- package/dist/components/composer/helpers.js +0 -75
- package/dist/components/composer/helpers.js.map +0 -1
- package/dist/components/composer/index.d.ts +0 -3
- package/dist/components/composer/index.d.ts.map +0 -1
- package/dist/components/composer/index.js +0 -19
- package/dist/components/composer/index.js.map +0 -1
- package/dist/components/imageLightbox/ImageLightbox.d.ts +0 -17
- package/dist/components/imageLightbox/ImageLightbox.d.ts.map +0 -1
- package/dist/components/imageLightbox/ImageLightbox.js +0 -129
- package/dist/components/imageLightbox/ImageLightbox.js.map +0 -1
- package/dist/components/imageLightbox/index.d.ts +0 -2
- package/dist/components/imageLightbox/index.d.ts.map +0 -1
- package/dist/components/imageLightbox/index.js +0 -18
- package/dist/components/imageLightbox/index.js.map +0 -1
- package/dist/components/supportTicket/AttachmentChips.d.ts +0 -10
- package/dist/components/supportTicket/AttachmentChips.d.ts.map +0 -1
- package/dist/components/supportTicket/AttachmentChips.js +0 -119
- package/dist/components/supportTicket/AttachmentChips.js.map +0 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +0 -35
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +0 -1
- package/dist/components/supportTicket/InterfaceReferenceTags.js +0 -31
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +0 -1
- package/dist/components/supportTicket/ReferencePicker.d.ts +0 -41
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +0 -1
- package/dist/components/supportTicket/ReferencePicker.js +0 -86
- package/dist/components/supportTicket/ReferencePicker.js.map +0 -1
- package/dist/components/supportTicket/TicketHeader.d.ts +0 -86
- package/dist/components/supportTicket/TicketHeader.d.ts.map +0 -1
- package/dist/components/supportTicket/TicketHeader.js +0 -149
- package/dist/components/supportTicket/TicketHeader.js.map +0 -1
- package/dist/components/supportTicket/TicketMetaTags.d.ts +0 -32
- package/dist/components/supportTicket/TicketMetaTags.d.ts.map +0 -1
- package/dist/components/supportTicket/TicketMetaTags.js +0 -21
- package/dist/components/supportTicket/TicketMetaTags.js.map +0 -1
- package/dist/components/supportTicket/TicketReplyBox.d.ts +0 -63
- package/dist/components/supportTicket/TicketReplyBox.d.ts.map +0 -1
- package/dist/components/supportTicket/TicketReplyBox.js +0 -115
- package/dist/components/supportTicket/TicketReplyBox.js.map +0 -1
- package/dist/components/supportTicket/fileToAttachment.d.ts +0 -9
- package/dist/components/supportTicket/fileToAttachment.d.ts.map +0 -1
- package/dist/components/supportTicket/fileToAttachment.js +0 -28
- package/dist/components/supportTicket/fileToAttachment.js.map +0 -1
- package/dist/components/supportTicket/index.d.ts +0 -9
- package/dist/components/supportTicket/index.d.ts.map +0 -1
- package/dist/components/supportTicket/index.js +0 -25
- package/dist/components/supportTicket/index.js.map +0 -1
- package/dist/components/supportTicket/meta.d.ts +0 -63
- package/dist/components/supportTicket/meta.d.ts.map +0 -1
- package/dist/components/supportTicket/meta.js +0 -79
- package/dist/components/supportTicket/meta.js.map +0 -1
- package/dist/components/ticketReferences/TicketReferences.d.ts +0 -52
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +0 -1
- package/dist/components/ticketReferences/TicketReferences.js +0 -453
- package/dist/components/ticketReferences/TicketReferences.js.map +0 -1
- package/dist/components/ticketReferences/index.d.ts +0 -3
- package/dist/components/ticketReferences/index.d.ts.map +0 -1
- package/dist/components/ticketReferences/index.js +0 -6
- package/dist/components/ticketReferences/index.js.map +0 -1
- package/dist/components/ticketThread/TicketThread.d.ts +0 -52
- package/dist/components/ticketThread/TicketThread.d.ts.map +0 -1
- package/dist/components/ticketThread/TicketThread.js +0 -248
- package/dist/components/ticketThread/TicketThread.js.map +0 -1
- package/dist/components/ticketThread/index.d.ts +0 -3
- package/dist/components/ticketThread/index.d.ts.map +0 -1
- package/dist/components/ticketThread/index.js +0 -7
- package/dist/components/ticketThread/index.js.map +0 -1
- package/dist/helpers/scrollbar.d.ts +0 -2
- package/dist/helpers/scrollbar.d.ts.map +0 -1
- package/dist/helpers/scrollbar.js +0 -25
- package/dist/helpers/scrollbar.js.map +0 -1
- package/src/components/composer/Composer.stories.tsx +0 -238
- package/src/components/composer/Composer.tsx +0 -545
- package/src/components/composer/helpers.ts +0 -74
- package/src/components/composer/index.ts +0 -2
- package/src/components/imageLightbox/ImageLightbox.stories.tsx +0 -156
- package/src/components/imageLightbox/ImageLightbox.tsx +0 -169
- package/src/components/imageLightbox/index.ts +0 -1
- package/src/components/supportTicket/AttachmentChips.stories.tsx +0 -122
- package/src/components/supportTicket/AttachmentChips.tsx +0 -142
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +0 -134
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +0 -85
- package/src/components/supportTicket/ReferencePicker.stories.tsx +0 -296
- package/src/components/supportTicket/ReferencePicker.tsx +0 -237
- package/src/components/supportTicket/TicketConversation.stories.tsx +0 -122
- package/src/components/supportTicket/TicketHeader.stories.tsx +0 -183
- package/src/components/supportTicket/TicketHeader.tsx +0 -247
- package/src/components/supportTicket/TicketMetaTags.stories.tsx +0 -103
- package/src/components/supportTicket/TicketMetaTags.tsx +0 -85
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +0 -355
- package/src/components/supportTicket/TicketReplyBox.tsx +0 -146
- package/src/components/supportTicket/fileToAttachment.ts +0 -24
- package/src/components/supportTicket/index.ts +0 -8
- package/src/components/supportTicket/meta.ts +0 -145
- package/src/components/ticketReferences/TicketReferences.stories.tsx +0 -301
- package/src/components/ticketReferences/TicketReferences.tsx +0 -899
- package/src/components/ticketReferences/index.ts +0 -2
- package/src/components/ticketThread/TicketThread.stories.tsx +0 -751
- package/src/components/ticketThread/TicketThread.tsx +0 -650
- package/src/components/ticketThread/index.ts +0 -6
- package/src/helpers/scrollbar.ts +0 -32
|
@@ -21,13 +21,13 @@ import {
|
|
|
21
21
|
} from '@qoretechnologies/reqore/dist/components/Panel';
|
|
22
22
|
import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
23
23
|
import { TReqoreIntent } from '@qoretechnologies/reqore/dist/constants/theme';
|
|
24
|
-
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
25
24
|
import {
|
|
26
25
|
changeDarkness,
|
|
27
26
|
getMainBackgroundColor,
|
|
28
27
|
getReadableColor,
|
|
29
28
|
percentToHexAlpha,
|
|
30
29
|
} from '@qoretechnologies/reqore/dist/helpers/colors';
|
|
30
|
+
import { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
31
31
|
import {
|
|
32
32
|
IQorusFormField,
|
|
33
33
|
IQorusFormFieldMessage,
|
|
@@ -48,11 +48,10 @@ import reduce from 'lodash/reduce';
|
|
|
48
48
|
import size from 'lodash/size';
|
|
49
49
|
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
50
50
|
import { useMeasure, useMount, useUpdateEffect } from 'react-use';
|
|
51
|
-
import { createContext } from 'use-context-selector';
|
|
52
51
|
import styled from 'styled-components';
|
|
52
|
+
import { createContext } from 'use-context-selector';
|
|
53
53
|
import { getDefaultValue, insertAtIndex, richtextToString } from '../../../helpers/common';
|
|
54
54
|
import { getRequiredOptionMessage } from '../../../helpers/options';
|
|
55
|
-
import { query } from '../../../utils/fetch';
|
|
56
55
|
import {
|
|
57
56
|
IValidationResult,
|
|
58
57
|
hasAllDependenciesFullfilled,
|
|
@@ -61,6 +60,7 @@ import {
|
|
|
61
60
|
} from '../../../helpers/validations';
|
|
62
61
|
import { useQorusTypes } from '../../../hooks/useQorusTypes';
|
|
63
62
|
import { useTemplates } from '../../../hooks/useTemplates';
|
|
63
|
+
import { query } from '../../../utils/fetch';
|
|
64
64
|
import { Description } from '../../Description';
|
|
65
65
|
import { FocusedEditing } from '../../FocusedEditing';
|
|
66
66
|
import { SelectFormField } from '../fields/select/Select';
|
|
@@ -72,12 +72,6 @@ import {
|
|
|
72
72
|
} from '../fields/template/TemplateField';
|
|
73
73
|
import { CompactRow } from './CompactRow';
|
|
74
74
|
import { CompactRowContext, ICompactRowContext } from './compactRowContext';
|
|
75
|
-
import { CompactToolbar } from './CompactToolbar';
|
|
76
|
-
import {
|
|
77
|
-
CompactToolbarContext,
|
|
78
|
-
ICompactToolbarContext,
|
|
79
|
-
TCompactSort,
|
|
80
|
-
} from './compactToolbarContext';
|
|
81
75
|
import {
|
|
82
76
|
GROUP_INDENT,
|
|
83
77
|
LABEL_COL_MAX,
|
|
@@ -91,9 +85,16 @@ import {
|
|
|
91
85
|
StyledStatusBox,
|
|
92
86
|
StyledStatusBoxGroupLabel,
|
|
93
87
|
} from './compactRowStyles';
|
|
88
|
+
import { CompactToolbar } from './CompactToolbar';
|
|
89
|
+
import {
|
|
90
|
+
CompactToolbarContext,
|
|
91
|
+
ICompactToolbarContext,
|
|
92
|
+
TCompactSort,
|
|
93
|
+
} from './compactToolbarContext';
|
|
94
94
|
import { OptionFieldMessages } from './OptionFieldMessages';
|
|
95
95
|
import { OptionsHelpDialog } from './OptionsHelpDialog';
|
|
96
96
|
import {
|
|
97
|
+
TReadFirstStatus,
|
|
97
98
|
getFirstAttentionOptionName,
|
|
98
99
|
getOptionGroup,
|
|
99
100
|
getOptionGroupLabel,
|
|
@@ -101,7 +102,6 @@ import {
|
|
|
101
102
|
getReadFirstCompletion,
|
|
102
103
|
getReadFirstStatus,
|
|
103
104
|
isOptionValueEmpty,
|
|
104
|
-
TReadFirstStatus,
|
|
105
105
|
} from './readFirst';
|
|
106
106
|
|
|
107
107
|
// Re-export types for consumers
|
|
@@ -201,13 +201,6 @@ const StyledCompactWrap = styled.div<{ $flush?: boolean }>`
|
|
|
201
201
|
engage instead of overflowing the container horizontally. */
|
|
202
202
|
min-width: 0;
|
|
203
203
|
max-width: 100%;
|
|
204
|
-
/* A bit of horizontal breathing room for the whole form (header + content
|
|
205
|
-
alike). Horizontal only — top padding would break the sticky toolbar's
|
|
206
|
-
flush pin (see the scroll-context note below). Dropped to flush via the
|
|
207
|
-
compactFlush prop, for embeds that own their own gutters (e.g. the
|
|
208
|
-
SchemaDefinition tab body). */
|
|
209
|
-
padding: ${({ $flush }) => ($flush ? '0' : '0 12px')};
|
|
210
|
-
|
|
211
204
|
/* Own our scroll context instead of borrowing the host's. The sticky toolbar
|
|
212
205
|
pins to whatever scrolls; if that scroller carries top padding (e.g. a
|
|
213
206
|
ReqorePanel/ReqoreContent body), sticky \`top: 0\` resolves against its
|
|
@@ -525,6 +518,18 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
|
|
|
525
518
|
* transparently inside the parent's edit card. Default `false`.
|
|
526
519
|
*/
|
|
527
520
|
compactNested?: boolean;
|
|
521
|
+
/**
|
|
522
|
+
* Compact mode only: props forwarded to the read-first form's outer
|
|
523
|
+
* `ReqorePanel` — the panel that carries the sticky toolbar header and holds
|
|
524
|
+
* the status boxes. Spread AFTER the engine's own defaults, so anything set
|
|
525
|
+
* here wins (`flat`, `raised`, `minimal`, `stickyHeader`, `label`, `icon`,
|
|
526
|
+
* `intent`, `padded`, `size`, …). Two props merge instead of replacing, so a
|
|
527
|
+
* consumer can add to the panel without dismantling the form:
|
|
528
|
+
* - `actions` — appended after the engine's toolbar action.
|
|
529
|
+
* - `contentStyle` — merged over the engine's flex-column layout.
|
|
530
|
+
* No-op in classic (non-compact) mode.
|
|
531
|
+
*/
|
|
532
|
+
compactPanelProps?: IReqorePanelProps;
|
|
528
533
|
/** Compact mode only: per-group display metadata (label / icon / subtitle /
|
|
529
534
|
* order) — the server only sends the bare group key. */
|
|
530
535
|
groups?: Record<string, IFormEngineGroup>;
|
|
@@ -609,6 +614,7 @@ export const FormEngine = ({
|
|
|
609
614
|
compact,
|
|
610
615
|
compactFlush = false,
|
|
611
616
|
compactNested = false,
|
|
617
|
+
compactPanelProps,
|
|
612
618
|
commitMode = 'immediate',
|
|
613
619
|
expandMode = 'single',
|
|
614
620
|
onCommit,
|
|
@@ -1709,8 +1715,13 @@ export const FormEngine = ({
|
|
|
1709
1715
|
<>
|
|
1710
1716
|
{(() => {
|
|
1711
1717
|
const schemaMsgs = (
|
|
1712
|
-
suppressSchemaMessages ?
|
|
1713
|
-
|
|
1718
|
+
suppressSchemaMessages ?
|
|
1719
|
+
[]
|
|
1720
|
+
: (options?.[optionName] as any)?.messages || []) as {
|
|
1721
|
+
intent?: string;
|
|
1722
|
+
title?: string;
|
|
1723
|
+
content?: string;
|
|
1724
|
+
}[];
|
|
1714
1725
|
if (!schemaMsgs.length) return null;
|
|
1715
1726
|
const items = schemaMsgs.map(({ intent, title, content }, index) => (
|
|
1716
1727
|
<ReqoreMessage
|
|
@@ -2114,7 +2125,13 @@ export const FormEngine = ({
|
|
|
2114
2125
|
]
|
|
2115
2126
|
);
|
|
2116
2127
|
|
|
2117
|
-
|
|
2128
|
+
// The engine's own toolbar action, plus whatever the consumer added through
|
|
2129
|
+
// `compactPanelProps.actions` — appended, so an outside action can never
|
|
2130
|
+
// knock the form's toolbar out of the header.
|
|
2131
|
+
const compactHeaderActions = useMemo(
|
|
2132
|
+
() => [{ as: CompactToolbar, responsive: false }, ...(compactPanelProps?.actions || [])],
|
|
2133
|
+
[compactPanelProps?.actions]
|
|
2134
|
+
);
|
|
2118
2135
|
const renderCompact = () => {
|
|
2119
2136
|
const headerBg = `${changeDarkness(getMainBackgroundColor(theme), 0.02)}${percentToHexAlpha(88)}`;
|
|
2120
2137
|
// Toolbar filters narrow the listed rows; the meter reflects the full set.
|
|
@@ -2357,22 +2374,24 @@ export const FormEngine = ({
|
|
|
2357
2374
|
$headerBg={compactNested ? 'transparent' : headerBg}
|
|
2358
2375
|
$nested={compactNested}
|
|
2359
2376
|
flat
|
|
2377
|
+
raised
|
|
2378
|
+
minimal
|
|
2360
2379
|
// No panel background: the form sits transparently on whatever
|
|
2361
2380
|
// hosts it (page, drawer, or — for an arg_schema field — the
|
|
2362
2381
|
// parent's edit card) instead of stacking its own dark surface.
|
|
2363
2382
|
// The status boxes keep their own tints; the sticky toolbar keeps
|
|
2364
2383
|
// its blurred header via the $headerBg override.
|
|
2365
|
-
transparent
|
|
2366
2384
|
stickyHeader={!compactNested}
|
|
2367
|
-
|
|
2385
|
+
// Consumer overrides land last so they win over every default
|
|
2386
|
+
// above; `actions` and `contentStyle` below merge rather than
|
|
2387
|
+
// replace (see `compactPanelProps`).
|
|
2388
|
+
{...compactPanelProps}
|
|
2368
2389
|
actions={compactHeaderActions}
|
|
2369
2390
|
contentStyle={{
|
|
2370
2391
|
display: 'flex',
|
|
2371
2392
|
flexFlow: 'column',
|
|
2372
2393
|
gap: '10px',
|
|
2373
|
-
|
|
2374
|
-
// the parent card); top-level keeps a small bottom gutter.
|
|
2375
|
-
padding: compactNested ? '0' : '0 0 12px',
|
|
2394
|
+
...compactPanelProps?.contentStyle,
|
|
2376
2395
|
}}
|
|
2377
2396
|
>
|
|
2378
2397
|
{size(groupKeys) === 0 ?
|
|
@@ -47,14 +47,13 @@ export const StyledCompactPanel = styled(ReqorePanel)<{
|
|
|
47
47
|
$nested?: boolean;
|
|
48
48
|
}>`
|
|
49
49
|
> .reqore-panel-title {
|
|
50
|
-
background: ${({ $headerBg }) => $headerBg};
|
|
51
50
|
/* The blur + translateZ exist only to make the STICKY top-level toolbar ghost
|
|
52
51
|
content beneath it; a nested sub-form's header isn't sticky, so skip them
|
|
53
52
|
(and the stacking context translateZ creates). */
|
|
54
53
|
${({ $nested }) =>
|
|
55
|
-
$nested ?
|
|
56
|
-
''
|
|
57
|
-
|
|
54
|
+
$nested ? '' : (
|
|
55
|
+
'backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); transform: translateZ(0);'
|
|
56
|
+
)}
|
|
58
57
|
padding-top: ${GAP_FROM_SIZE[HEADER_GAP]}px;
|
|
59
58
|
padding-bottom: ${GAP_FROM_SIZE[HEADER_GAP]}px;
|
|
60
59
|
}
|
|
@@ -625,5 +624,4 @@ export const StyledGroupBody = styled.div<{
|
|
|
625
624
|
padding-top: 0;
|
|
626
625
|
padding-bottom: 0;
|
|
627
626
|
}
|
|
628
|
-
|
|
629
627
|
`;
|
package/src/helpers/common.ts
CHANGED
|
@@ -174,25 +174,6 @@ export const splitByteSize = (value?: string): [number | undefined, string | und
|
|
|
174
174
|
return [amount?.[0] ? Number(amount[0]) : undefined, unit?.[0]];
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
-
/** Human-readable file size, e.g. `"12.3 KB"` — whole bytes below 1 KiB, one
|
|
178
|
-
* decimal above. Shared by the ticket thread / references attachment rows. */
|
|
179
|
-
export const formatBytes = (bytes: number): string => {
|
|
180
|
-
if (bytes < 1024) {
|
|
181
|
-
return `${bytes} B`;
|
|
182
|
-
}
|
|
183
|
-
const units = ['KB', 'MB', 'GB'];
|
|
184
|
-
let value = bytes / 1024;
|
|
185
|
-
let unit = 0;
|
|
186
|
-
while (value >= 1024 && unit < units.length - 1) {
|
|
187
|
-
value /= 1024;
|
|
188
|
-
unit += 1;
|
|
189
|
-
}
|
|
190
|
-
return `${value.toFixed(1)} ${units[unit]}`;
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
/** True when a MIME content type is an image (`image/*`). */
|
|
194
|
-
export const isImage = (contentType: string): boolean => contentType.startsWith('image/');
|
|
195
|
-
|
|
196
177
|
// URL value splitting (IDE `urlField.tsx` behavior). Lives here rather than
|
|
197
178
|
// in the Url component so `validations.ts` can use it without pulling the
|
|
198
179
|
// component chain into non-DOM consumers (jest).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReqoreButton, ReqoreControlGroup, ReqoreP } from '@qoretechnologies/reqore';
|
|
2
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { useRef } from 'react';
|
|
3
4
|
import { fireEvent, within } from 'storybook/test';
|
|
4
5
|
import { storiesStorageMock } from '../../../__tests__/ mock';
|
|
5
6
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
@@ -93,6 +94,57 @@ export const ValueCanBeUpdated: Story = {
|
|
|
93
94
|
},
|
|
94
95
|
};
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Regression for the "stale updater clobbers other keys" bug: the storage
|
|
99
|
+
* updater must persist onto the LATEST storage blob, not the one captured in
|
|
100
|
+
* its closure. Long-lived callers (an imperative store subscription, a ref to
|
|
101
|
+
* the mount-time setter) hold an old updater; writing through it must not wipe
|
|
102
|
+
* keys other hooks wrote in the meantime.
|
|
103
|
+
*/
|
|
104
|
+
export const StaleUpdaterDoesNotClobberOtherKeys: Story = {
|
|
105
|
+
args: { reqraftOptions: { waitForStorage: true } },
|
|
106
|
+
parameters: {
|
|
107
|
+
docs: {
|
|
108
|
+
description: {
|
|
109
|
+
story:
|
|
110
|
+
"Renders two useReqraftStorage keys and captures the mount-time setter for the first one (a deliberately STALE updater). After a second key is written, writing through the stale updater must keep the second key intact — proving storage writes merge onto the latest blob rather than a closure copy. This is the regression test for page-visit tracking wiping other stored settings.",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
mockData: [...storiesStorageMock],
|
|
114
|
+
},
|
|
115
|
+
render: () => {
|
|
116
|
+
const [someVal, setSomeVal] = useReqraftStorage<string>('some-path', 'default');
|
|
117
|
+
const [keyB] = useReqraftStorage<string>('key-b', 'b-default');
|
|
118
|
+
const [, setKeyB] = useReqraftStorage<string>('key-b', 'b-default');
|
|
119
|
+
// Capture the mount-time setter — it stays stale once other keys change.
|
|
120
|
+
const staleSetSomeVal = useRef(setSomeVal);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<ReqoreControlGroup vertical>
|
|
124
|
+
<ReqoreP>{`some-path: ${someVal}`}</ReqoreP>
|
|
125
|
+
<ReqoreP>{`key-b: ${keyB}`}</ReqoreP>
|
|
126
|
+
<ReqoreButton onClick={() => setKeyB('b-written')}>Write B</ReqoreButton>
|
|
127
|
+
<ReqoreButton onClick={() => staleSetSomeVal.current('a-written')}>
|
|
128
|
+
Write A (stale)
|
|
129
|
+
</ReqoreButton>
|
|
130
|
+
</ReqoreControlGroup>
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
play: async ({ canvasElement }) => {
|
|
134
|
+
const canvas = within(canvasElement);
|
|
135
|
+
|
|
136
|
+
await testsWaitForText('some-path: This is a storage value');
|
|
137
|
+
// Write a second key through its own (fresh) updater.
|
|
138
|
+
await fireEvent.click(canvas.getByText('Write B'));
|
|
139
|
+
await testsWaitForText('key-b: b-written');
|
|
140
|
+
// Write the first key through the STALE mount-time updater.
|
|
141
|
+
await fireEvent.click(canvas.getByText('Write A (stale)'));
|
|
142
|
+
await testsWaitForText('some-path: a-written');
|
|
143
|
+
// The stale write must NOT have wiped the second key.
|
|
144
|
+
await testsWaitForText('key-b: b-written');
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
96
148
|
export const ValueCanBeRemoved: Story = {
|
|
97
149
|
...StorageValue,
|
|
98
150
|
parameters: {
|
package/src/index.tsx
CHANGED
|
@@ -10,8 +10,6 @@ export {
|
|
|
10
10
|
validateOptionWithRequiredGroups,
|
|
11
11
|
} from './helpers/validations';
|
|
12
12
|
export type { IValidationResult } from './helpers/validations';
|
|
13
|
-
export { thinScrollbar } from './helpers/scrollbar';
|
|
14
|
-
export * from './components/imageLightbox';
|
|
15
13
|
export { ReqraftMenu } from './components/menu/Menu';
|
|
16
14
|
export type {
|
|
17
15
|
IReqraftMenuItem,
|
|
@@ -54,9 +52,6 @@ export type {
|
|
|
54
52
|
ILspTextEdit,
|
|
55
53
|
TLspDocumentText,
|
|
56
54
|
} from './utils/lspClient.types';
|
|
57
|
-
export * from './components/composer';
|
|
58
55
|
export * from './components/smartEditor';
|
|
59
56
|
export * from './components/dpqlEditor';
|
|
60
57
|
export * from './components/qonsoleSmartInput';
|
|
61
|
-
export * from './components/supportTicket';
|
|
62
|
-
export * from './components/ticketThread';
|
|
@@ -9,6 +9,23 @@ import { TReqraftStorageValue } from '../hooks/useStorage/useStorage';
|
|
|
9
9
|
import { currentUserStore } from '../stores/currentUser/currentUser';
|
|
10
10
|
import { IReqraftProviderProps } from './ReqraftProvider';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Apply a single path write onto the current storage blob, seeding a fresh object
|
|
14
|
+
* when the user has no storage yet.
|
|
15
|
+
*
|
|
16
|
+
* `?? {}` is load-bearing: a user who has never written storage has
|
|
17
|
+
* `storage === undefined | null`, and `set(cloneDeep(undefined), …)` returns
|
|
18
|
+
* `undefined` (lodash won't set on a nullish target). Without the seed the FIRST
|
|
19
|
+
* write for such a user — e.g. accepting the cookie-consent banner — persists an
|
|
20
|
+
* empty body and silently drops the value, so the flag stays unset forever and
|
|
21
|
+
* the banner never closes. Exported for testing.
|
|
22
|
+
*/
|
|
23
|
+
export const applyStorageWrite = (
|
|
24
|
+
storage: TReqraftStorage | undefined,
|
|
25
|
+
path: string,
|
|
26
|
+
value: unknown
|
|
27
|
+
): TReqraftStorage => set(cloneDeep(storage) ?? {}, path, value);
|
|
28
|
+
|
|
12
29
|
export interface IReqraftStorageProviderProps
|
|
13
30
|
extends Pick<IReqraftProviderProps, 'waitForStorage'> {
|
|
14
31
|
children: ReactNode;
|
|
@@ -53,26 +70,35 @@ export const ReqraftUserProvider = ({ children, waitForStorage }: IReqraftStorag
|
|
|
53
70
|
includeAppPrefix: boolean = true
|
|
54
71
|
) {
|
|
55
72
|
const _path = includeAppPrefix ? `${appName}.${path}` : path;
|
|
56
|
-
|
|
73
|
+
// Base the write on the LATEST storage read straight from the store, not
|
|
74
|
+
// the blob captured in this callback's closure. A caller holding a stale
|
|
75
|
+
// updater — e.g. an imperative store subscription created on mount, before
|
|
76
|
+
// other keys had loaded — would otherwise persist an out-of-date blob and
|
|
77
|
+
// wipe every key written to storage since its closure was captured.
|
|
78
|
+
const latestStorage = currentUserStore.getState().currentUser?.storage;
|
|
79
|
+
const updatedStorage = applyStorageWrite(latestStorage, _path, value);
|
|
57
80
|
|
|
58
81
|
updateCurrentUserStorage(updatedStorage);
|
|
59
82
|
|
|
60
83
|
load({ body: { storage: updatedStorage } });
|
|
61
84
|
},
|
|
62
|
-
[appName,
|
|
85
|
+
[appName, load, updateCurrentUserStorage]
|
|
63
86
|
);
|
|
64
87
|
|
|
65
88
|
const removeStorageValue = useCallback(
|
|
66
89
|
function (path: string, includeAppPrefix: boolean = true) {
|
|
67
90
|
const _path = includeAppPrefix ? `${appName}.${path}` : path;
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
// Same as `updateStorage`: mutate the LATEST blob (never a stale closure
|
|
93
|
+
// copy), seeding a fresh object when there's no storage yet.
|
|
94
|
+
const latestStorage = currentUserStore.getState().currentUser?.storage;
|
|
95
|
+
const updatedStorage = applyStorageWrite(latestStorage, _path, null);
|
|
70
96
|
|
|
71
97
|
updateCurrentUserStorage(updatedStorage);
|
|
72
98
|
|
|
73
99
|
load({ body: { storage_path: _path } });
|
|
74
100
|
},
|
|
75
|
-
[appName,
|
|
101
|
+
[appName, load, updateCurrentUserStorage]
|
|
76
102
|
);
|
|
77
103
|
|
|
78
104
|
const contextValue = useMemo(
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { ReqoreButton, ReqoreDropdown, ReqoreTextarea } from '@qoretechnologies/reqore';
|
|
2
|
-
import { StyledEffect } from '@qoretechnologies/reqore/dist/components/Effect';
|
|
3
|
-
import type { IReqoreIconName } from '@qoretechnologies/reqore/dist/types/icons';
|
|
4
|
-
import { ComponentProps, KeyboardEvent as ReactKeyboardEvent, ReactNode } from 'react';
|
|
5
|
-
/** Reqore's intent / effect / theme unions, without guessing import paths. */
|
|
6
|
-
type TIntent = ComponentProps<typeof ReqoreButton>['intent'];
|
|
7
|
-
type TEffect = ComponentProps<typeof StyledEffect>['effect'];
|
|
8
|
-
type TCustomTheme = ComponentProps<typeof ReqoreButton>['customTheme'];
|
|
9
|
-
/** The exact `items` shape ReqoreDropdown accepts. */
|
|
10
|
-
type TDropdownItems = NonNullable<ComponentProps<typeof ReqoreDropdown>['items']>;
|
|
11
|
-
/** Which editor the composer renders. */
|
|
12
|
-
export type TComposerEditor = 'rich' | 'plain';
|
|
13
|
-
/** One send action. With more than one, the send button splits into a primary
|
|
14
|
-
* button (the first action) + a caret dropdown listing them all. `id` is echoed
|
|
15
|
-
* back to `onSend` so the caller knows which action fired. */
|
|
16
|
-
export interface IComposerSendAction {
|
|
17
|
-
id?: string;
|
|
18
|
-
/** button label; omit for an icon-only button (like the Qonsole send). */
|
|
19
|
-
label?: string;
|
|
20
|
-
icon?: IReqoreIconName;
|
|
21
|
-
intent?: TIntent;
|
|
22
|
-
/** per-button Reqore theme (e.g. `{ main: 'custom1' }` for the Qonsole accent). */
|
|
23
|
-
customTheme?: TCustomTheme;
|
|
24
|
-
/** render the button minimal (subtle, blends into the bar — the Qonsole style). */
|
|
25
|
-
minimal?: boolean;
|
|
26
|
-
}
|
|
27
|
-
/** Imperative handle for consumers that drive the editor externally (e.g. the
|
|
28
|
-
* Qonsole input inserting a completion or navigating history). */
|
|
29
|
-
export interface IComposerHandle {
|
|
30
|
-
focus: () => void;
|
|
31
|
-
clear: () => void;
|
|
32
|
-
setText: (text: string) => void;
|
|
33
|
-
getText: () => string;
|
|
34
|
-
}
|
|
35
|
-
export interface IComposerProps {
|
|
36
|
-
/** Editor flavour. `rich` = ReqoreRichTextEditor (default), `plain` = ReqoreTextarea. */
|
|
37
|
-
editor?: TComposerEditor;
|
|
38
|
-
/** Initial draft text (also seeds the editor for a restored draft / a story). */
|
|
39
|
-
defaultText?: string;
|
|
40
|
-
/** Initial staged files (a restored draft's attachments, or a demo). */
|
|
41
|
-
defaultFiles?: File[];
|
|
42
|
-
placeholder?: string;
|
|
43
|
-
/**
|
|
44
|
-
* Submit the composed message. `text` is the plain-string body; `files` are the
|
|
45
|
-
* staged attachments (raw `File`s — the caller converts to its own upload shape);
|
|
46
|
-
* `action` is the chosen send action's `id` (undefined for the default button).
|
|
47
|
-
*/
|
|
48
|
-
onSend: (text: string, options: {
|
|
49
|
-
files: File[];
|
|
50
|
-
action?: string;
|
|
51
|
-
}) => void | Promise<void>;
|
|
52
|
-
/** Single-button label / icon / intent (ignored when `sendActions` is set). */
|
|
53
|
-
sendLabel?: string;
|
|
54
|
-
sendIcon?: IReqoreIconName;
|
|
55
|
-
sendIntent?: TIntent;
|
|
56
|
-
/** Default send button: minimal (subtle, Qonsole-style). Default `true`. */
|
|
57
|
-
sendMinimal?: boolean;
|
|
58
|
-
/** Default send button accent (Reqore `customTheme`). Default `{ main: 'custom1' }`
|
|
59
|
-
* — the Qorus accent, which each app's theme colours (gray in a bare reqore theme). */
|
|
60
|
-
sendCustomTheme?: TCustomTheme;
|
|
61
|
-
/** Multiple send actions → a split send button. The first is the primary. */
|
|
62
|
-
sendActions?: IComposerSendAction[];
|
|
63
|
-
/** className on the split-send caret dropdown (keeps consumer test hooks, e.g.
|
|
64
|
-
* the helpdesk `ticket-send-more`). */
|
|
65
|
-
sendMenuClassName?: string;
|
|
66
|
-
/** the send round-trip is in flight */
|
|
67
|
-
sending?: boolean;
|
|
68
|
-
/** disabled → renders a muted notice with `disabledReason` instead of the composer */
|
|
69
|
-
disabled?: boolean;
|
|
70
|
-
disabledReason?: string;
|
|
71
|
-
/** submit on plain Enter (Shift/Ctrl/Alt/Meta+Enter insert a newline). Default true. */
|
|
72
|
-
submitOnEnter?: boolean;
|
|
73
|
-
/** clear the editor + staged files after a resolved send. Default true. */
|
|
74
|
-
clearOnSend?: boolean;
|
|
75
|
-
/** allow sending with empty text (e.g. attachments only). Default false. */
|
|
76
|
-
allowEmptySend?: boolean;
|
|
77
|
-
/** Extra attach-menu entries beyond "Upload files" (receives `addFiles`). With any
|
|
78
|
-
* entry the attach control becomes a dropdown; otherwise a plain button. */
|
|
79
|
-
attachMenuItems?: (addFiles: (files: File[]) => void) => TDropdownItems;
|
|
80
|
-
/** the attach control icon (Qonsole uses `AddLine`). */
|
|
81
|
-
attachIcon?: IReqoreIconName;
|
|
82
|
-
/** hide the attach control entirely. */
|
|
83
|
-
hideAttach?: boolean;
|
|
84
|
-
/** Slots: `toolbarStart` sits after attach on the left (e.g. the Qonsole slash
|
|
85
|
-
* dropdown), `footerActions` after it (e.g. a reference toggle), `rightActions`
|
|
86
|
-
* left of send (e.g. the Qonsole design button), `aboveInput` on top of the
|
|
87
|
-
* editor inside the bar (e.g. reference chips), and `belowInput` under the bar. */
|
|
88
|
-
toolbarStart?: ReactNode;
|
|
89
|
-
footerActions?: ReactNode;
|
|
90
|
-
rightActions?: ReactNode;
|
|
91
|
-
aboveInput?: ReactNode;
|
|
92
|
-
belowInput?: ReactNode;
|
|
93
|
-
/** Hooks for consumers layering behaviour on the editor (completions, history). */
|
|
94
|
-
onChange?: (text: string) => void;
|
|
95
|
-
onEditorKeyDown?: (e: ReactKeyboardEvent) => void;
|
|
96
|
-
onFocus?: () => void;
|
|
97
|
-
onBlur?: () => void;
|
|
98
|
-
/** editor sizing */
|
|
99
|
-
size?: ComponentProps<typeof ReqoreTextarea>['size'];
|
|
100
|
-
/** drop the composer's outer margin (for tight embeds). */
|
|
101
|
-
minimal?: boolean;
|
|
102
|
-
/** override the bar's gradient / effect (qorus-ide passes its `custom1` accent to
|
|
103
|
-
* match the Qonsole floating bar exactly). */
|
|
104
|
-
effect?: TEffect;
|
|
105
|
-
}
|
|
106
|
-
export declare const Composer: import("react").ForwardRefExoticComponent<IComposerProps & import("react").RefAttributes<IComposerHandle>>;
|
|
107
|
-
export {};
|
|
108
|
-
//# sourceMappingURL=Composer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Composer.d.ts","sourceRoot":"","sources":["../../../src/components/composer/Composer.tsx"],"names":[],"mappings":"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"}
|