@pihanga2/shadcn 0.2.15 → 0.2.18
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/AGENT.building-cards.md +68 -0
- package/AGENT.using-cards.md +15 -3
- package/README.md +4 -1
- package/cards/conditional/conditional.component.js +30 -12
- package/cards/conditional/conditional.component.js.map +1 -1
- package/cards/conditional/conditional.types.d.ts +21 -3
- package/cards/conditional/conditional.types.js +1 -1
- package/cards/conditional/conditional.types.js.map +1 -1
- package/cards/fileDrop/fileDrop.component.js.map +1 -1
- package/cards/fileDrop/fileDrop.css +1 -1
- package/cards/jsonViewer/jsonViewer.css +1 -1
- package/cards/keyboardOverlay/keyboardOverlay.component.js +25 -13
- package/cards/keyboardOverlay/keyboardOverlay.component.js.map +1 -1
- package/cards/keyboardOverlay/keyboardOverlay.types.d.ts +22 -4
- package/cards/keyboardOverlay/keyboardOverlay.types.js.map +1 -1
- package/cards/loadingOverlay/loadingOverlay.css +1 -1
- package/cards/modeToggle/mode-toggle.component.js +1 -1
- package/cards/modeToggle/mode-toggle.component.js.map +1 -1
- package/cards/pageWithNavbar/pageWithNavbar.css +1 -1
- package/cards/pasteTarget/pasteTarget.css +1 -1
- package/cards/resizableColumns/index.d.ts +1 -0
- package/cards/resizableColumns/index.js +12 -0
- package/cards/resizableColumns/index.js.map +1 -0
- package/cards/resizableColumns/resizableColumns.component.d.ts +4 -0
- package/cards/resizableColumns/resizableColumns.component.js +105 -0
- package/cards/resizableColumns/resizableColumns.component.js.map +1 -0
- package/cards/resizableColumns/resizableColumns.css +1 -0
- package/cards/resizableColumns/resizableColumns.types.d.ts +22 -0
- package/cards/resizableColumns/resizableColumns.types.js +7 -0
- package/cards/resizableColumns/resizableColumns.types.js.map +1 -0
- package/cards/resizableGrid/index.d.ts +1 -0
- package/cards/resizableGrid/index.js +12 -0
- package/cards/resizableGrid/index.js.map +1 -0
- package/cards/resizableGrid/resizableGrid.component.d.ts +4 -0
- package/cards/resizableGrid/resizableGrid.component.js +166 -0
- package/cards/resizableGrid/resizableGrid.component.js.map +1 -0
- package/cards/resizableGrid/resizableGrid.css +1 -0
- package/cards/resizableGrid/resizableGrid.types.d.ts +33 -0
- package/cards/resizableGrid/resizableGrid.types.js +7 -0
- package/cards/resizableGrid/resizableGrid.types.js.map +1 -0
- package/cards/scrollbarWithAnnotations/scrollbarWithAnnotations.css +1 -1
- package/cards/suspense/index.d.ts +1 -0
- package/cards/suspense/index.js +12 -0
- package/cards/suspense/index.js.map +1 -0
- package/cards/suspense/suspense.component.d.ts +15 -0
- package/cards/suspense/suspense.component.js +45 -0
- package/cards/suspense/suspense.component.js.map +1 -0
- package/cards/suspense/suspense.types.d.ts +67 -0
- package/cards/suspense/suspense.types.js +7 -0
- package/cards/suspense/suspense.types.js.map +1 -0
- package/cards/typography/typography.component.js +13 -12
- package/cards/typography/typography.component.js.map +1 -1
- package/cards/typography/typography.types.d.ts +2 -0
- package/cards/typography/typography.types.js.map +1 -1
- package/package.json +17 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import './resizableColumns.css';/* empty css */
|
|
2
|
+
import * as e from "react";
|
|
3
|
+
import { Card as t } from "@pihanga2/core";
|
|
4
|
+
import n from "clsx";
|
|
5
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
6
|
+
//#region src/cards/resizableColumns/resizableColumns.component.tsx
|
|
7
|
+
var a = 10;
|
|
8
|
+
function o(e, t, n) {
|
|
9
|
+
if (!e?.length || e.length !== t) return Array(t).fill(100 / t);
|
|
10
|
+
let r = e.map((e) => {
|
|
11
|
+
let t = e.trim();
|
|
12
|
+
return t.endsWith("px") ? {
|
|
13
|
+
type: "px",
|
|
14
|
+
value: Math.max(0, parseFloat(t))
|
|
15
|
+
} : t.endsWith("%") ? {
|
|
16
|
+
type: "pct",
|
|
17
|
+
value: Math.max(0, parseFloat(t))
|
|
18
|
+
} : t.endsWith("fr") ? {
|
|
19
|
+
type: "fr",
|
|
20
|
+
value: Math.max(0, parseFloat(t))
|
|
21
|
+
} : {
|
|
22
|
+
type: "fr",
|
|
23
|
+
value: 1
|
|
24
|
+
};
|
|
25
|
+
}), i = n > 0 ? r.reduce((e, t) => e + (t.type === "px" ? t.value / n * 100 : 0), 0) : 0, a = r.reduce((e, t) => e + (t.type === "pct" ? t.value : 0), 0), o = Math.max(0, 100 - i - a), s = r.reduce((e, t) => e + (t.type === "fr" ? t.value : 0), 0), c = r.map((e) => e.type === "px" ? n > 0 ? e.value / n * 100 : 100 / t : e.type === "pct" ? e.value : s > 0 ? e.value / s * o : o / t), l = c.reduce((e, t) => e + t, 0);
|
|
26
|
+
return l > 0 ? c.map((e) => e / l * 100) : Array(t).fill(100 / t);
|
|
27
|
+
}
|
|
28
|
+
var s = (e, t, n) => Math.min(n, Math.max(t, e)), c = (c) => {
|
|
29
|
+
let { cardName: l, columnCards: u = [], columnWidths: d, minColumnPercent: f = a, className: p, dividerClassName: m } = c, h = u.length, [g, _] = e.useState(() => Array(h).fill(100 / h)), v = e.useRef(g), y = e.useRef(null), b = e.useRef(null);
|
|
30
|
+
e.useLayoutEffect(() => {
|
|
31
|
+
if (!y.current) return;
|
|
32
|
+
let e = y.current.getBoundingClientRect().width, t = o(d, h, e);
|
|
33
|
+
v.current = t, _(t);
|
|
34
|
+
}, [d, h]), e.useEffect(() => () => {
|
|
35
|
+
b.current?.();
|
|
36
|
+
}, []);
|
|
37
|
+
let x = (e, t) => ({
|
|
38
|
+
min: f,
|
|
39
|
+
max: t[e] + t[e + 1] - f
|
|
40
|
+
}), S = e.useCallback((e, t) => {
|
|
41
|
+
let n = v.current, r = n[e] + n[e + 1], { min: i, max: a } = x(e, n), o = s(t, i, a), c = [...n];
|
|
42
|
+
c[e] = o, c[e + 1] = r - o, v.current = c, _(c);
|
|
43
|
+
}, [f]), C = e.useCallback((e) => (t) => {
|
|
44
|
+
t.preventDefault();
|
|
45
|
+
let n = t.clientX, r = v.current[e], i = y.current;
|
|
46
|
+
if (!i) return;
|
|
47
|
+
let { width: a } = i.getBoundingClientRect();
|
|
48
|
+
if (a <= 0) return;
|
|
49
|
+
let o = (t) => {
|
|
50
|
+
S(e, r + (t.clientX - n) / a * 100);
|
|
51
|
+
}, s = () => {
|
|
52
|
+
window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", s), b.current = null;
|
|
53
|
+
};
|
|
54
|
+
b.current?.(), window.addEventListener("mousemove", o), window.addEventListener("mouseup", s), b.current = () => {
|
|
55
|
+
window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", s);
|
|
56
|
+
};
|
|
57
|
+
}, [S]), w = e.useCallback((e) => (t) => {
|
|
58
|
+
let { min: n, max: r } = x(e, v.current), i = v.current[e];
|
|
59
|
+
switch (t.key) {
|
|
60
|
+
case "ArrowLeft":
|
|
61
|
+
t.preventDefault(), S(e, i + (t.shiftKey ? -10 : -2));
|
|
62
|
+
break;
|
|
63
|
+
case "ArrowRight":
|
|
64
|
+
t.preventDefault(), S(e, i + (t.shiftKey ? 10 : 2));
|
|
65
|
+
break;
|
|
66
|
+
case "Home":
|
|
67
|
+
t.preventDefault(), S(e, n);
|
|
68
|
+
break;
|
|
69
|
+
case "End":
|
|
70
|
+
t.preventDefault(), S(e, r);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}, [S]);
|
|
74
|
+
return h === 0 ? null : /* @__PURE__ */ r("div", {
|
|
75
|
+
ref: y,
|
|
76
|
+
"data-pihanga": l,
|
|
77
|
+
className: n("pi-resizable-columns", p),
|
|
78
|
+
children: u.map((a, o) => {
|
|
79
|
+
let s = g[o] ?? 100 / h;
|
|
80
|
+
return /* @__PURE__ */ i(e.Fragment, { children: [o > 0 && /* @__PURE__ */ r("div", {
|
|
81
|
+
className: n("pi-resizable-columns-divider", m),
|
|
82
|
+
onMouseDown: C(o - 1),
|
|
83
|
+
onKeyDown: w(o - 1),
|
|
84
|
+
role: "separator",
|
|
85
|
+
"aria-orientation": "vertical",
|
|
86
|
+
"aria-label": `Resize columns ${o} and ${o + 1}`,
|
|
87
|
+
"aria-valuemin": f,
|
|
88
|
+
"aria-valuemax": (g[o - 1] ?? 0) + (g[o] ?? 0) - f,
|
|
89
|
+
"aria-valuenow": Math.round(g[o - 1] ?? 0),
|
|
90
|
+
tabIndex: 0
|
|
91
|
+
}), /* @__PURE__ */ r("div", {
|
|
92
|
+
className: "pi-resizable-columns-pane",
|
|
93
|
+
style: { width: `${s}%` },
|
|
94
|
+
children: /* @__PURE__ */ r(t, {
|
|
95
|
+
cardName: a,
|
|
96
|
+
parentCard: l
|
|
97
|
+
})
|
|
98
|
+
})] }, o);
|
|
99
|
+
})
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
export { c as ResizableColumnsComponent };
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=resizableColumns.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resizableColumns.component.js","names":[],"sources":["../../../src/cards/resizableColumns/resizableColumns.component.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport clsx from \"clsx\";\n\nimport \"./resizableColumns.css\";\nimport type {ResizableColumnsProps} from \"./resizableColumns.types\";\n\nconst DEFAULT_MIN_PERCENT = 10;\n\ntype ParsedUnit = {type: \"px\" | \"pct\" | \"fr\"; value: number};\n\nfunction parseColumnWidths(\n widths: string[] | undefined,\n count: number,\n containerPx: number,\n): number[] {\n if (!widths?.length || widths.length !== count) {\n return new Array(count).fill(100 / count);\n }\n\n const parsed: ParsedUnit[] = widths.map((w) => {\n const s = w.trim();\n if (s.endsWith(\"px\"))\n return {type: \"px\", value: Math.max(0, parseFloat(s))};\n if (s.endsWith(\"%\"))\n return {type: \"pct\", value: Math.max(0, parseFloat(s))};\n if (s.endsWith(\"fr\"))\n return {type: \"fr\", value: Math.max(0, parseFloat(s))};\n return {type: \"fr\", value: 1};\n });\n\n const totalPxAsPct =\n containerPx > 0\n ? parsed.reduce(\n (s, p) => s + (p.type === \"px\" ? (p.value / containerPx) * 100 : 0),\n 0,\n )\n : 0;\n const totalExplicitPct = parsed.reduce(\n (s, p) => s + (p.type === \"pct\" ? p.value : 0),\n 0,\n );\n const remainingPct = Math.max(0, 100 - totalPxAsPct - totalExplicitPct);\n const totalFr = parsed.reduce(\n (s, p) => s + (p.type === \"fr\" ? p.value : 0),\n 0,\n );\n\n const result = parsed.map((p) => {\n if (p.type === \"px\")\n return containerPx > 0 ? (p.value / containerPx) * 100 : 100 / count;\n if (p.type === \"pct\") return p.value;\n return totalFr > 0\n ? (p.value / totalFr) * remainingPct\n : remainingPct / count;\n });\n\n const total = result.reduce((s, v) => s + v, 0);\n return total > 0\n ? result.map((v) => (v / total) * 100)\n : new Array(count).fill(100 / count);\n}\n\nconst clamp = (v: number, min: number, max: number) =>\n Math.min(max, Math.max(min, v));\n\nexport const ResizableColumnsComponent = (\n props: PiCardProps<ResizableColumnsProps>,\n): React.ReactNode => {\n const {\n cardName,\n columnCards = [],\n columnWidths,\n minColumnPercent = DEFAULT_MIN_PERCENT,\n className,\n dividerClassName,\n } = props;\n\n const count = columnCards.length;\n\n // Equal split as the initial state — useLayoutEffect overwrites before paint.\n const [panePercents, setPanePercents] = React.useState<number[]>(() =>\n new Array(count).fill(100 / count),\n );\n const panePercentsRef = React.useRef(panePercents);\n const containerRef = React.useRef<HTMLDivElement | null>(null);\n const dragCleanupRef = React.useRef<(() => void) | null>(null);\n\n // Parse columnWidths once the container is mounted so px values can be resolved.\n React.useLayoutEffect(() => {\n if (!containerRef.current) return;\n const width = containerRef.current.getBoundingClientRect().width;\n const next = parseColumnWidths(columnWidths, count, width);\n panePercentsRef.current = next;\n setPanePercents(next);\n }, [columnWidths, count]);\n\n // Cleanup any in-progress drag on unmount.\n React.useEffect(() => {\n return () => {\n dragCleanupRef.current?.();\n };\n }, []);\n\n const getDividerBounds = (dividerIndex: number, percents: number[]) => {\n const combined = percents[dividerIndex] + percents[dividerIndex + 1];\n return {min: minColumnPercent, max: combined - minColumnPercent};\n };\n\n const updatePanePair = React.useCallback(\n (dividerIndex: number, nextLeadingPercent: number) => {\n const current = panePercentsRef.current;\n const combined = current[dividerIndex] + current[dividerIndex + 1];\n const {min, max} = getDividerBounds(dividerIndex, current);\n const clamped = clamp(nextLeadingPercent, min, max);\n const next = [...current];\n next[dividerIndex] = clamped;\n next[dividerIndex + 1] = combined - clamped;\n panePercentsRef.current = next;\n setPanePercents(next);\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [minColumnPercent],\n );\n\n const handleMouseDown = React.useCallback(\n (dividerIndex: number) => (event: React.MouseEvent<HTMLDivElement>) => {\n event.preventDefault();\n const startX = event.clientX;\n const startLeadingPercent = panePercentsRef.current[dividerIndex];\n const container = containerRef.current;\n if (!container) return;\n const {width} = container.getBoundingClientRect();\n if (width <= 0) return;\n\n const onMouseMove = (e: MouseEvent) => {\n const deltaPercent = ((e.clientX - startX) / width) * 100;\n updatePanePair(dividerIndex, startLeadingPercent + deltaPercent);\n };\n const onMouseUp = () => {\n window.removeEventListener(\"mousemove\", onMouseMove);\n window.removeEventListener(\"mouseup\", onMouseUp);\n dragCleanupRef.current = null;\n };\n\n dragCleanupRef.current?.();\n window.addEventListener(\"mousemove\", onMouseMove);\n window.addEventListener(\"mouseup\", onMouseUp);\n dragCleanupRef.current = () => {\n window.removeEventListener(\"mousemove\", onMouseMove);\n window.removeEventListener(\"mouseup\", onMouseUp);\n };\n },\n [updatePanePair],\n );\n\n const handleKeyDown = React.useCallback(\n (dividerIndex: number) => (event: React.KeyboardEvent<HTMLDivElement>) => {\n const {min, max} = getDividerBounds(\n dividerIndex,\n panePercentsRef.current,\n );\n const current = panePercentsRef.current[dividerIndex];\n switch (event.key) {\n case \"ArrowLeft\":\n event.preventDefault();\n updatePanePair(dividerIndex, current + (event.shiftKey ? -10 : -2));\n break;\n case \"ArrowRight\":\n event.preventDefault();\n updatePanePair(dividerIndex, current + (event.shiftKey ? 10 : 2));\n break;\n case \"Home\":\n event.preventDefault();\n updatePanePair(dividerIndex, min);\n break;\n case \"End\":\n event.preventDefault();\n updatePanePair(dividerIndex, max);\n break;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [updatePanePair],\n );\n\n if (count === 0) return null;\n\n return (\n <div\n ref={containerRef}\n data-pihanga={cardName}\n className={clsx(\"pi-resizable-columns\", className)}\n >\n {columnCards.map((card, i) => {\n const pct = panePercents[i] ?? 100 / count;\n return (\n <React.Fragment key={i}>\n {i > 0 && (\n <div\n className={clsx(\n \"pi-resizable-columns-divider\",\n dividerClassName,\n )}\n onMouseDown={handleMouseDown(i - 1)}\n onKeyDown={handleKeyDown(i - 1)}\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={`Resize columns ${i} and ${i + 1}`}\n aria-valuemin={minColumnPercent}\n aria-valuemax={\n (panePercents[i - 1] ?? 0) +\n (panePercents[i] ?? 0) -\n minColumnPercent\n }\n aria-valuenow={Math.round(panePercents[i - 1] ?? 0)}\n tabIndex={0}\n />\n )}\n <div\n className=\"pi-resizable-columns-pane\"\n style={{width: `${pct}%`}}\n >\n <Card cardName={card} parentCard={cardName} />\n </div>\n </React.Fragment>\n );\n })}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.pi-resizable-columns{flex-direction:row;width:100%;min-height:100%;display:flex;overflow:hidden}.pi-resizable-columns-pane{flex-shrink:0;min-width:0;overflow:hidden}.pi-resizable-columns-divider{cursor:col-resize;flex:0 0 8px;position:relative}.pi-resizable-columns-divider:before{content:"";width:var(--rg-divider-width,1px);background-color:var(--rg-divider-color,hsl(var(--border,214 32% 91%)));transition:width .12s,background-color .12s;position:absolute;top:0;bottom:0;left:50%;transform:translate(-50%)}.pi-resizable-columns-divider:hover:before,.pi-resizable-columns-divider:focus-visible:before{width:var(--rg-divider-hover-width,4px);background-color:var(--rg-divider-hover-color,hsl(var(--muted-foreground,215 16% 47%)))}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PiCardRef } from '@pihanga2/core';
|
|
2
|
+
export declare const RESIZABLE_COLUMNS_CARD = "pi/resizable-columns";
|
|
3
|
+
export type ResizableColumnsProps = {
|
|
4
|
+
columnCards: PiCardRef[];
|
|
5
|
+
/**
|
|
6
|
+
* Initial column widths. Supports CSS-like values:
|
|
7
|
+
* - '200px' — fixed initial width
|
|
8
|
+
* - '30%' — explicit percentage
|
|
9
|
+
* - '1fr' — fraction of remaining space (after px/% columns)
|
|
10
|
+
*
|
|
11
|
+
* Defaults to equal split when omitted or when length doesn't match columnCards.
|
|
12
|
+
* @example ['200px', '1fr', '1fr'] — fixed sidebar, two equal remaining columns
|
|
13
|
+
* @example ['30%', '70%']
|
|
14
|
+
* @example ['1fr', '2fr', '1fr']
|
|
15
|
+
*/
|
|
16
|
+
columnWidths?: string[];
|
|
17
|
+
/** Minimum percentage any column can shrink to during drag. Default: 10 */
|
|
18
|
+
minColumnPercent?: number;
|
|
19
|
+
className?: string;
|
|
20
|
+
dividerClassName?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare const ResizableColumns: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<ResizableColumnsProps, S, {}>) => import('@pihanga2/core').PiCardDef;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createCardDeclaration as e } from "@pihanga2/core";
|
|
2
|
+
//#region src/cards/resizableColumns/resizableColumns.types.ts
|
|
3
|
+
var t = "pi/resizable-columns", n = e(t);
|
|
4
|
+
//#endregion
|
|
5
|
+
export { t as RESIZABLE_COLUMNS_CARD, n as ResizableColumns };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=resizableColumns.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resizableColumns.types.js","names":[],"sources":["../../../src/cards/resizableColumns/resizableColumns.types.ts"],"sourcesContent":["import {createCardDeclaration, type PiCardRef} from \"@pihanga2/core\";\n\nexport const RESIZABLE_COLUMNS_CARD = \"pi/resizable-columns\";\n\nexport type ResizableColumnsProps = {\n columnCards: PiCardRef[];\n /**\n * Initial column widths. Supports CSS-like values:\n * - '200px' — fixed initial width\n * - '30%' — explicit percentage\n * - '1fr' — fraction of remaining space (after px/% columns)\n *\n * Defaults to equal split when omitted or when length doesn't match columnCards.\n * @example ['200px', '1fr', '1fr'] — fixed sidebar, two equal remaining columns\n * @example ['30%', '70%']\n * @example ['1fr', '2fr', '1fr']\n */\n columnWidths?: string[];\n /** Minimum percentage any column can shrink to during drag. Default: 10 */\n minColumnPercent?: number;\n className?: string;\n dividerClassName?: string;\n};\n\nexport const ResizableColumns = createCardDeclaration<ResizableColumnsProps>(\n RESIZABLE_COLUMNS_CARD,\n);\n"],"mappings":";;AAEA,IAAa,IAAyB,wBAsBzB,IAAmB,EAC9B,CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './resizableGrid.types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ResizableGridComponent as e } from "./resizableGrid.component.js";
|
|
2
|
+
import { RESIZABLE_GRID_CARD as t, ResizableGrid as n } from "./resizableGrid.types.js";
|
|
3
|
+
import { registerCardComponent as r } from "@pihanga2/core";
|
|
4
|
+
//#region src/cards/resizableGrid/index.ts
|
|
5
|
+
r({
|
|
6
|
+
name: t,
|
|
7
|
+
component: e
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
export { t as RESIZABLE_GRID_CARD, n as ResizableGrid };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/resizableGrid/index.ts"],"sourcesContent":["import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {ResizableGridComponent} from \"./resizableGrid.component\";\nimport {RESIZABLE_GRID_CARD} from \"./resizableGrid.types\";\n\nexport * from \"./resizableGrid.types\";\n\nregisterCardComponent({\n name: RESIZABLE_GRID_CARD,\n component: ResizableGridComponent,\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;AACb,CAAC"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import './resizableGrid.css';/* empty css */
|
|
2
|
+
import * as e from "react";
|
|
3
|
+
import { Card as t } from "@pihanga2/core";
|
|
4
|
+
import n from "clsx";
|
|
5
|
+
import { jsx as r, jsxs as i } from "react/jsx-runtime";
|
|
6
|
+
//#region src/cards/resizableGrid/resizableGrid.component.tsx
|
|
7
|
+
var a = 10, o = 8;
|
|
8
|
+
function s(e) {
|
|
9
|
+
let t = e.trim();
|
|
10
|
+
return t === "auto" ? {
|
|
11
|
+
type: "auto",
|
|
12
|
+
value: 0
|
|
13
|
+
} : t.endsWith("px") ? {
|
|
14
|
+
type: "px",
|
|
15
|
+
value: Math.max(0, parseFloat(t))
|
|
16
|
+
} : t.endsWith("%") ? {
|
|
17
|
+
type: "pct",
|
|
18
|
+
value: Math.max(0, parseFloat(t))
|
|
19
|
+
} : t.endsWith("fr") ? {
|
|
20
|
+
type: "fr",
|
|
21
|
+
value: Math.max(0, parseFloat(t))
|
|
22
|
+
} : {
|
|
23
|
+
type: "fr",
|
|
24
|
+
value: 1
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function c(e, t, n) {
|
|
28
|
+
if (!e?.length || e.length !== t) return Array(t).fill(100 / t);
|
|
29
|
+
let r = e.map(s), i = n > 0 ? r.reduce((e, t) => e + (t.type === "px" ? t.value / n * 100 : 0), 0) : 0, a = r.reduce((e, t) => e + (t.type === "pct" ? t.value : 0), 0), o = Math.max(0, 100 - i - a), c = r.reduce((e, t) => e + (t.type === "fr" ? t.value : 0), 0), l = r.map((e) => e.type === "auto" ? 0 : e.type === "px" ? n > 0 ? e.value / n * 100 : 0 : e.type === "pct" ? e.value : c > 0 ? e.value / c * o : 0), u = l.reduce((e, t) => e + t, 0);
|
|
30
|
+
if (u <= 0) {
|
|
31
|
+
let e = r.filter((e) => e.type !== "auto").length || t;
|
|
32
|
+
return r.map((t) => t.type === "auto" ? 0 : 100 / e);
|
|
33
|
+
}
|
|
34
|
+
return l.map((e, t) => r[t].type === "auto" ? 0 : e / u * 100);
|
|
35
|
+
}
|
|
36
|
+
function l(e, t) {
|
|
37
|
+
return e.map((n, r) => {
|
|
38
|
+
let i = n === "auto" ? "auto" : `${t[r]}fr`;
|
|
39
|
+
return r < e.length - 1 && n !== "auto" && e[r + 1] !== "auto" ? `${i} ${o}px` : i;
|
|
40
|
+
}).join(" ");
|
|
41
|
+
}
|
|
42
|
+
function u(e) {
|
|
43
|
+
let t = 1;
|
|
44
|
+
return e.map((n, r) => {
|
|
45
|
+
let i = t;
|
|
46
|
+
return t++, r < e.length - 1 && n !== "auto" && e[r + 1] !== "auto" && t++, i;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function d(e, t, n) {
|
|
50
|
+
return n >= e.length - 1 || e[n] === "auto" || e[n + 1] === "auto" ? null : t[n] + 1;
|
|
51
|
+
}
|
|
52
|
+
var f = (e, t, n) => Math.min(n, Math.max(t, e));
|
|
53
|
+
function p(e, t, n) {
|
|
54
|
+
return (r, i) => {
|
|
55
|
+
let a = e.current, o = a[r] + a[r + 1], s = f(i, Math.min(n, o - n), Math.max(n, o - n)), c = [...a];
|
|
56
|
+
c[r] = s, c[r + 1] = o - s, e.current = c, t(c);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
var m = (o) => {
|
|
60
|
+
let { cardName: s, cells: f = [], columnWidths: m, rowHeights: h, minColumnPercent: g = a, minRowPercent: _ = a, className: v, dividerClassName: y } = o, b = f.length, x = f[0]?.length ?? 0, S = e.useMemo(() => m ?? Array(x).fill("1fr"), [m, x]), C = e.useMemo(() => h ?? Array(b).fill("1fr"), [h, b]), [w, T] = e.useState(() => c(S, x, 0)), [E, D] = e.useState(() => c(C, b, 0)), O = e.useRef(w), k = e.useRef(E), A = e.useRef(null), j = e.useRef(null), M = e.useRef(null);
|
|
61
|
+
e.useLayoutEffect(() => {
|
|
62
|
+
if (!A.current) return;
|
|
63
|
+
let { width: e, height: t } = A.current.getBoundingClientRect(), n = c(S, x, e), r = c(C, b, t);
|
|
64
|
+
O.current = n, k.current = r, T(n), D(r);
|
|
65
|
+
}, [
|
|
66
|
+
S,
|
|
67
|
+
C,
|
|
68
|
+
x,
|
|
69
|
+
b
|
|
70
|
+
]), e.useEffect(() => () => {
|
|
71
|
+
j.current?.(), M.current?.();
|
|
72
|
+
}, []);
|
|
73
|
+
let N = e.useMemo(() => p(O, T, g), [g]), P = e.useMemo(() => p(k, D, _), [_]), F = e.useCallback((e) => (t) => {
|
|
74
|
+
t.preventDefault();
|
|
75
|
+
let n = t.clientX, r = O.current[e], i = A.current;
|
|
76
|
+
if (!i) return;
|
|
77
|
+
let { width: a } = i.getBoundingClientRect();
|
|
78
|
+
if (a <= 0) return;
|
|
79
|
+
let o = (t) => N(e, r + (t.clientX - n) / a * 100), s = () => {
|
|
80
|
+
window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", s), j.current = null;
|
|
81
|
+
};
|
|
82
|
+
j.current?.(), window.addEventListener("mousemove", o), window.addEventListener("mouseup", s), j.current = () => {
|
|
83
|
+
window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", s);
|
|
84
|
+
};
|
|
85
|
+
}, [N]), I = e.useCallback((e) => (t) => {
|
|
86
|
+
let n = O.current[e], r = n + O.current[e + 1], i = t.shiftKey ? 10 : 2;
|
|
87
|
+
t.key === "ArrowLeft" ? (t.preventDefault(), N(e, n - i)) : t.key === "ArrowRight" ? (t.preventDefault(), N(e, n + i)) : t.key === "Home" ? (t.preventDefault(), N(e, g)) : t.key === "End" && (t.preventDefault(), N(e, r - g));
|
|
88
|
+
}, [N, g]), L = e.useCallback((e) => (t) => {
|
|
89
|
+
t.preventDefault();
|
|
90
|
+
let n = t.clientY, r = k.current[e], i = A.current;
|
|
91
|
+
if (!i) return;
|
|
92
|
+
let { height: a } = i.getBoundingClientRect();
|
|
93
|
+
if (a <= 0) return;
|
|
94
|
+
let o = (t) => P(e, r + (t.clientY - n) / a * 100), s = () => {
|
|
95
|
+
window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", s), M.current = null;
|
|
96
|
+
};
|
|
97
|
+
M.current?.(), window.addEventListener("mousemove", o), window.addEventListener("mouseup", s), M.current = () => {
|
|
98
|
+
window.removeEventListener("mousemove", o), window.removeEventListener("mouseup", s);
|
|
99
|
+
};
|
|
100
|
+
}, [P]), R = e.useCallback((e) => (t) => {
|
|
101
|
+
let n = k.current[e], r = n + k.current[e + 1], i = t.shiftKey ? 10 : 2;
|
|
102
|
+
t.key === "ArrowUp" ? (t.preventDefault(), P(e, n - i)) : t.key === "ArrowDown" ? (t.preventDefault(), P(e, n + i)) : t.key === "Home" ? (t.preventDefault(), P(e, _)) : t.key === "End" && (t.preventDefault(), P(e, r - _));
|
|
103
|
+
}, [P, _]);
|
|
104
|
+
if (b === 0 || x === 0) return null;
|
|
105
|
+
let z = l(S, w), B = l(C, E), V = u(S), H = u(C);
|
|
106
|
+
return /* @__PURE__ */ i("div", {
|
|
107
|
+
ref: A,
|
|
108
|
+
"data-pihanga": s,
|
|
109
|
+
className: n("pi-resizable-grid", v),
|
|
110
|
+
style: {
|
|
111
|
+
gridTemplateColumns: z,
|
|
112
|
+
gridTemplateRows: B
|
|
113
|
+
},
|
|
114
|
+
children: [
|
|
115
|
+
f.map((e, n) => e.map((e, i) => /* @__PURE__ */ r("div", {
|
|
116
|
+
className: "pi-resizable-grid-cell",
|
|
117
|
+
style: {
|
|
118
|
+
gridColumn: V[i],
|
|
119
|
+
gridRow: H[n]
|
|
120
|
+
},
|
|
121
|
+
children: /* @__PURE__ */ r(t, {
|
|
122
|
+
cardName: e,
|
|
123
|
+
parentCard: s
|
|
124
|
+
})
|
|
125
|
+
}, `c${n}-${i}`))),
|
|
126
|
+
S.map((e, t) => {
|
|
127
|
+
let i = d(S, V, t);
|
|
128
|
+
return i === null ? null : /* @__PURE__ */ r("div", {
|
|
129
|
+
className: n("pi-resizable-grid-col-divider", y),
|
|
130
|
+
style: {
|
|
131
|
+
gridColumn: i,
|
|
132
|
+
gridRow: "1 / -1"
|
|
133
|
+
},
|
|
134
|
+
onMouseDown: F(t),
|
|
135
|
+
onKeyDown: I(t),
|
|
136
|
+
role: "separator",
|
|
137
|
+
"aria-orientation": "vertical",
|
|
138
|
+
"aria-label": `Resize columns ${t + 1} and ${t + 2}`,
|
|
139
|
+
"aria-valuenow": Math.round(w[t]),
|
|
140
|
+
tabIndex: 0
|
|
141
|
+
}, `cd${t}`);
|
|
142
|
+
}),
|
|
143
|
+
C.map((e, t) => {
|
|
144
|
+
let i = d(C, H, t);
|
|
145
|
+
return i === null ? null : /* @__PURE__ */ r("div", {
|
|
146
|
+
className: n("pi-resizable-grid-row-divider", y),
|
|
147
|
+
style: {
|
|
148
|
+
gridRow: i,
|
|
149
|
+
gridColumn: "1 / -1"
|
|
150
|
+
},
|
|
151
|
+
onMouseDown: L(t),
|
|
152
|
+
onKeyDown: R(t),
|
|
153
|
+
role: "separator",
|
|
154
|
+
"aria-orientation": "horizontal",
|
|
155
|
+
"aria-label": `Resize rows ${t + 1} and ${t + 2}`,
|
|
156
|
+
"aria-valuenow": Math.round(E[t]),
|
|
157
|
+
tabIndex: 0
|
|
158
|
+
}, `rd${t}`);
|
|
159
|
+
})
|
|
160
|
+
]
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
//#endregion
|
|
164
|
+
export { m as ResizableGridComponent };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=resizableGrid.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resizableGrid.component.js","names":[],"sources":["../../../src/cards/resizableGrid/resizableGrid.component.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport clsx from \"clsx\";\n\nimport \"./resizableGrid.css\";\nimport type {ResizableGridProps} from \"./resizableGrid.types\";\n\nconst DEFAULT_MIN_PERCENT = 10;\nconst DIVIDER_PX = 8;\n\ntype ParsedUnit = {type: \"px\" | \"pct\" | \"fr\" | \"auto\"; value: number};\n\nfunction parseWidthSpec(w: string): ParsedUnit {\n const s = w.trim();\n if (s === \"auto\") return {type: \"auto\", value: 0};\n if (s.endsWith(\"px\")) return {type: \"px\", value: Math.max(0, parseFloat(s))};\n if (s.endsWith(\"%\")) return {type: \"pct\", value: Math.max(0, parseFloat(s))};\n if (s.endsWith(\"fr\")) return {type: \"fr\", value: Math.max(0, parseFloat(s))};\n return {type: \"fr\", value: 1};\n}\n\n/**\n * Convert CSS-like spec strings to an array of ratio values (summing to 100 for\n * explicit tracks; auto tracks get 0). The ratios are later rendered as `{v}fr`\n * units in the CSS grid template so the browser distributes available space\n * proportionally after fixed tracks (px, dividers) are allocated.\n */\nfunction parseSpecs(\n specs: string[] | undefined,\n count: number,\n containerPx: number,\n): number[] {\n if (!specs?.length || specs.length !== count) {\n return new Array(count).fill(100 / count);\n }\n\n const parsed = specs.map(parseWidthSpec);\n\n const totalPxAsPct =\n containerPx > 0\n ? parsed.reduce(\n (s, p) => s + (p.type === \"px\" ? (p.value / containerPx) * 100 : 0),\n 0,\n )\n : 0;\n const totalExplicitPct = parsed.reduce(\n (s, p) => s + (p.type === \"pct\" ? p.value : 0),\n 0,\n );\n const remainingPct = Math.max(0, 100 - totalPxAsPct - totalExplicitPct);\n const totalFr = parsed.reduce(\n (s, p) => s + (p.type === \"fr\" ? p.value : 0),\n 0,\n );\n\n const raw = parsed.map((p) => {\n if (p.type === \"auto\") return 0;\n if (p.type === \"px\")\n return containerPx > 0 ? (p.value / containerPx) * 100 : 0;\n if (p.type === \"pct\") return p.value;\n return totalFr > 0 ? (p.value / totalFr) * remainingPct : 0;\n });\n\n // Normalise so explicit entries sum to 100 (auto entries stay 0).\n const explicitTotal = raw.reduce((s, v) => s + v, 0);\n if (explicitTotal <= 0) {\n const n = parsed.filter((p) => p.type !== \"auto\").length || count;\n return parsed.map((p) => (p.type === \"auto\" ? 0 : 100 / n));\n }\n return raw.map((v, i) =>\n parsed[i].type === \"auto\" ? 0 : (v / explicitTotal) * 100,\n );\n}\n\n/** Build CSS grid-template-columns / grid-template-rows string. */\nfunction buildTemplate(specs: string[], ratios: number[]): string {\n return specs\n .map((spec, i) => {\n const cell = spec === \"auto\" ? \"auto\" : `${ratios[i]}fr`;\n const addDivider =\n i < specs.length - 1 && spec !== \"auto\" && specs[i + 1] !== \"auto\";\n return addDivider ? `${cell} ${DIVIDER_PX}px` : cell;\n })\n .join(\" \");\n}\n\n/** Compute 1-based CSS grid track index for each cell column/row. */\nfunction computeCellTrackIndices(specs: string[]): number[] {\n let track = 1;\n return specs.map((spec, i) => {\n const idx = track;\n track++;\n if (i < specs.length - 1 && spec !== \"auto\" && specs[i + 1] !== \"auto\") {\n track++; // skip divider track\n }\n return idx;\n });\n}\n\n/**\n * CSS track index of the divider between cell[i] and cell[i+1].\n * Returns null when no divider should be rendered.\n */\nfunction dividerTrack(\n specs: string[],\n cellTracks: number[],\n i: number,\n): number | null {\n if (i >= specs.length - 1) return null;\n if (specs[i] === \"auto\" || specs[i + 1] === \"auto\") return null;\n return cellTracks[i] + 1;\n}\n\nconst clamp = (v: number, lo: number, hi: number) =>\n Math.min(hi, Math.max(lo, v));\n\n/** Update a pair of adjacent explicit tracks, keeping their combined ratio constant. */\nfunction makePairUpdater(\n ref: React.MutableRefObject<number[]>,\n setter: React.Dispatch<React.SetStateAction<number[]>>,\n minPct: number,\n) {\n return (divIdx: number, nextLeading: number) => {\n const curr = ref.current;\n const combined = curr[divIdx] + curr[divIdx + 1];\n const lo = Math.min(minPct, combined - minPct);\n const hi = Math.max(minPct, combined - minPct);\n const clamped = clamp(nextLeading, lo, hi);\n const next = [...curr];\n next[divIdx] = clamped;\n next[divIdx + 1] = combined - clamped;\n ref.current = next;\n setter(next);\n };\n}\n\nexport const ResizableGridComponent = (\n props: PiCardProps<ResizableGridProps>,\n): React.ReactNode => {\n const {\n cardName,\n cells = [],\n columnWidths,\n rowHeights,\n minColumnPercent = DEFAULT_MIN_PERCENT,\n minRowPercent = DEFAULT_MIN_PERCENT,\n className,\n dividerClassName,\n } = props;\n\n const numRows = cells.length;\n const numCols = cells[0]?.length ?? 0;\n\n const colSpecs = React.useMemo(\n () => columnWidths ?? new Array(numCols).fill(\"1fr\"),\n [columnWidths, numCols],\n );\n const rowSpecs = React.useMemo(\n () => rowHeights ?? new Array(numRows).fill(\"1fr\"),\n [rowHeights, numRows],\n );\n\n // Initialise with equal ratios; useLayoutEffect recalculates with real px.\n const [colRatios, setColRatios] = React.useState(() =>\n parseSpecs(colSpecs, numCols, 0),\n );\n const [rowRatios, setRowRatios] = React.useState(() =>\n parseSpecs(rowSpecs, numRows, 0),\n );\n const colRatiosRef = React.useRef(colRatios);\n const rowRatiosRef = React.useRef(rowRatios);\n const containerRef = React.useRef<HTMLDivElement | null>(null);\n const colCleanupRef = React.useRef<(() => void) | null>(null);\n const rowCleanupRef = React.useRef<(() => void) | null>(null);\n\n // Re-parse after mount so px specs can be resolved against actual container size.\n React.useLayoutEffect(() => {\n if (!containerRef.current) return;\n const {width, height} = containerRef.current.getBoundingClientRect();\n const nextCol = parseSpecs(colSpecs, numCols, width);\n const nextRow = parseSpecs(rowSpecs, numRows, height);\n colRatiosRef.current = nextCol;\n rowRatiosRef.current = nextRow;\n setColRatios(nextCol);\n setRowRatios(nextRow);\n }, [colSpecs, rowSpecs, numCols, numRows]);\n\n React.useEffect(\n () => () => {\n colCleanupRef.current?.();\n rowCleanupRef.current?.();\n },\n [],\n );\n\n const updateColPair = React.useMemo(\n () => makePairUpdater(colRatiosRef, setColRatios, minColumnPercent),\n [minColumnPercent],\n );\n const updateRowPair = React.useMemo(\n () => makePairUpdater(rowRatiosRef, setRowRatios, minRowPercent),\n [minRowPercent],\n );\n\n const handleColMouseDown = React.useCallback(\n (divIdx: number) => (e: React.MouseEvent) => {\n e.preventDefault();\n const startX = e.clientX;\n const startLeading = colRatiosRef.current[divIdx];\n const container = containerRef.current;\n if (!container) return;\n const {width} = container.getBoundingClientRect();\n if (width <= 0) return;\n const onMove = (ev: MouseEvent) =>\n updateColPair(\n divIdx,\n startLeading + ((ev.clientX - startX) / width) * 100,\n );\n const onUp = () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n colCleanupRef.current = null;\n };\n colCleanupRef.current?.();\n window.addEventListener(\"mousemove\", onMove);\n window.addEventListener(\"mouseup\", onUp);\n colCleanupRef.current = () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n };\n },\n [updateColPair],\n );\n\n const handleColKeyDown = React.useCallback(\n (divIdx: number) => (e: React.KeyboardEvent) => {\n const curr = colRatiosRef.current[divIdx];\n const combined = curr + colRatiosRef.current[divIdx + 1];\n const step = e.shiftKey ? 10 : 2;\n if (e.key === \"ArrowLeft\") {\n e.preventDefault();\n updateColPair(divIdx, curr - step);\n } else if (e.key === \"ArrowRight\") {\n e.preventDefault();\n updateColPair(divIdx, curr + step);\n } else if (e.key === \"Home\") {\n e.preventDefault();\n updateColPair(divIdx, minColumnPercent);\n } else if (e.key === \"End\") {\n e.preventDefault();\n updateColPair(divIdx, combined - minColumnPercent);\n }\n },\n [updateColPair, minColumnPercent],\n );\n\n const handleRowMouseDown = React.useCallback(\n (divIdx: number) => (e: React.MouseEvent) => {\n e.preventDefault();\n const startY = e.clientY;\n const startLeading = rowRatiosRef.current[divIdx];\n const container = containerRef.current;\n if (!container) return;\n const {height} = container.getBoundingClientRect();\n if (height <= 0) return;\n const onMove = (ev: MouseEvent) =>\n updateRowPair(\n divIdx,\n startLeading + ((ev.clientY - startY) / height) * 100,\n );\n const onUp = () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n rowCleanupRef.current = null;\n };\n rowCleanupRef.current?.();\n window.addEventListener(\"mousemove\", onMove);\n window.addEventListener(\"mouseup\", onUp);\n rowCleanupRef.current = () => {\n window.removeEventListener(\"mousemove\", onMove);\n window.removeEventListener(\"mouseup\", onUp);\n };\n },\n [updateRowPair],\n );\n\n const handleRowKeyDown = React.useCallback(\n (divIdx: number) => (e: React.KeyboardEvent) => {\n const curr = rowRatiosRef.current[divIdx];\n const combined = curr + rowRatiosRef.current[divIdx + 1];\n const step = e.shiftKey ? 10 : 2;\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n updateRowPair(divIdx, curr - step);\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n updateRowPair(divIdx, curr + step);\n } else if (e.key === \"Home\") {\n e.preventDefault();\n updateRowPair(divIdx, minRowPercent);\n } else if (e.key === \"End\") {\n e.preventDefault();\n updateRowPair(divIdx, combined - minRowPercent);\n }\n },\n [updateRowPair, minRowPercent],\n );\n\n if (numRows === 0 || numCols === 0) return null;\n\n const colTemplate = buildTemplate(colSpecs, colRatios);\n const rowTemplate = buildTemplate(rowSpecs, rowRatios);\n const colTracks = computeCellTrackIndices(colSpecs);\n const rowTracks = computeCellTrackIndices(rowSpecs);\n\n return (\n <div\n ref={containerRef}\n data-pihanga={cardName}\n className={clsx(\"pi-resizable-grid\", className)}\n style={\n {\n gridTemplateColumns: colTemplate,\n gridTemplateRows: rowTemplate,\n } as React.CSSProperties\n }\n >\n {/* Grid cells */}\n {cells.map((row, ri) =>\n row.map((card, ci) => (\n <div\n key={`c${ri}-${ci}`}\n className=\"pi-resizable-grid-cell\"\n style={{gridColumn: colTracks[ci], gridRow: rowTracks[ri]}}\n >\n <Card cardName={card} parentCard={cardName} />\n </div>\n )),\n )}\n\n {/* Vertical column dividers — each spans all row tracks */}\n {colSpecs.map((_, ci) => {\n const track = dividerTrack(colSpecs, colTracks, ci);\n if (track === null) return null;\n return (\n <div\n key={`cd${ci}`}\n className={clsx(\"pi-resizable-grid-col-divider\", dividerClassName)}\n style={{gridColumn: track, gridRow: \"1 / -1\"}}\n onMouseDown={handleColMouseDown(ci)}\n onKeyDown={handleColKeyDown(ci)}\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={`Resize columns ${ci + 1} and ${ci + 2}`}\n aria-valuenow={Math.round(colRatios[ci])}\n tabIndex={0}\n />\n );\n })}\n\n {/* Horizontal row dividers — each spans all column tracks */}\n {rowSpecs.map((_, ri) => {\n const track = dividerTrack(rowSpecs, rowTracks, ri);\n if (track === null) return null;\n return (\n <div\n key={`rd${ri}`}\n className={clsx(\"pi-resizable-grid-row-divider\", dividerClassName)}\n style={{gridRow: track, gridColumn: \"1 / -1\"}}\n onMouseDown={handleRowMouseDown(ri)}\n onKeyDown={handleRowKeyDown(ri)}\n role=\"separator\"\n aria-orientation=\"horizontal\"\n aria-label={`Resize rows ${ri + 1} and ${ri + 2}`}\n aria-valuenow={Math.round(rowRatios[ri])}\n tabIndex={0}\n />\n );\n })}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.pi-resizable-grid{width:100%;height:100%;display:grid}.pi-resizable-grid-cell{min-width:0;min-height:0;overflow:hidden}.pi-resizable-grid-col-divider{cursor:col-resize;z-index:1;min-height:0;position:relative}.pi-resizable-grid-col-divider:before{content:"";width:var(--rg-divider-width,1px);background-color:var(--rg-divider-color,var(--border));transition:width .12s,background-color .12s;position:absolute;top:0;bottom:0;left:50%;transform:translate(-50%)}.pi-resizable-grid-col-divider:hover:before,.pi-resizable-grid-col-divider:focus-visible:before{width:var(--rg-divider-hover-width,4px);background-color:var(--rg-divider-hover-color,var(--muted-foreground))}.pi-resizable-grid-row-divider{cursor:row-resize;z-index:1;min-width:0;position:relative}.pi-resizable-grid-row-divider:before{content:"";height:var(--rg-divider-width,1px);background-color:var(--rg-divider-color,var(--border));transition:height .12s,background-color .12s;position:absolute;top:50%;left:0;right:0;transform:translateY(-50%)}.pi-resizable-grid-row-divider:hover:before,.pi-resizable-grid-row-divider:focus-visible:before{height:var(--rg-divider-hover-width,4px);background-color:var(--rg-divider-hover-color,var(--muted-foreground))}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PiCardRef } from '@pihanga2/core';
|
|
2
|
+
export declare const RESIZABLE_GRID_CARD = "pi/resizable-grid";
|
|
3
|
+
export type ResizableGridProps = {
|
|
4
|
+
/**
|
|
5
|
+
* 2D array of cards — cells[row][col]. Must be rectangular (all rows same length).
|
|
6
|
+
*/
|
|
7
|
+
cells: PiCardRef[][];
|
|
8
|
+
/**
|
|
9
|
+
* Initial column widths using CSS-like values:
|
|
10
|
+
* - '200px' — fixed initial width (resolved against container width at mount)
|
|
11
|
+
* - '30%' — explicit percentage of container
|
|
12
|
+
* - '1fr' — fraction of remaining space after px/% columns
|
|
13
|
+
* - 'auto' — natural content width; no drag handle rendered for adjacent boundaries
|
|
14
|
+
*
|
|
15
|
+
* Defaults to equal `1fr` split when omitted.
|
|
16
|
+
*/
|
|
17
|
+
columnWidths?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* Initial row heights using the same syntax as columnWidths.
|
|
20
|
+
* 'auto' rows follow content height (max across all cells in that row).
|
|
21
|
+
* No drag handle is rendered between two rows unless both are explicit (non-auto).
|
|
22
|
+
*
|
|
23
|
+
* Defaults to equal `1fr` split when omitted.
|
|
24
|
+
*/
|
|
25
|
+
rowHeights?: string[];
|
|
26
|
+
/** Minimum percentage any explicit column can shrink to during drag. Default: 10 */
|
|
27
|
+
minColumnPercent?: number;
|
|
28
|
+
/** Minimum percentage any explicit row can shrink to during drag. Default: 10 */
|
|
29
|
+
minRowPercent?: number;
|
|
30
|
+
className?: string;
|
|
31
|
+
dividerClassName?: string;
|
|
32
|
+
};
|
|
33
|
+
export declare const ResizableGrid: <S extends import('@pihanga2/core').ReduxState>(p: import('@pihanga2/core').PiMapProps<ResizableGridProps, S, {}>) => import('@pihanga2/core').PiCardDef;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createCardDeclaration as e } from "@pihanga2/core";
|
|
2
|
+
//#region src/cards/resizableGrid/resizableGrid.types.ts
|
|
3
|
+
var t = "pi/resizable-grid", n = e(t);
|
|
4
|
+
//#endregion
|
|
5
|
+
export { t as RESIZABLE_GRID_CARD, n as ResizableGrid };
|
|
6
|
+
|
|
7
|
+
//# sourceMappingURL=resizableGrid.types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resizableGrid.types.js","names":[],"sources":["../../../src/cards/resizableGrid/resizableGrid.types.ts"],"sourcesContent":["import {createCardDeclaration, type PiCardRef} from \"@pihanga2/core\";\n\nexport const RESIZABLE_GRID_CARD = \"pi/resizable-grid\";\n\nexport type ResizableGridProps = {\n /**\n * 2D array of cards — cells[row][col]. Must be rectangular (all rows same length).\n */\n cells: PiCardRef[][];\n /**\n * Initial column widths using CSS-like values:\n * - '200px' — fixed initial width (resolved against container width at mount)\n * - '30%' — explicit percentage of container\n * - '1fr' — fraction of remaining space after px/% columns\n * - 'auto' — natural content width; no drag handle rendered for adjacent boundaries\n *\n * Defaults to equal `1fr` split when omitted.\n */\n columnWidths?: string[];\n /**\n * Initial row heights using the same syntax as columnWidths.\n * 'auto' rows follow content height (max across all cells in that row).\n * No drag handle is rendered between two rows unless both are explicit (non-auto).\n *\n * Defaults to equal `1fr` split when omitted.\n */\n rowHeights?: string[];\n /** Minimum percentage any explicit column can shrink to during drag. Default: 10 */\n minColumnPercent?: number;\n /** Minimum percentage any explicit row can shrink to during drag. Default: 10 */\n minRowPercent?: number;\n className?: string;\n dividerClassName?: string;\n};\n\nexport const ResizableGrid =\n createCardDeclaration<ResizableGridProps>(RESIZABLE_GRID_CARD);\n"],"mappings":";;AAEA,IAAa,IAAsB,qBAiCtB,IACX,EAA0C,CAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.swa-root{flex-direction:column;min-height:0;display:flex}.swa-content-wrapper{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.swa-scroll-container{flex:1;min-height:0;overflow:auto}.swa-scroll-container::-webkit-scrollbar{width:14px}.swa-scroll-container::-webkit-scrollbar-track{background:var(--
|
|
1
|
+
.swa-root{flex-direction:column;min-height:0;display:flex}.swa-content-wrapper{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.swa-scroll-container{flex:1;min-height:0;overflow:auto}.swa-scroll-container::-webkit-scrollbar{width:14px}.swa-scroll-container::-webkit-scrollbar-track{background:var(--muted)}.swa-scroll-container::-webkit-scrollbar-thumb{background:var(--border);border-radius:var(--radius-sm);border:2px solid var(--background)}.swa-overlay{pointer-events:none;z-index:1;width:12px;height:100%;position:absolute;top:0;right:2px}.swa-marker{cursor:pointer;pointer-events:auto;transition:opacity .15s,transform .15s,box-shadow .15s;position:absolute;left:0}:root{--swa-point-size:10px}.swa-marker-point{width:var(--swa-point-size);height:var(--swa-point-size);border:2px solid var(--background);box-shadow:0 0 4px color-mix(in srgb, var(--foreground) 30%, transparent);border-radius:50%;left:2px;transform:translateY(-50%)}.swa-marker-point.swa-marker--hover{transform:translateY(-50%)scale(1.5)}:root{--swa-bar-width:10px;--swa-bar-height:10px}.swa-marker-bar{width:var(--swa-bar-width);height:var(--swa-bar-height);opacity:.8;border-radius:1px;transform:translateY(-50%)}.swa-marker-bar.swa-marker--hover{opacity:1;box-shadow:0 0 4px color-mix(in srgb, var(--foreground) 40%, transparent)}:root{--swa-range-width:8px;--swa-range-min-height:8px}.swa-marker-range{width:var(--swa-range-width);min-height:var(--swa-range-min-height);opacity:.4;border-left:2px solid var(--background);box-shadow:inset 0 0 2px color-mix(in srgb, var(--foreground) 20%, transparent);border-radius:2px}.swa-marker-range.swa-marker--hover{opacity:.65;box-shadow:0 0 6px color-mix(in srgb, var(--foreground) 40%, transparent)}:root{--swa-color-error:var(--destructive);--swa-color-warning:oklch(75% .15 70);--swa-color-todo:oklch(55% .15 250);--swa-color-comment:oklch(65% .04 220);--swa-color-review:oklch(60% .04 100);--swa-color-fixme:oklch(60% .12 55)}.swa-type-error{background:var(--swa-color-error)}.swa-type-warning{background:var(--swa-color-warning)}.swa-type-todo{background:var(--swa-color-todo)}.swa-type-comment{background:var(--swa-color-comment)}.swa-type-review{background:var(--swa-color-review)}.swa-type-fixme{background:var(--swa-color-fixme)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './suspense.types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SUSPENSE_CARD as e, Suspense as t } from "./suspense.types.js";
|
|
2
|
+
import { SuspenseComponent as n } from "./suspense.component.js";
|
|
3
|
+
import { registerCardComponent as r } from "@pihanga2/core";
|
|
4
|
+
//#region src/cards/suspense/index.ts
|
|
5
|
+
r({
|
|
6
|
+
name: e,
|
|
7
|
+
component: n
|
|
8
|
+
});
|
|
9
|
+
//#endregion
|
|
10
|
+
export { e as SUSPENSE_CARD, t as Suspense };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/suspense/index.ts"],"sourcesContent":["import {registerCardComponent} from \"@pihanga2/core\";\nimport {SUSPENSE_CARD} from \"./suspense.types\";\nimport {SuspenseComponent} from \"./suspense.component\";\n\nexport * from \"./suspense.types\";\n\n// No events — structural card.\nregisterCardComponent({\n name: SUSPENSE_CARD,\n component: SuspenseComponent,\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;AACb,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PiCardProps } from '@pihanga2/core';
|
|
2
|
+
import { SuspenseProps } from './suspense.types';
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
/**
|
|
5
|
+
* SuspenseComponent
|
|
6
|
+
*
|
|
7
|
+
* Wraps `content` in a React `<Suspense>` boundary so it commits and paints
|
|
8
|
+
* independently of the rest of the tree.
|
|
9
|
+
*
|
|
10
|
+
* When `content`'s registered component is code-split with `React.lazy`, this
|
|
11
|
+
* boundary catches the thrown promise and shows either:
|
|
12
|
+
* • a custom `fallback` card (when the `fallback` prop is set), or
|
|
13
|
+
* • an animated shimmer skeleton (default).
|
|
14
|
+
*/
|
|
15
|
+
export declare const SuspenseComponent: (props: PiCardProps<SuspenseProps>) => React.ReactNode;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Suspense as e } from "react";
|
|
2
|
+
import { Card as t } from "@pihanga2/core";
|
|
3
|
+
import { jsx as n } from "react/jsx-runtime";
|
|
4
|
+
//#region src/cards/suspense/suspense.component.tsx
|
|
5
|
+
var r = {
|
|
6
|
+
xs: "h-3",
|
|
7
|
+
sm: "h-5",
|
|
8
|
+
md: "h-10",
|
|
9
|
+
lg: "h-14",
|
|
10
|
+
xl: "h-20"
|
|
11
|
+
}, i = {
|
|
12
|
+
sm: "gap-2",
|
|
13
|
+
md: "gap-3",
|
|
14
|
+
lg: "gap-4"
|
|
15
|
+
};
|
|
16
|
+
function a({ rows: e = 3, rowSize: t = "md", spacing: a = "md", rowClassName: o, className: s }) {
|
|
17
|
+
let c = o ?? `rounded-md bg-foreground/15 ${r[t ?? "md"]}`;
|
|
18
|
+
return /* @__PURE__ */ n("div", {
|
|
19
|
+
className: s ?? `flex w-full flex-col ${i[a ?? "md"]}`,
|
|
20
|
+
children: Array.from({ length: e }).map((e, t) => /* @__PURE__ */ n("div", { className: `animate-pulse ${c}` }, t))
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
var o = (r) => {
|
|
24
|
+
let { cardName: i, content: o, fallback: s, rows: c, rowSize: l, spacing: u, rowClassName: d, className: f } = r;
|
|
25
|
+
return /* @__PURE__ */ n(e, {
|
|
26
|
+
fallback: s ? /* @__PURE__ */ n(t, {
|
|
27
|
+
cardName: s,
|
|
28
|
+
parentCard: i
|
|
29
|
+
}) : /* @__PURE__ */ n(a, {
|
|
30
|
+
rows: c,
|
|
31
|
+
rowSize: l,
|
|
32
|
+
spacing: u,
|
|
33
|
+
rowClassName: d,
|
|
34
|
+
className: f
|
|
35
|
+
}),
|
|
36
|
+
children: /* @__PURE__ */ n(t, {
|
|
37
|
+
cardName: o,
|
|
38
|
+
parentCard: i
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { o as SuspenseComponent };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=suspense.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suspense.component.js","names":[],"sources":["../../../src/cards/suspense/suspense.component.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {Suspense} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport type {SuspenseProps} from \"./suspense.types\";\n\n// ── Preset maps (mirrors loading-skeleton) ─────────────────────────────────────\n\nconst ROW_SIZE_CLASS: Record<string, string> = {\n xs: \"h-3\",\n sm: \"h-5\",\n md: \"h-10\",\n lg: \"h-14\",\n xl: \"h-20\",\n};\n\nconst SPACING_CLASS: Record<string, string> = {\n sm: \"gap-2\",\n md: \"gap-3\",\n lg: \"gap-4\",\n};\n\n// ── Inline skeleton (used when no `fallback` card is provided) ─────────────────\n\nfunction InlineSkeleton({\n rows = 3,\n rowSize = \"md\",\n spacing = \"md\",\n rowClassName,\n className,\n}: Pick<\n SuspenseProps,\n \"rows\" | \"rowSize\" | \"spacing\" | \"rowClassName\" | \"className\"\n>) {\n const rowCls =\n rowClassName ??\n `rounded-md bg-foreground/15 ${ROW_SIZE_CLASS[rowSize ?? \"md\"]}`;\n const wrapperCls =\n className ?? `flex w-full flex-col ${SPACING_CLASS[spacing ?? \"md\"]}`;\n\n return (\n <div className={wrapperCls}>\n {Array.from({length: rows}).map((_, i) => (\n <div key={i} className={`animate-pulse ${rowCls}`} />\n ))}\n </div>\n );\n}\n\n// ── Component ──────────────────────────────────────────────────────────────────\n\n/**\n * SuspenseComponent\n *\n * Wraps `content` in a React `<Suspense>` boundary so it commits and paints\n * independently of the rest of the tree.\n *\n * When `content`'s registered component is code-split with `React.lazy`, this\n * boundary catches the thrown promise and shows either:\n * • a custom `fallback` card (when the `fallback` prop is set), or\n * • an animated shimmer skeleton (default).\n */\nexport const SuspenseComponent = (\n props: PiCardProps<SuspenseProps>,\n): React.ReactNode => {\n const {\n cardName,\n content,\n fallback,\n rows,\n rowSize,\n spacing,\n rowClassName,\n className,\n } = props;\n\n const fallbackNode = fallback ? (\n <Card cardName={fallback} parentCard={cardName} />\n ) : (\n <InlineSkeleton\n rows={rows}\n rowSize={rowSize}\n spacing={spacing}\n rowClassName={rowClassName}\n className={className}\n />\n );\n\n return (\n <Suspense fallback={fallbackNode}>\n <Card cardName={content} parentCard={cardName} />\n </Suspense>\n );\n};\n"],"mappings":";;;;AAOA,IAAM,IAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN,GAEM,IAAwC;CAC5C,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAIA,SAAS,EAAe,EACtB,UAAO,GACP,aAAU,MACV,aAAU,MACV,iBACA,gBAIC;CACD,IAAM,IACJ,KACA,+BAA+B,EAAe,KAAW;CAI3D,OACE,kBAAC,OAAD;EAAK,WAHL,KAAa,wBAAwB,EAAc,KAAW;YAI3D,MAAM,KAAK,EAAC,QAAQ,EAAI,CAAC,EAAE,KAAK,GAAG,MAClC,kBAAC,OAAD,EAAa,WAAW,iBAAiB,IAAW,GAA1C,CAA0C,CACrD;CACE,CAAA;AAET;AAeA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,aACA,YACA,aACA,SACA,YACA,YACA,iBACA,iBACE;CAcJ,OACE,kBAAC,GAAD;EAAU,UAbS,IACnB,kBAAC,GAAD;GAAM,UAAU;GAAU,YAAY;EAAW,CAAA,IAEjD,kBAAC,GAAD;GACQ;GACG;GACA;GACK;GACH;EACZ,CAAA;YAKC,kBAAC,GAAD;GAAM,UAAU;GAAS,YAAY;EAAW,CAAA;CACxC,CAAA;AAEd"}
|