@cosmicdrift/kumiko-renderer-web 0.241.0 → 0.242.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-web",
3
- "version": "0.241.0",
3
+ "version": "0.242.0",
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.241.0",
20
- "@cosmicdrift/kumiko-headless": "0.241.0",
21
- "@cosmicdrift/kumiko-renderer": "0.241.0",
19
+ "@cosmicdrift/kumiko-dispatcher-live": "0.242.0",
20
+ "@cosmicdrift/kumiko-headless": "0.242.0",
21
+ "@cosmicdrift/kumiko-renderer": "0.242.0",
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",
@@ -39,10 +39,10 @@
39
39
  "react-resizable-panels": "^4.12.2",
40
40
  "tailwind-merge": "^3.6.0",
41
41
  "temporal-polyfill": "^0.3.2",
42
- "@tiptap/core": "^3.29.2",
43
- "@tiptap/pm": "^3.29.2",
44
- "@tiptap/react": "^3.29.2",
45
- "@tiptap/starter-kit": "^3.29.2"
42
+ "@tiptap/core": "^3.30.5",
43
+ "@tiptap/pm": "^3.30.5",
44
+ "@tiptap/react": "^3.30.5",
45
+ "@tiptap/starter-kit": "^3.30.5"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@tailwindcss/cli": "^4.3.0",
@@ -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.241.0"
67
+ "@cosmicdrift/kumiko-locale-de": "0.242.0"
68
68
  },
69
69
  "repository": {
70
70
  "type": "git",
@@ -0,0 +1,58 @@
1
+ // #2677 — `field-sizing: content` on the vendored shadcn Textarea makes the
2
+ // `rows` attribute inert, so a declared row count has to reach the rendered
3
+ // height as a min-height floor. Asserting the attribute alone would pass even
4
+ // with the bug present.
5
+ import { describe, expect, test } from "bun:test";
6
+ import { render } from "@testing-library/react";
7
+ import { defaultPrimitives } from "../index";
8
+
9
+ const { Input } = defaultPrimitives;
10
+ const noop = () => {};
11
+
12
+ function renderTextarea(rows?: number): HTMLTextAreaElement {
13
+ const { container } = render(
14
+ <Input
15
+ kind="textarea"
16
+ id="notes"
17
+ name="notes"
18
+ value=""
19
+ onChange={noop}
20
+ {...(rows !== undefined && { rows })}
21
+ />,
22
+ );
23
+ const textarea = container.querySelector("textarea");
24
+ if (textarea === null) throw new Error("no textarea rendered");
25
+ return textarea;
26
+ }
27
+
28
+ function rowFactor(minHeight: string): number {
29
+ const match = /calc\((\d+(?:\.\d+)?) \* 1lh/.exec(minHeight);
30
+ if (match?.[1] === undefined) throw new Error(`min-height not row-derived: "${minHeight}"`);
31
+ return Number(match[1]);
32
+ }
33
+
34
+ describe("textarea rows → min-height (#2677)", () => {
35
+ test("declared rows drive the min-height, not just the attribute", () => {
36
+ const textarea = renderTextarea(16);
37
+ expect(textarea.getAttribute("rows")).toBe("16");
38
+ expect(rowFactor(textarea.style.minHeight)).toBe(16);
39
+ });
40
+
41
+ test("min-height scales with the declared row count", () => {
42
+ const small = rowFactor(renderTextarea(4).style.minHeight);
43
+ const large = rowFactor(renderTextarea(16).style.minHeight);
44
+ expect(large).toBeGreaterThan(small);
45
+ });
46
+
47
+ test("without rows no min-height is imposed — the Tailwind floor stays", () => {
48
+ expect(renderTextarea().style.minHeight).toBe("");
49
+ });
50
+
51
+ test("a row count that is not a positive integer falls back to the default", () => {
52
+ for (const rows of [0, -3, 2.5]) {
53
+ const textarea = renderTextarea(rows);
54
+ expect(textarea.style.minHeight).toBe("");
55
+ expect(textarea.getAttribute("rows")).toBe("4");
56
+ }
57
+ });
58
+ });
@@ -528,6 +528,22 @@ function SegmentedSelect({
528
528
  );
529
529
  }
530
530
 
531
+ // The row count reaches the textarea through an untyped view-model hint, so a
532
+ // sloppy schema can hand over a fraction or a zero — neither renders a sane
533
+ // attribute nor a sane min-height, so both fall back to the default instead.
534
+ function normalizedTextareaRows(rows: number | undefined): number | undefined {
535
+ return rows !== undefined && Number.isInteger(rows) && rows >= 1 ? rows : undefined;
536
+ }
537
+
538
+ // The vendored shadcn Textarea carries `field-sizing: content`, which derives
539
+ // the box height from the content and makes the `rows` attribute inert (#2677).
540
+ // A declared row count therefore only survives as a min-height floor: the field
541
+ // starts at `rows` lines tall and still grows with its content. The addend is
542
+ // the textarea frame — py-2 top+bottom plus the 1px borders.
543
+ function textareaMinHeight(rows: number): CSSProperties {
544
+ return { minHeight: `calc(${rows} * 1lh + 1rem + 2px)` };
545
+ }
546
+
531
547
  function DefaultInput(props: InputProps): ReactNode {
532
548
  // Vendored ui/input + ui/checkbox stylen Fehler über `aria-invalid`
533
549
  // selbst — kein manuelles border-destructive mehr nötig.
@@ -783,17 +799,20 @@ function DefaultInput(props: InputProps): ReactNode {
783
799
  {...(props.hasError !== undefined && { hasError: props.hasError })}
784
800
  />
785
801
  );
786
- case "textarea":
802
+ case "textarea": {
803
+ const rows = normalizedTextareaRows(props.rows);
787
804
  return (
788
805
  <Textarea
789
806
  {...common}
790
807
  readOnly={props.readOnly}
791
808
  value={props.value}
792
809
  onChange={(e: ChangeEvent<HTMLTextAreaElement>) => props.onChange(e.target.value)}
793
- rows={props.rows ?? 4}
810
+ rows={rows ?? 4}
794
811
  className="resize-y"
812
+ {...(rows !== undefined && { style: textareaMinHeight(rows) })}
795
813
  />
796
814
  );
815
+ }
797
816
  case "tz":
798
817
  return (
799
818
  <TzInput