@cosmicdrift/kumiko-renderer 0.155.1 → 0.156.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.156.0",
|
|
4
4
|
"description": "Platform-agnostic React renderer for Kumiko screens. Contains the shared logic — primitives-contract, hooks, KumikoScreen, navigation & SSE abstractions — that any platform-specific renderer (web, native) composes. No DOM, no EventSource, no react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
19
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
18
|
+
"@cosmicdrift/kumiko-framework": "0.156.0",
|
|
19
|
+
"@cosmicdrift/kumiko-headless": "0.156.0",
|
|
20
20
|
"react": "^19.2.6",
|
|
21
21
|
"temporal-polyfill": "^0.3.2"
|
|
22
22
|
},
|
|
@@ -87,6 +87,12 @@ export type KumikoScreenProps = {
|
|
|
87
87
|
// createKumikoApp does exactly that; override to open a drawer,
|
|
88
88
|
// inline-expand, etc.
|
|
89
89
|
readonly onRowClick?: (row: ListRowViewModel, entityName: string) => void;
|
|
90
|
+
// Copy-Link-Action auf entityEdit-Update-Screens (Issue #912). Bereits
|
|
91
|
+
// vollständig gebunden (URL-Bau + Clipboard) — createKumikoApp/RoutedScreen
|
|
92
|
+
// liefert die Default-Impl, dieses platform-neutrale Package baut selbst
|
|
93
|
+
// keine URLs/Clipboard-Calls (guard-renderer-boundaries). Nur in
|
|
94
|
+
// update-mode sichtbar (entityId muss gesetzt sein).
|
|
95
|
+
readonly onCopyLink?: () => Promise<void> | void;
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
// Build the qualified name the registry would stamp on screen ingest:
|
|
@@ -112,6 +118,7 @@ export function KumikoScreen({
|
|
|
112
118
|
translate,
|
|
113
119
|
entityId,
|
|
114
120
|
onRowClick,
|
|
121
|
+
onCopyLink,
|
|
115
122
|
}: KumikoScreenProps): ReactNode {
|
|
116
123
|
const { Banner, Text } = usePrimitives();
|
|
117
124
|
const screen = useMemo(
|
|
@@ -138,6 +145,7 @@ export function KumikoScreen({
|
|
|
138
145
|
screen={screen}
|
|
139
146
|
translate={translate}
|
|
140
147
|
{...(entityId !== undefined && { entityId })}
|
|
148
|
+
{...(onCopyLink !== undefined && { onCopyLink })}
|
|
141
149
|
/>
|
|
142
150
|
);
|
|
143
151
|
case "entityList":
|
|
@@ -306,11 +314,13 @@ function EntityEditScreen({
|
|
|
306
314
|
screen,
|
|
307
315
|
translate,
|
|
308
316
|
entityId,
|
|
317
|
+
onCopyLink,
|
|
309
318
|
}: {
|
|
310
319
|
readonly schema: FeatureSchema;
|
|
311
320
|
readonly screen: EntityEditScreenDefinition;
|
|
312
321
|
readonly translate?: Translate;
|
|
313
322
|
readonly entityId?: string;
|
|
323
|
+
readonly onCopyLink?: () => Promise<void> | void;
|
|
314
324
|
}): ReactNode {
|
|
315
325
|
const { Banner, Text } = usePrimitives();
|
|
316
326
|
const entity = schema.entities[screen.entity];
|
|
@@ -333,6 +343,7 @@ function EntityEditScreen({
|
|
|
333
343
|
entity={entity}
|
|
334
344
|
entityId={entityId}
|
|
335
345
|
{...(translate !== undefined && { translate })}
|
|
346
|
+
{...(onCopyLink !== undefined && { onCopyLink })}
|
|
336
347
|
/>
|
|
337
348
|
);
|
|
338
349
|
}
|
|
@@ -402,12 +413,14 @@ function EntityEditUpdateBody({
|
|
|
402
413
|
entity,
|
|
403
414
|
entityId,
|
|
404
415
|
translate,
|
|
416
|
+
onCopyLink,
|
|
405
417
|
}: {
|
|
406
418
|
readonly schema: FeatureSchema;
|
|
407
419
|
readonly screen: EntityEditScreenDefinition;
|
|
408
420
|
readonly entity: EntityDefinition;
|
|
409
421
|
readonly entityId: string;
|
|
410
422
|
readonly translate?: Translate;
|
|
423
|
+
readonly onCopyLink?: () => Promise<void> | void;
|
|
411
424
|
}): ReactNode {
|
|
412
425
|
const { Banner, Text } = usePrimitives();
|
|
413
426
|
const detailQn = `${toKebab(schema.featureName)}:query:${toKebab(screen.entity)}:detail`;
|
|
@@ -452,6 +465,7 @@ function EntityEditUpdateBody({
|
|
|
452
465
|
record={record}
|
|
453
466
|
onReload={detailQuery.refetch}
|
|
454
467
|
{...(translate !== undefined && { translate })}
|
|
468
|
+
{...(onCopyLink !== undefined && { onCopyLink })}
|
|
455
469
|
/>
|
|
456
470
|
);
|
|
457
471
|
}
|
|
@@ -464,6 +478,7 @@ function EntityEditUpdateForm({
|
|
|
464
478
|
record,
|
|
465
479
|
onReload,
|
|
466
480
|
translate,
|
|
481
|
+
onCopyLink,
|
|
467
482
|
}: {
|
|
468
483
|
readonly schema: FeatureSchema;
|
|
469
484
|
readonly screen: EntityEditScreenDefinition;
|
|
@@ -472,6 +487,7 @@ function EntityEditUpdateForm({
|
|
|
472
487
|
readonly record: Readonly<Record<string, unknown>>;
|
|
473
488
|
readonly onReload: () => Promise<void> | void;
|
|
474
489
|
readonly translate?: Translate;
|
|
490
|
+
readonly onCopyLink?: () => Promise<void> | void;
|
|
475
491
|
}): ReactNode {
|
|
476
492
|
// Seed the form with the server values for the entity's declared
|
|
477
493
|
// fields; anything else (id, tenant_id, created_at…) stays out of
|
|
@@ -547,6 +563,7 @@ function EntityEditUpdateForm({
|
|
|
547
563
|
onReload={() => void onReload()}
|
|
548
564
|
{...(screen.submitLabel !== undefined && { submitLabel: screen.submitLabel })}
|
|
549
565
|
{...(translate !== undefined && { translate })}
|
|
566
|
+
{...(onCopyLink !== undefined && { onCopyLink })}
|
|
550
567
|
/>
|
|
551
568
|
);
|
|
552
569
|
}
|
|
@@ -26,9 +26,11 @@ import type {
|
|
|
26
26
|
EntityEditScreenDefinition,
|
|
27
27
|
ProjectionDetailScreenDefinition,
|
|
28
28
|
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
import {
|
|
30
|
+
isExtensionEditSection,
|
|
31
|
+
normalizeEditField,
|
|
32
|
+
PROJECTION_DETAIL_ENTITY as PROJECTION_DETAIL_PSEUDO_ENTITY,
|
|
33
|
+
} from "@cosmicdrift/kumiko-framework/ui-types";
|
|
32
34
|
|
|
33
35
|
/** Minimale EntityDefinition aus den Layout-Feldern: jedes Feld ein Text-
|
|
34
36
|
* Feld — computeEditViewModel liest nur `fields[<f>].type`, Text reicht für
|
|
@@ -76,6 +76,13 @@ export type RenderEditProps<TValues extends FormValues, TCtx = unknown> = {
|
|
|
76
76
|
readonly onDelete?: () => Promise<void> | void;
|
|
77
77
|
readonly onCancel?: () => void;
|
|
78
78
|
readonly onReload?: () => void;
|
|
79
|
+
/** Copy-Link-Action (Issue #912) — nur in update-mode gesetzt (create-mode
|
|
80
|
+
* hat noch keine entity-id, also keinen Permalink). Der Callback ist
|
|
81
|
+
* bereits vollständig gebunden (URL-Bau + Clipboard passiert außerhalb,
|
|
82
|
+
* in `@cosmicdrift/kumiko-renderer-web`'s RoutedScreen — dieses
|
|
83
|
+
* platform-neutrale Package darf kein `navigator`/`window` anfassen,
|
|
84
|
+
* siehe guard-renderer-boundaries). undefined = kein Button. */
|
|
85
|
+
readonly onCopyLink?: () => Promise<void> | void;
|
|
79
86
|
/** i18n-key für den Submit-Button. Default: "kumiko.actions.save".
|
|
80
87
|
* Action-Forms (Tier 2.7d) übergeben hier ihren screen.submitLabel,
|
|
81
88
|
* damit "Speichern" durch domain-spezifischere Strings ersetzt
|
|
@@ -185,6 +192,7 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
185
192
|
onDelete,
|
|
186
193
|
onCancel,
|
|
187
194
|
onReload,
|
|
195
|
+
onCopyLink,
|
|
188
196
|
submitLabel,
|
|
189
197
|
labelAppendix,
|
|
190
198
|
fieldAppendix,
|
|
@@ -201,6 +209,7 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
201
209
|
const translate = translateProp ?? t;
|
|
202
210
|
|
|
203
211
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
|
212
|
+
const [linkCopied, setLinkCopied] = useState(false);
|
|
204
213
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
205
214
|
const [formError, setFormError] = useState<DispatcherError | null>(null);
|
|
206
215
|
// Composed-Save: Extension-Sections melden hier ihren dirty-State (damit der
|
|
@@ -336,6 +345,19 @@ export function RenderEdit<TValues extends FormValues, TCtx = unknown>(
|
|
|
336
345
|
// gegen Fehlklicks. Save bleibt rechts (primary affordance).
|
|
337
346
|
const formActions = (
|
|
338
347
|
<>
|
|
348
|
+
{onCopyLink !== undefined && (
|
|
349
|
+
<Button
|
|
350
|
+
type="button"
|
|
351
|
+
variant="secondary"
|
|
352
|
+
testId="render-edit-copy-link"
|
|
353
|
+
onClick={async () => {
|
|
354
|
+
await onCopyLink();
|
|
355
|
+
setLinkCopied(true);
|
|
356
|
+
}}
|
|
357
|
+
>
|
|
358
|
+
{translate(linkCopied ? "kumiko.actions.copyLinkCopied" : "kumiko.actions.copyLink")}
|
|
359
|
+
</Button>
|
|
360
|
+
)}
|
|
339
361
|
{onDelete !== undefined && (
|
|
340
362
|
<Button
|
|
341
363
|
type="button"
|
package/src/i18n-defaults.ts
CHANGED
|
@@ -20,6 +20,8 @@ export const kumikoDefaultTranslations: TranslationsByLocale = {
|
|
|
20
20
|
"kumiko.actions.reload": "Neu laden",
|
|
21
21
|
"kumiko.actions.create": "Neu",
|
|
22
22
|
"kumiko.actions.edit": "Bearbeiten",
|
|
23
|
+
"kumiko.actions.copyLink": "Link kopieren",
|
|
24
|
+
"kumiko.actions.copyLinkCopied": "Kopiert!",
|
|
23
25
|
|
|
24
26
|
// Version — Update-Awareness-Banner (UpdateChecker).
|
|
25
27
|
"kumiko.version.update-available": "Eine neue Version ist verfügbar.",
|
|
@@ -155,6 +157,8 @@ export const kumikoDefaultTranslations: TranslationsByLocale = {
|
|
|
155
157
|
"kumiko.actions.reload": "Reload",
|
|
156
158
|
"kumiko.actions.create": "New",
|
|
157
159
|
"kumiko.actions.edit": "Edit",
|
|
160
|
+
"kumiko.actions.copyLink": "Copy link",
|
|
161
|
+
"kumiko.actions.copyLinkCopied": "Copied!",
|
|
158
162
|
|
|
159
163
|
"kumiko.version.update-available": "A new version is available.",
|
|
160
164
|
|