@cosmicdrift/kumiko-renderer-web 0.237.0 → 0.237.2
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-web",
|
|
3
|
-
"version": "0.237.
|
|
3
|
+
"version": "0.237.2",
|
|
4
4
|
"description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"./styles.css": "./src/styles.css"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.237.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.237.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.237.
|
|
19
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.237.2",
|
|
20
|
+
"@cosmicdrift/kumiko-headless": "0.237.2",
|
|
21
|
+
"@cosmicdrift/kumiko-renderer": "0.237.2",
|
|
22
22
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
23
23
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
24
24
|
"@radix-ui/react-label": "^2.1.8",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"@types/react-dom": "^19.2.3",
|
|
65
65
|
"jsdom": "^29.1.1",
|
|
66
66
|
"tailwindcss": "^4.3.0",
|
|
67
|
-
"@cosmicdrift/kumiko-locale-de": "0.237.
|
|
67
|
+
"@cosmicdrift/kumiko-locale-de": "0.237.2"
|
|
68
68
|
},
|
|
69
69
|
"repository": {
|
|
70
70
|
"type": "git",
|
|
@@ -386,6 +386,26 @@ describe("DataTable", () => {
|
|
|
386
386
|
expect(screen.getByTestId("t-empty")).not.toBeNull();
|
|
387
387
|
});
|
|
388
388
|
|
|
389
|
+
test("empty rows without emptyState translate the default label (issue #2653)", async () => {
|
|
390
|
+
const { LocaleProvider, createStaticLocaleResolver, kumikoDefaultTranslations } = await import(
|
|
391
|
+
"@cosmicdrift/kumiko-renderer"
|
|
392
|
+
);
|
|
393
|
+
const { localeDeBundle } = await import("@cosmicdrift/kumiko-locale-de");
|
|
394
|
+
render(
|
|
395
|
+
<LocaleProvider
|
|
396
|
+
resolver={createStaticLocaleResolver({ locale: "de" })}
|
|
397
|
+
fallbackBundles={[{ de: localeDeBundle }, kumikoDefaultTranslations]}
|
|
398
|
+
>
|
|
399
|
+
<DataTable
|
|
400
|
+
columns={[{ field: "name", label: "Name", type: "string", sortable: false }]}
|
|
401
|
+
rows={[]}
|
|
402
|
+
testId="t"
|
|
403
|
+
/>
|
|
404
|
+
</LocaleProvider>,
|
|
405
|
+
);
|
|
406
|
+
expect(screen.getByTestId("t-empty").textContent).toBe("Keine Einträge.");
|
|
407
|
+
});
|
|
408
|
+
|
|
389
409
|
test("rows + cells get individual testIds for E2E hooks", () => {
|
|
390
410
|
render(
|
|
391
411
|
<DataTable
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { type ReactNode, useCallback, useState } from "react";
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import { PATCH_DRAFT_SAVE_DEBOUNCE_MS } from "../../../renderer/src/components/render-edit";
|
|
20
|
+
import { defaultPrimitives, ScreenWidthProvider } from "../primitives";
|
|
20
21
|
import {
|
|
21
22
|
act,
|
|
22
23
|
createFakeDraftStorage,
|
|
@@ -37,6 +38,8 @@ const orderEntity = {
|
|
|
37
38
|
},
|
|
38
39
|
} as unknown as EntityDefinition;
|
|
39
40
|
|
|
41
|
+
const { Button } = defaultPrimitives;
|
|
42
|
+
|
|
40
43
|
function makeScreen(): EntityEditScreenDefinition {
|
|
41
44
|
return {
|
|
42
45
|
id: "orders:screen:order-edit",
|
|
@@ -320,6 +323,78 @@ describe("RenderEdit", () => {
|
|
|
320
323
|
expect(shell?.className).not.toContain("max-w-full");
|
|
321
324
|
});
|
|
322
325
|
|
|
326
|
+
test("ScreenWidthProvider width='full' widens the form shell when layout.width is unset (fw#2656)", () => {
|
|
327
|
+
render(
|
|
328
|
+
<ScreenWidthProvider width="full">
|
|
329
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
330
|
+
<RenderEdit<TestValues>
|
|
331
|
+
screen={makeScreen()}
|
|
332
|
+
entity={orderEntity}
|
|
333
|
+
featureName="orders"
|
|
334
|
+
initial={{ title: "", count: 0, isUrgent: false }}
|
|
335
|
+
writeCommand="order:create"
|
|
336
|
+
/>
|
|
337
|
+
</DispatcherProvider>
|
|
338
|
+
</ScreenWidthProvider>,
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const shell = screen.getByTestId("render-edit-form").firstElementChild;
|
|
342
|
+
expect(shell?.className).toContain("max-w-full");
|
|
343
|
+
expect(shell?.className).not.toContain("max-w-4xl");
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
test("layout.width: '3xl' overrides ScreenWidthProvider width='full' (fw#2656)", () => {
|
|
347
|
+
const screenDef = makeScreen();
|
|
348
|
+
render(
|
|
349
|
+
<ScreenWidthProvider width="full">
|
|
350
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
351
|
+
<RenderEdit<TestValues>
|
|
352
|
+
screen={{ ...screenDef, layout: { ...screenDef.layout, width: "3xl" } }}
|
|
353
|
+
entity={orderEntity}
|
|
354
|
+
featureName="orders"
|
|
355
|
+
initial={{ title: "", count: 0, isUrgent: false }}
|
|
356
|
+
writeCommand="order:create"
|
|
357
|
+
/>
|
|
358
|
+
</DispatcherProvider>
|
|
359
|
+
</ScreenWidthProvider>,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const shell = screen.getByTestId("render-edit-form").firstElementChild;
|
|
363
|
+
expect(shell?.className).toContain("max-w-3xl");
|
|
364
|
+
expect(shell?.className).not.toContain("max-w-full");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("Cancel button renders with the same hover-class family as a secondary button, not the link look (fw#2656)", () => {
|
|
368
|
+
render(
|
|
369
|
+
<>
|
|
370
|
+
<Button testId="secondary-reference" variant="secondary">
|
|
371
|
+
Reference
|
|
372
|
+
</Button>
|
|
373
|
+
<DispatcherProvider dispatcher={makeDispatcher()}>
|
|
374
|
+
<RenderEdit<TestValues>
|
|
375
|
+
screen={makeScreen()}
|
|
376
|
+
entity={orderEntity}
|
|
377
|
+
featureName="orders"
|
|
378
|
+
initial={{ title: "", count: 0, isUrgent: false }}
|
|
379
|
+
writeCommand="order:create"
|
|
380
|
+
onCancel={() => {}}
|
|
381
|
+
/>
|
|
382
|
+
</DispatcherProvider>
|
|
383
|
+
</>,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const reference = screen.getByTestId("secondary-reference");
|
|
387
|
+
const cancel = screen.getByTestId("render-edit-cancel");
|
|
388
|
+
const referenceHoverClass = reference.className
|
|
389
|
+
.split(" ")
|
|
390
|
+
.find((token) => token.startsWith("hover:"));
|
|
391
|
+
|
|
392
|
+
expect(referenceHoverClass).toBeDefined();
|
|
393
|
+
expect(cancel.className).toContain(referenceHoverClass as string);
|
|
394
|
+
expect(cancel.className).not.toContain("h-auto");
|
|
395
|
+
expect(cancel.className).not.toContain("px-0");
|
|
396
|
+
});
|
|
397
|
+
|
|
323
398
|
test("title resolved aus i18n-Key `screen:<id>.title` mit screenId als Fallback", () => {
|
|
324
399
|
const dispatcher = makeDispatcher();
|
|
325
400
|
render(
|
package/src/app/create-app.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
DraftStorageProvider,
|
|
20
20
|
ExtensionSectionsProvider,
|
|
21
21
|
type FeatureSchema,
|
|
22
|
+
type FormWidth,
|
|
22
23
|
hasDetailScreen,
|
|
23
24
|
KumikoScreen,
|
|
24
25
|
kumikoDefaultTranslations,
|
|
@@ -40,7 +41,7 @@ import {
|
|
|
40
41
|
import { type ComponentType, type ReactNode, useMemo } from "react";
|
|
41
42
|
import { createRoot, type Root } from "react-dom/client";
|
|
42
43
|
import { lastSegment } from "../layout/nav-tree";
|
|
43
|
-
import { defaultPrimitives } from "../primitives";
|
|
44
|
+
import { defaultPrimitives, ScreenWidthProvider } from "../primitives";
|
|
44
45
|
import { ToastProvider } from "../primitives/toast";
|
|
45
46
|
import { createEventSourceLiveEvents } from "../sse/live-events";
|
|
46
47
|
import { useBrowserTokensApi } from "../tokens";
|
|
@@ -189,6 +190,13 @@ export type CreateKumikoAppOptions = {
|
|
|
189
190
|
readonly hasWorkspaces?: boolean;
|
|
190
191
|
readonly features?: readonly FeatureSchema[];
|
|
191
192
|
}) => NavApi;
|
|
193
|
+
/** App-wide default width for form/detail screens that don't set their
|
|
194
|
+
* own `layout.width` (fw#2656). Default "4xl" (unchanged behavior) — a
|
|
195
|
+
* consumer app that wants e.g. bundled-feature screens to match its own
|
|
196
|
+
* full-width lists passes `screenWidth: "full"` once here instead of
|
|
197
|
+
* forking every screen it doesn't own. Per-screen `layout.width` always
|
|
198
|
+
* wins over this default. */
|
|
199
|
+
readonly screenWidth?: FormWidth;
|
|
192
200
|
};
|
|
193
201
|
|
|
194
202
|
// Reads the dev-server-injected schema from the global. Guarded for
|
|
@@ -429,32 +437,34 @@ export function createKumikoApp(options: CreateKumikoAppOptions = {}): { readonl
|
|
|
429
437
|
<LocaleProvider resolver={localeResolver} fallbackBundles={fallbackBundles}>
|
|
430
438
|
<DocumentLangSync resolver={localeResolver} />
|
|
431
439
|
<PrimitivesProvider value={primitives}>
|
|
432
|
-
<
|
|
433
|
-
<
|
|
434
|
-
<
|
|
435
|
-
<
|
|
436
|
-
<
|
|
437
|
-
<
|
|
438
|
-
<
|
|
439
|
-
<
|
|
440
|
-
<
|
|
441
|
-
<
|
|
442
|
-
<
|
|
443
|
-
<
|
|
444
|
-
<
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
440
|
+
<ScreenWidthProvider width={options.screenWidth ?? "4xl"}>
|
|
441
|
+
<AppFeaturesProvider features={app.features}>
|
|
442
|
+
<DispatcherProvider dispatcher={dispatcher}>
|
|
443
|
+
<DraftStorageProvider value={draftStorage}>
|
|
444
|
+
<LiveEventsProvider value={liveEvents}>
|
|
445
|
+
<DashboardBodyProvider value={WebDashboardBody}>
|
|
446
|
+
<CustomScreensProvider value={customScreens}>
|
|
447
|
+
<ColumnRenderersProvider value={columnRenderers}>
|
|
448
|
+
<ContentEditorsProvider value={contentEditors}>
|
|
449
|
+
<ExtensionSectionsProvider value={extensionSectionComponents}>
|
|
450
|
+
<NavProvidersProvider value={navProviders} entities={navEntities}>
|
|
451
|
+
<ResolversProvider resolvers={resolvers}>
|
|
452
|
+
<ToastProvider>
|
|
453
|
+
<UpdateChecker />
|
|
454
|
+
{stackWrappers(providers, stackWrappers(gates, screenNode))}
|
|
455
|
+
</ToastProvider>
|
|
456
|
+
</ResolversProvider>
|
|
457
|
+
</NavProvidersProvider>
|
|
458
|
+
</ExtensionSectionsProvider>
|
|
459
|
+
</ContentEditorsProvider>
|
|
460
|
+
</ColumnRenderersProvider>
|
|
461
|
+
</CustomScreensProvider>
|
|
462
|
+
</DashboardBodyProvider>
|
|
463
|
+
</LiveEventsProvider>
|
|
464
|
+
</DraftStorageProvider>
|
|
465
|
+
</DispatcherProvider>
|
|
466
|
+
</AppFeaturesProvider>
|
|
467
|
+
</ScreenWidthProvider>
|
|
458
468
|
</PrimitivesProvider>
|
|
459
469
|
</LocaleProvider>
|
|
460
470
|
</TokensBoot>
|
package/src/index.ts
CHANGED
|
@@ -141,6 +141,7 @@ export {
|
|
|
141
141
|
DefaultCard as Card,
|
|
142
142
|
defaultPrimitives,
|
|
143
143
|
FormScreenShell,
|
|
144
|
+
ScreenWidthProvider,
|
|
144
145
|
} from "./primitives";
|
|
145
146
|
export type { ActionMenuProps, MenuItemDef } from "./primitives/action-menu";
|
|
146
147
|
export { ActionMenu } from "./primitives/action-menu";
|
package/src/primitives/index.tsx
CHANGED
|
@@ -899,7 +899,7 @@ function DefaultDataTable({
|
|
|
899
899
|
data-testid={testId !== undefined ? `${testId}-empty` : "render-list-empty"}
|
|
900
900
|
className="flex flex-col items-center justify-center rounded-md border border-dashed p-12 text-sm text-muted-foreground gap-3"
|
|
901
901
|
>
|
|
902
|
-
{emptyState ?? <span>No entries
|
|
902
|
+
{emptyState ?? <span>{tableTranslate?.("kumiko.list.no-entries") ?? "No entries."}</span>}
|
|
903
903
|
</div>
|
|
904
904
|
);
|
|
905
905
|
|
|
@@ -2028,6 +2028,21 @@ export function BareFormProvider({ children }: { children: ReactNode }): ReactNo
|
|
|
2028
2028
|
return <BareFormContext.Provider value={true}>{children}</BareFormContext.Provider>;
|
|
2029
2029
|
}
|
|
2030
2030
|
|
|
2031
|
+
// App-wide default for FormScreenShell's width when a screen doesn't set its
|
|
2032
|
+
// own `layout.width`. Default "4xl" keeps today's behavior for apps that
|
|
2033
|
+
// don't opt into `createKumikoApp({ screenWidth })` (fw#2656).
|
|
2034
|
+
const ScreenWidthContext = createContext<FormWidth>("4xl");
|
|
2035
|
+
|
|
2036
|
+
export function ScreenWidthProvider({
|
|
2037
|
+
width,
|
|
2038
|
+
children,
|
|
2039
|
+
}: {
|
|
2040
|
+
readonly width: FormWidth;
|
|
2041
|
+
readonly children: ReactNode;
|
|
2042
|
+
}): ReactNode {
|
|
2043
|
+
return <ScreenWidthContext.Provider value={width}>{children}</ScreenWidthContext.Provider>;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2031
2046
|
function DefaultForm({
|
|
2032
2047
|
onSubmit,
|
|
2033
2048
|
children,
|
|
@@ -2161,27 +2176,29 @@ function DefaultForm({
|
|
|
2161
2176
|
}
|
|
2162
2177
|
|
|
2163
2178
|
// Canonical form/settings shell shared by DefaultForm (configEdit/entityEdit)
|
|
2164
|
-
// and custom settings screens (profile, privacy-center).
|
|
2165
|
-
//
|
|
2166
|
-
//
|
|
2167
|
-
//
|
|
2179
|
+
// and custom settings screens (profile, privacy-center). Width resolution:
|
|
2180
|
+
// explicit `maxWidth` prop (screen's `layout.width`) wins, else the app-wide
|
|
2181
|
+
// `createKumikoApp({ screenWidth })` default from ScreenWidthContext, else
|
|
2182
|
+
// "4xl" (a centered column, narrower than list chrome, which runs full-width).
|
|
2168
2183
|
export type FormScreenShellWidth = FormWidth;
|
|
2169
2184
|
|
|
2170
2185
|
export function FormScreenShell({
|
|
2171
2186
|
children,
|
|
2172
2187
|
className,
|
|
2173
2188
|
testId,
|
|
2174
|
-
maxWidth
|
|
2189
|
+
maxWidth,
|
|
2175
2190
|
}: {
|
|
2176
2191
|
readonly children: ReactNode;
|
|
2177
2192
|
readonly className?: string;
|
|
2178
2193
|
readonly testId?: string;
|
|
2179
2194
|
readonly maxWidth?: FormScreenShellWidth;
|
|
2180
2195
|
}): ReactNode {
|
|
2196
|
+
const contextWidth = useContext(ScreenWidthContext);
|
|
2197
|
+
const width = maxWidth ?? contextWidth;
|
|
2181
2198
|
return (
|
|
2182
2199
|
<div
|
|
2183
2200
|
data-testid={testId}
|
|
2184
|
-
className={cn("px-6 pt-6 pb-12 w-full", screenWidthClassName[
|
|
2201
|
+
className={cn("px-6 pt-6 pb-12 w-full", screenWidthClassName[width], className)}
|
|
2185
2202
|
>
|
|
2186
2203
|
{children}
|
|
2187
2204
|
</div>
|