@pihanga2/shadcn 0.2.3 → 0.2.5

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/README.md CHANGED
@@ -62,7 +62,7 @@ import "@pihanga2/shadcn/cards/dataTable";
62
62
 
63
63
  ---
64
64
 
65
- ## Included cards (33)
65
+ ## Included cards (35)
66
66
 
67
67
  - `badge`
68
68
  - `box`
@@ -78,6 +78,7 @@ import "@pihanga2/shadcn/cards/dataTable";
78
78
  - `form`
79
79
  - `framework`
80
80
  - `input`
81
+ - `jsonViewer`
81
82
  - `list`
82
83
  - `loadingOverlay`
83
84
  - `loadingSkeleton`
@@ -86,6 +87,7 @@ import "@pihanga2/shadcn/cards/dataTable";
86
87
  - `navbarSearch`
87
88
  - `pageWithNavbar`
88
89
  - `pasteTarget`
90
+ - `resizable`
89
91
  - `select`
90
92
  - `slider`
91
93
  - `sliderValue`
@@ -47,6 +47,55 @@ export type PiChartSeries = {
47
47
  */
48
48
  fillOpacity?: number;
49
49
  };
50
+ /**
51
+ * A single recharts `<ReferenceArea>` to overlay on the chart.
52
+ * Highlights a rectangular region between two x or y coordinates.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Highlight a y-range (horizontal band)
57
+ * { y1: 100, y2: 200, fill: "var(--chart-3)", fillOpacity: 0.2, label: "Target zone" }
58
+ *
59
+ * // Highlight an x-range (vertical band)
60
+ * { x1: "Feb", x2: "Apr", fill: "var(--chart-2)", fillOpacity: 0.15 }
61
+ * ```
62
+ */
63
+ export type PiChartReferenceArea = {
64
+ /** Start value on the **x-axis**. Omit for a y-only band. */
65
+ x1?: string | number;
66
+ /** End value on the **x-axis**. Omit for a y-only band. */
67
+ x2?: string | number;
68
+ /** Start value on the **y-axis**. Omit for an x-only band. */
69
+ y1?: number;
70
+ /** End value on the **y-axis**. Omit for an x-only band. */
71
+ y2?: number;
72
+ /**
73
+ * Label rendered inside the area. Pass a plain string or a
74
+ * `{ value, position, fill, fontSize }` object for full control.
75
+ */
76
+ label?: string | {
77
+ value: string;
78
+ position?: string;
79
+ fill?: string;
80
+ fontSize?: number;
81
+ };
82
+ /** CSS fill colour. Defaults to `"var(--chart-1)"`. */
83
+ fill?: string;
84
+ /** Fill opacity, 0–1. Defaults to `0.2`. */
85
+ fillOpacity?: number;
86
+ /** CSS colour for the area border stroke. */
87
+ stroke?: string;
88
+ /** SVG `stroke-dasharray` for the area border. */
89
+ strokeDasharray?: string;
90
+ /** Area border width in pixels. */
91
+ strokeWidth?: number;
92
+ /**
93
+ * Behaviour when the area extends beyond the chart boundary.
94
+ * `"discard"` | `"hidden"` | `"visible"` | `"extendDomain"`.
95
+ * Defaults to `"discard"`.
96
+ */
97
+ ifOverflow?: "discard" | "hidden" | "visible" | "extendDomain";
98
+ };
50
99
  /**
51
100
  * A single recharts `<ReferenceLine>` to overlay on the chart.
52
101
  * Specify either `x` (vertical line) or `y` (horizontal line).
@@ -218,6 +267,20 @@ export type PiChartGraphProps = {
218
267
  * ```
219
268
  */
220
269
  referenceLines?: PiChartReferenceLine[];
270
+ /**
271
+ * One or more reference areas to overlay on the chart.
272
+ * Each entry maps directly to a recharts `<ReferenceArea>` and highlights
273
+ * a rectangular region (x-band, y-band, or both).
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * referenceAreas: [
278
+ * { y1: 100, y2: 200, fill: "var(--chart-3)", fillOpacity: 0.15, label: "Target" },
279
+ * { x1: "Feb", x2: "Apr", fill: "var(--chart-4)", fillOpacity: 0.1 },
280
+ * ]
281
+ * ```
282
+ */
283
+ referenceAreas?: PiChartReferenceArea[];
221
284
  /** Extra Tailwind / CSS classes forwarded to the outermost element. */
222
285
  className?: string;
223
286
  };
@@ -208,12 +208,87 @@ export type GraphinProps = {
208
208
  * @default "style"
209
209
  */
210
210
  nodeStyleKey?: string;
211
+ /**
212
+ * Legend overlay rendered as an absolutely-positioned panel over the graph.
213
+ *
214
+ * Two content modes:
215
+ * - **`items`** — built-in swatch list (colour + shape + label).
216
+ * - **`cardName`** — any registered Pihanga card used as a fully custom legend.
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * legend: {
221
+ * position: "bottom-left",
222
+ * items: [
223
+ * { label: "Gateway", fill: "#e67e22", shape: "star" },
224
+ * { label: "Service", fill: "#2980b9", shape: "circle" },
225
+ * { label: "Database", fill: "#8e44ad", shape: "rect" },
226
+ * ],
227
+ * }
228
+ * ```
229
+ */
230
+ legend?: GraphinLegend;
211
231
  style?: {
212
232
  root?: React.CSSProperties;
213
233
  item?: React.CSSProperties;
214
234
  };
215
235
  className?: string;
216
236
  };
237
+ /**
238
+ * A single item in the built-in legend.
239
+ */
240
+ export type GraphinLegendItem = {
241
+ /** Text label displayed beside the shape swatch. */
242
+ label: string;
243
+ /** Fill colour of the swatch. Defaults to `"#4793AF"`. */
244
+ fill?: string;
245
+ /** Stroke / border colour of the swatch. */
246
+ stroke?: string;
247
+ /**
248
+ * Shape of the swatch marker. Mirrors the values accepted by `nodeStyles`.
249
+ * Use `"line"` for edge/connector legend entries.
250
+ * @default "circle"
251
+ */
252
+ shape?: "circle" | "rect" | "diamond" | "star" | "triangle" | "hexagon" | "ellipse" | "line";
253
+ };
254
+ /**
255
+ * Legend overlay configuration.
256
+ *
257
+ * Two mutually-exclusive content modes:
258
+ * - **`items`** — serialisable list of `{ label, fill, stroke, shape }` entries
259
+ * rendered as a built-in swatch list.
260
+ * - **`cardName`** — name of any registered Pihanga card whose component is
261
+ * rendered as the legend body. Takes priority over `items` when both are set.
262
+ * Use this for fully custom legends (images, interactive filters, etc.).
263
+ */
264
+ export type GraphinLegend = {
265
+ /**
266
+ * Built-in legend items — each rendered as a small SVG shape swatch + label.
267
+ * Ignored when `cardName` is also provided.
268
+ */
269
+ items?: GraphinLegendItem[];
270
+ /**
271
+ * Registered pihanga card name to use as the legend body.
272
+ * The card is rendered inside the legend wrapper div; it receives no special
273
+ * context props — source its data from Redux state as usual.
274
+ */
275
+ cardName?: string;
276
+ /**
277
+ * Corner of the graph container where the legend is anchored.
278
+ * @default "bottom-left"
279
+ */
280
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
281
+ /**
282
+ * Extra CSS class name(s) applied to the legend wrapper `<div>`.
283
+ * @example "rounded-xl shadow-lg"
284
+ */
285
+ className?: string;
286
+ /**
287
+ * Inline style overrides for the legend wrapper `<div>`.
288
+ * Merged on top of the built-in positioning styles.
289
+ */
290
+ style?: React.CSSProperties;
291
+ };
217
292
  export type GraphinTooltip = {
218
293
  /** Registered card name whose component is rendered inside the floating panel. */
219
294
  node?: string;
@@ -331,6 +406,7 @@ export declare const GRAPHIN_OP_ACTION: {
331
406
  CLEAR_NODE_STYLES: string;
332
407
  ZOOM_TO_NODE: string;
333
408
  UPDATE_NODE_STYLE_MAP: string;
409
+ FIT_VIEW: string;
334
410
  };
335
411
  /**
336
412
  * Payload for `GRAPHIN_OP_ACTION.SET_NODE_STYLES`.
@@ -470,6 +546,36 @@ export type GraphinUpdateNodeStyleMapAction = {
470
546
  * ```
471
547
  */
472
548
  export declare const dispatchGraphinUpdateNodeStyleMap: (dispatch: DispatchF, payload: GraphinUpdateNodeStyleMapAction) => string;
549
+ /**
550
+ * Payload for `GRAPHIN_OP_ACTION.FIT_VIEW`.
551
+ *
552
+ * Fits (and optionally re-centres) the entire graph within the visible
553
+ * container — the same effect as the initial `autoFit: 'view'`.
554
+ */
555
+ export type GraphinFitViewAction = {
556
+ cardName: string;
557
+ /**
558
+ * - `"view"` — zoom + pan so all nodes fit inside the viewport (default).
559
+ * - `"center"` — pan to centre the graph without changing the zoom level.
560
+ */
561
+ mode?: "view" | "center";
562
+ };
563
+ /**
564
+ * Fit the entire graph into the available canvas area.
565
+ *
566
+ * - `mode: "view"` (default) — zoom + pan so every node is visible.
567
+ * - `mode: "center"` — pan to the graph centroid without changing zoom.
568
+ *
569
+ * @example
570
+ * ```ts
571
+ * // Zoom-to-fit after the user has panned/zoomed away
572
+ * dispatchGraphinFitView(dispatch, { cardName: "myGraph" });
573
+ *
574
+ * // Re-centre without changing zoom
575
+ * dispatchGraphinFitView(dispatch, { cardName: "myGraph", mode: "center" });
576
+ * ```
577
+ */
578
+ export declare const dispatchGraphinFitView: (dispatch: DispatchF, payload: GraphinFitViewAction) => string;
473
579
  export type { ReduxAction };
474
580
  export type GraphinEvents = {
475
581
  /** Fired when the pointer enters a node */
@@ -0,0 +1,6 @@
1
+ import { default as React } from 'react';
2
+ import { GraphinLegend } from './graphin.types';
3
+ export declare function LegendOverlay({ legend, parentCard, }: {
4
+ legend: GraphinLegend;
5
+ parentCard: string;
6
+ }): React.ReactNode;
@@ -0,0 +1,13 @@
1
+ import { JSON_VIEWER_ACTION as e, JSON_VIEWER_CARD as t, JsonViewer as n, onJsonViewerClicked as r, onJsonViewerCopied as i } from "./jsonViewer.types.js";
2
+ import { JsonViewerComponent as a } from "./jsonViewer.component.js";
3
+ import { actionTypesToEvents as o, registerCardComponent as s } from "@pihanga2/core";
4
+ //#region src/cards/jsonViewer/index.ts
5
+ s({
6
+ name: t,
7
+ component: a,
8
+ events: o(e)
9
+ });
10
+ //#endregion
11
+ export { e as JSON_VIEWER_ACTION, t as JSON_VIEWER_CARD, n as JsonViewer, r as onJsonViewerClicked, i as onJsonViewerCopied };
12
+
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/jsonViewer/index.ts"],"sourcesContent":["import {registerCardComponent, actionTypesToEvents} from \"@pihanga2/core\";\n\nimport {JSON_VIEWER_CARD, JSON_VIEWER_ACTION} from \"./jsonViewer.types\";\nimport {JsonViewerComponent} from \"./jsonViewer.component\";\n\nexport * from \"./jsonViewer.types\";\n\nregisterCardComponent({\n name: JSON_VIEWER_CARD,\n component: JsonViewerComponent,\n events: actionTypesToEvents(JSON_VIEWER_ACTION),\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;CACX,QAAQ,EAAoB,CAAkB;AAChD,CAAC"}
@@ -0,0 +1,89 @@
1
+ import { getIconElement as e } from "../icons.js";
2
+ /* empty css */
3
+ /* empty css */
4
+ import { useCallback as t, useLayoutEffect as n, useRef as r, useState as i } from "react";
5
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
+ import { Check as s, Copy as c } from "lucide-react";
7
+ import { JsonView as l, allExpanded as u, defaultStyles as d } from "react-json-view-lite";
8
+ //#region src/cards/jsonViewer/jsonViewer.component.tsx
9
+ function f(e) {
10
+ if (e === !1) return u;
11
+ if (e === !0) return () => !1;
12
+ let t = e ?? 1;
13
+ return (e) => e < t;
14
+ }
15
+ var p = {
16
+ ...d,
17
+ container: "jv-container",
18
+ basicChildStyle: "jv-basic-child",
19
+ label: "jv-label",
20
+ clickableLabel: "jv-clickable-label",
21
+ nullValue: "jv-null",
22
+ undefinedValue: "jv-undefined",
23
+ numberValue: "jv-number",
24
+ stringValue: "jv-string",
25
+ booleanValue: "jv-boolean",
26
+ otherValue: "jv-other",
27
+ punctuation: "jv-punctuation",
28
+ expandIcon: "jv-expand-icon",
29
+ collapseIcon: "jv-collapse-icon",
30
+ collapsedContent: "jv-collapsed-content",
31
+ childFieldsContainer: "jv-child-fields-container"
32
+ };
33
+ function m(t) {
34
+ if (t) {
35
+ let n = e(t);
36
+ if (n) return n;
37
+ }
38
+ return c;
39
+ }
40
+ async function h(e) {
41
+ if (typeof ClipboardItem < "u") {
42
+ let t = new Blob([e], { type: "text/plain" });
43
+ await navigator.clipboard.write([new ClipboardItem({ "text/plain": t })]);
44
+ } else await navigator.clipboard.writeText(e);
45
+ }
46
+ var g = (e) => {
47
+ let { source: c, collapsed: u = 1, modifyFn: d, style: g, className: _, cardName: v, _cls: y, copyToClipboard: b, copyIcon: x, onCopied: S } = e, C = r(null), [w, T] = i(!1);
48
+ n(() => {
49
+ d && d(c, C.current);
50
+ }, [d, c]);
51
+ let E = f(u), D = t(async () => {
52
+ let e = JSON.stringify(c, null, 2), t = !1;
53
+ try {
54
+ await h(e), t = !0, T(!0), setTimeout(() => T(!1), 2e3);
55
+ } catch (e) {
56
+ console.warn("[JsonViewer] clipboard write failed:", e);
57
+ }
58
+ S?.({ success: t });
59
+ }, [c, S]), O = m(x), k = s;
60
+ return /* @__PURE__ */ o("div", {
61
+ className: [y("root", _), "jv-wrapper"].join(" "),
62
+ ref: C,
63
+ "data-pihanga": v,
64
+ style: g,
65
+ children: [b && /* @__PURE__ */ a("button", {
66
+ type: "button",
67
+ "aria-label": w ? "Copied!" : "Copy JSON to clipboard",
68
+ title: w ? "Copied!" : "Copy JSON to clipboard",
69
+ className: `jv-copy-btn${w ? " jv-copy-btn--done" : ""}`,
70
+ onClick: D,
71
+ children: w ? /* @__PURE__ */ a(k, {
72
+ size: 13,
73
+ strokeWidth: 2.5
74
+ }) : /* @__PURE__ */ a(O, {
75
+ size: 13,
76
+ strokeWidth: 2
77
+ })
78
+ }), /* @__PURE__ */ a(l, {
79
+ data: c,
80
+ shouldExpandNode: E,
81
+ style: p,
82
+ clickToExpandNode: !0
83
+ })]
84
+ });
85
+ };
86
+ //#endregion
87
+ export { g as JsonViewerComponent };
88
+
89
+ //# sourceMappingURL=jsonViewer.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonViewer.component.js","names":[],"sources":["../../../src/cards/jsonViewer/jsonViewer.component.tsx"],"sourcesContent":["import React, {useLayoutEffect, useRef, useState, useCallback} from \"react\";\nimport type {PiCardProps} from \"@pihanga2/core\";\nimport {JsonView, allExpanded, defaultStyles} from \"react-json-view-lite\";\nimport {Copy, Check} from \"lucide-react\";\nimport \"react-json-view-lite/dist/index.css\";\nimport \"./jsonViewer.css\";\n\nimport {getIconElement} from \"@/cards/icons\";\nimport type {JsonViewerProps, JsonViewerEvents} from \"./jsonViewer.types\";\n\n/**\n * Converts the `collapsed` prop (from the react-json-view API) into a\n * `shouldExpandNode` predicate for react-json-view-lite.\n *\n * - `false` → expand everything\n * - `true` → collapse everything\n * - number N → expand nodes at depth < N (root is depth 0)\n */\nfunction toShouldExpandNode(\n collapsed: boolean | number | undefined,\n): (level: number) => boolean {\n if (collapsed === false) return allExpanded;\n if (collapsed === true) return () => false;\n // default: collapsed = 1 → only root level expanded\n const depth = collapsed ?? 1;\n return (level: number) => level < depth;\n}\n\n/**\n * Compact, code-editor style for react-json-view-lite.\n *\n * The expand/collapse icon is positioned absolutely inside a fixed left gutter\n * (via padding-left on `.jv-basic-child`), so the opening `{` and closing `}`\n * always start at the same column — fixing the original misalignment where the\n * icon pushed the open-brace further right than the close-brace.\n */\nconst compactStyles = {\n // spread defaultStyles first to satisfy required fields (ariaLables, stringifyStringValues, …)\n ...defaultStyles,\n container: \"jv-container\",\n basicChildStyle: \"jv-basic-child\",\n label: \"jv-label\",\n clickableLabel: \"jv-clickable-label\",\n nullValue: \"jv-null\",\n undefinedValue: \"jv-undefined\",\n numberValue: \"jv-number\",\n stringValue: \"jv-string\",\n booleanValue: \"jv-boolean\",\n otherValue: \"jv-other\",\n punctuation: \"jv-punctuation\",\n expandIcon: \"jv-expand-icon\",\n collapseIcon: \"jv-collapse-icon\",\n collapsedContent: \"jv-collapsed-content\",\n childFieldsContainer: \"jv-child-fields-container\",\n};\n\n/**\n * Resolve the icon element to use for the copy button.\n *\n * Resolution order:\n * 1. `iconName` looked up in the global icon registry\n * 2. Lucide `Copy` as the built-in fallback\n */\nfunction resolveCopyIcon(iconName?: string): React.ElementType {\n if (iconName) {\n const registered = getIconElement(iconName);\n if (registered) return registered;\n }\n return Copy;\n}\n\n/** Write `text` to the clipboard, preferring `ClipboardItem` for explicit MIME. */\nasync function writeToClipboard(text: string): Promise<void> {\n if (typeof ClipboardItem !== \"undefined\") {\n const blob = new Blob([text], {type: \"text/plain\"});\n await navigator.clipboard.write([new ClipboardItem({\"text/plain\": blob})]);\n } else {\n // Fallback for browsers without ClipboardItem support\n await navigator.clipboard.writeText(text);\n }\n}\n\nexport const JsonViewerComponent = (\n props: PiCardProps<JsonViewerProps, JsonViewerEvents>,\n): React.ReactNode => {\n const {\n source,\n collapsed = 1,\n modifyFn,\n style,\n className,\n cardName,\n _cls,\n copyToClipboard,\n copyIcon,\n onCopied,\n } = props;\n\n const elRef = useRef<HTMLDivElement>(null);\n const [copied, setCopied] = useState(false);\n\n useLayoutEffect(() => {\n if (modifyFn) {\n modifyFn(source, elRef.current);\n }\n }, [modifyFn, source]);\n\n const shouldExpandNode = toShouldExpandNode(collapsed);\n\n const handleCopy = useCallback(async () => {\n const text = JSON.stringify(source, null, 2);\n let success = false;\n try {\n await writeToClipboard(text);\n success = true;\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n } catch (err) {\n console.warn(\"[JsonViewer] clipboard write failed:\", err);\n }\n onCopied?.({success});\n }, [source, onCopied]);\n\n const CopyIconEl = resolveCopyIcon(copyIcon);\n const ConfirmIconEl = Check;\n\n return (\n <div\n className={[_cls(\"root\", className), \"jv-wrapper\"].join(\" \")}\n ref={elRef}\n data-pihanga={cardName}\n style={style as React.CSSProperties | undefined}\n >\n {copyToClipboard && (\n <button\n type=\"button\"\n aria-label={copied ? \"Copied!\" : \"Copy JSON to clipboard\"}\n title={copied ? \"Copied!\" : \"Copy JSON to clipboard\"}\n className={`jv-copy-btn${copied ? \" jv-copy-btn--done\" : \"\"}`}\n onClick={handleCopy}\n >\n {copied ? (\n <ConfirmIconEl size={13} strokeWidth={2.5} />\n ) : (\n <CopyIconEl size={13} strokeWidth={2} />\n )}\n </button>\n )}\n <JsonView\n data={source as object | unknown[]}\n shouldExpandNode={shouldExpandNode}\n style={compactStyles}\n clickToExpandNode\n />\n </div>\n );\n};\n"],"mappings":";;;;;;;;AAkBA,SAAS,EACP,GAC4B;CAC5B,IAAI,MAAc,IAAO,OAAO;CAChC,IAAI,MAAc,IAAM,aAAa;CAErC,IAAM,IAAQ,KAAa;CAC3B,QAAQ,MAAkB,IAAQ;AACpC;AAUA,IAAM,IAAgB;CAEpB,GAAG;CACH,WAAW;CACX,iBAAiB;CACjB,OAAO;CACP,gBAAgB;CAChB,WAAW;CACX,gBAAgB;CAChB,aAAa;CACb,aAAa;CACb,cAAc;CACd,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,cAAc;CACd,kBAAkB;CAClB,sBAAsB;AACxB;AASA,SAAS,EAAgB,GAAsC;CAC7D,IAAI,GAAU;EACZ,IAAM,IAAa,EAAe,CAAQ;EAC1C,IAAI,GAAY,OAAO;CACzB;CACA,OAAO;AACT;AAGA,eAAe,EAAiB,GAA6B;CAC3D,IAAI,OAAO,gBAAkB,KAAa;EACxC,IAAM,IAAO,IAAI,KAAK,CAAC,CAAI,GAAG,EAAC,MAAM,aAAY,CAAC;EAClD,MAAM,UAAU,UAAU,MAAM,CAAC,IAAI,cAAc,EAAC,cAAc,EAAI,CAAC,CAAC,CAAC;CAC3E,OAEE,MAAM,UAAU,UAAU,UAAU,CAAI;AAE5C;AAEA,IAAa,KACX,MACoB;CACpB,IAAM,EACJ,WACA,eAAY,GACZ,aACA,UACA,cACA,aACA,SACA,oBACA,aACA,gBACE,GAEE,IAAQ,EAAuB,IAAI,GACnC,CAAC,GAAQ,KAAa,EAAS,EAAK;CAE1C,QAAsB;EACpB,AAAI,KACF,EAAS,GAAQ,EAAM,OAAO;CAElC,GAAG,CAAC,GAAU,CAAM,CAAC;CAErB,IAAM,IAAmB,EAAmB,CAAS,GAE/C,IAAa,EAAY,YAAY;EACzC,IAAM,IAAO,KAAK,UAAU,GAAQ,MAAM,CAAC,GACvC,IAAU;EACd,IAAI;GAIF,AAHA,MAAM,EAAiB,CAAI,GAC3B,IAAU,IACV,EAAU,EAAI,GACd,iBAAiB,EAAU,EAAK,GAAG,GAAI;EACzC,SAAS,GAAK;GACZ,QAAQ,KAAK,wCAAwC,CAAG;EAC1D;EACA,IAAW,EAAC,WAAO,CAAC;CACtB,GAAG,CAAC,GAAQ,CAAQ,CAAC,GAEf,IAAa,EAAgB,CAAQ,GACrC,IAAgB;CAEtB,OACE,kBAAC,OAAD;EACE,WAAW,CAAC,EAAK,QAAQ,CAAS,GAAG,YAAY,EAAE,KAAK,GAAG;EAC3D,KAAK;EACL,gBAAc;EACP;YAJT,CAMG,KACC,kBAAC,UAAD;GACE,MAAK;GACL,cAAY,IAAS,YAAY;GACjC,OAAO,IAAS,YAAY;GAC5B,WAAW,cAAc,IAAS,uBAAuB;GACzD,SAAS;aAER,IACC,kBAAC,GAAD;IAAe,MAAM;IAAI,aAAa;GAAM,CAAA,IAE5C,kBAAC,GAAD;IAAY,MAAM;IAAI,aAAa;GAAI,CAAA;EAEnC,CAAA,GAEV,kBAAC,GAAD;GACE,MAAM;GACY;GAClB,OAAO;GACP,mBAAA;EACD,CAAA,CACE;;AAET"}
@@ -0,0 +1,7 @@
1
+ import { createCardDeclaration as e, createOnAction as t, registerActions as n } from "@pihanga2/core";
2
+ //#region src/cards/jsonViewer/jsonViewer.types.ts
3
+ var r = "json-viewer", i = e(r), a = n(r, ["clicked", "copied"]), o = t(a.CLICKED), s = t(a.COPIED);
4
+ //#endregion
5
+ export { a as JSON_VIEWER_ACTION, r as JSON_VIEWER_CARD, i as JsonViewer, o as onJsonViewerClicked, s as onJsonViewerCopied };
6
+
7
+ //# sourceMappingURL=jsonViewer.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonViewer.types.js","names":[],"sources":["../../../src/cards/jsonViewer/jsonViewer.types.ts"],"sourcesContent":["import {\n createCardDeclaration,\n createOnAction,\n registerActions,\n} from \"@pihanga2/core\";\n\nexport const JSON_VIEWER_CARD = \"json-viewer\";\n\nexport const JsonViewer = createCardDeclaration<\n JsonViewerProps,\n JsonViewerEvents\n>(JSON_VIEWER_CARD);\n\nexport const JSON_VIEWER_ACTION = registerActions(JSON_VIEWER_CARD, [\n \"clicked\",\n \"copied\",\n]);\n\nexport const onJsonViewerClicked = createOnAction<ClickedEvent>(\n JSON_VIEWER_ACTION.CLICKED,\n);\n\nexport const onJsonViewerCopied = createOnAction<CopiedEvent>(\n JSON_VIEWER_ACTION.COPIED,\n);\n\nexport type JsonViewerProps<S = object> = {\n /** The JSON value to display. */\n source: unknown;\n /**\n * Theme name. Maps loosely to a visual style.\n * Use \"monokai\", \"solarized\", \"ocean\", etc. (visual only — actual theming\n * is done via className / global CSS since react-json-view-lite uses CSS\n * variables rather than named themes).\n */\n theme?: string;\n iconStyle?: \"circle\" | \"triangle\" | \"square\";\n /**\n * When `true`, all nodes are collapsed.\n * When `false`, all nodes are expanded.\n * When a number N, nodes at depth >= N are collapsed (root is depth 0).\n * Default: 1 (expand root only).\n */\n collapsed?: boolean | number;\n /** When `true`, a click-to-expand interaction is enabled on node labels. */\n enableClipboard?: boolean;\n /** When `true`, removes quotes from object keys in display. */\n removeQuotesOnKeys?: boolean;\n /** When `true`, objects and arrays are labelled with their size. */\n displayObjectSize?: boolean;\n /** When `true`, data-type labels prefix values. */\n displayDataTypes?: boolean;\n /**\n * Called after the JSON is rendered, allowing post-render DOM modifications.\n * Receives the raw `source` value and the container element.\n */\n modifyFn?: ModifyFn;\n\n /**\n * When `true`, shows a copy-to-clipboard icon button in the top-right\n * corner of the viewer. Clicking it copies the pretty-printed JSON to the\n * clipboard using `ClipboardItem` with `text/plain` MIME type.\n */\n copyToClipboard?: boolean;\n\n /**\n * Icon name resolved via the global icon registry (`getIconElement`).\n * If omitted or the name is not registered, falls back to the Lucide\n * `Copy` icon automatically.\n *\n * @example\n * // Register your own icon first:\n * registerIcon(\"my-copy\", MyCustomCopyIcon);\n * // Then pass the name to the card:\n * JsonViewer({ copyToClipboard: true, copyIcon: \"my-copy\", ... })\n */\n copyIcon?: string;\n\n /** Inline style for the react-json-view-lite container. */\n style?: S;\n /** Additional CSS class for the outer wrapper. */\n className?: string;\n};\n\nexport type ModifyFn = (source: unknown, el: HTMLElement | null) => void;\n\nexport type ClickedEvent = Record<string, never>;\n\nexport type CopiedEvent = {\n /** `true` when the clipboard write succeeded, `false` on error. */\n success: boolean;\n};\n\nexport type JsonViewerEvents = {\n onClicked: ClickedEvent;\n onCopied: CopiedEvent;\n};\n"],"mappings":";;AAMA,IAAa,IAAmB,eAEnB,IAAa,EAGxB,CAAgB,GAEL,IAAqB,EAAgB,GAAkB,CAClE,WACA,QACF,CAAC,GAEY,IAAsB,EACjC,EAAmB,OACrB,GAEa,IAAqB,EAChC,EAAmB,MACrB"}
@@ -0,0 +1,12 @@
1
+ import { DefResizableHandle as e, RESIZABLE_CARD as t, Resizable as n, SdResizable as r } from "./resizable.types.js";
2
+ import { ResizableComponent as i } from "./resizable.component.js";
3
+ import { registerCardComponent as a } from "@pihanga2/core";
4
+ //#region src/cards/resizable/index.ts
5
+ a({
6
+ name: t,
7
+ component: i
8
+ });
9
+ //#endregion
10
+ export { e as DefResizableHandle, t as RESIZABLE_CARD, n as Resizable, r as SdResizable };
11
+
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/resizable/index.ts"],"sourcesContent":["import {registerCardComponent} from \"@pihanga2/core\";\n\nimport {ResizableComponent} from \"./resizable.component\";\nimport {RESIZABLE_CARD} from \"./resizable.types\";\n\nexport * from \"./resizable.types\";\n\nregisterCardComponent({\n name: RESIZABLE_CARD,\n component: ResizableComponent,\n});\n"],"mappings":";;;;AAOA,EAAsB;CACpB,MAAM;CACN,WAAW;AACb,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { ResizableHandle as e, ResizablePanel as t, ResizablePanelGroup as n } from "../../components/ui/resizable.js";
2
+ import { DefResizableHandle as r } from "./resizable.types.js";
3
+ import { Fragment as i } from "react";
4
+ import { Card as a } from "@pihanga2/core";
5
+ import { clsx as o } from "clsx";
6
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
7
+ //#region src/cards/resizable/resizable.component.tsx
8
+ var l = (l) => {
9
+ let { content: u, handles: d, direction: f, className: p, style: m, cardName: h } = l, g = m ?? {}, _ = Array.isArray(d) ? d : Array(u.length - 1).fill(d ?? r);
10
+ function v(e, n) {
11
+ let r = e.name ?? `panel${n}`, l = {
12
+ defaultSize: e.defaultSize ?? 50,
13
+ collapsible: e.collapsible
14
+ };
15
+ return e.maxSize !== void 0 && (l.maxSize = e.maxSize), e.minSize !== void 0 && (l.minSize = e.minSize), /* @__PURE__ */ c(i, { children: [y(n), /* @__PURE__ */ s(t, {
16
+ ...l,
17
+ className: o(g.panel, p && `${p}-${r}`),
18
+ children: /* @__PURE__ */ s(a, {
19
+ cardName: e.content,
20
+ parentCard: h
21
+ })
22
+ })] }, n);
23
+ }
24
+ function y(t) {
25
+ if (t === 0) return null;
26
+ let n = _[t - 1];
27
+ return /* @__PURE__ */ s(e, {
28
+ ...n,
29
+ className: o(g.handle)
30
+ });
31
+ }
32
+ return /* @__PURE__ */ s(n, {
33
+ direction: f ?? "horizontal",
34
+ className: o(p, g.root),
35
+ "data-pihanga": h,
36
+ children: u.map(v)
37
+ });
38
+ };
39
+ //#endregion
40
+ export { l as ResizableComponent };
41
+
42
+ //# sourceMappingURL=resizable.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizable.component.js","names":[],"sources":["../../../src/cards/resizable/resizable.component.tsx"],"sourcesContent":["import React, {Fragment} from \"react\";\nimport {Card, type PiCardProps} from \"@pihanga2/core\";\nimport {clsx} from \"clsx\";\nimport {\n ResizableHandle,\n ResizablePanel,\n ResizablePanelGroup,\n} from \"@/components/ui/resizable\";\nimport {\n DefResizableHandle,\n type PiResizableHandle,\n type PiResizablePanel,\n type ResizableProps,\n} from \"./resizable.types\";\n\nexport const ResizableComponent = (\n props: PiCardProps<ResizableProps>,\n): React.ReactNode => {\n const {content, handles, direction, className, style, cardName} = props;\n\n const sy =\n (style as {root?: string; panel?: string; handle?: string} | undefined) ??\n {};\n\n const _handles: PiResizableHandle[] = Array.isArray(handles)\n ? handles\n : Array(content.length - 1).fill(handles ?? DefResizableHandle);\n\n function renderPanel(el: PiResizablePanel, idx: number) {\n const name = el.name ?? `panel${idx}`;\n const panelProps: {\n defaultSize: number;\n collapsible?: boolean;\n maxSize?: number;\n minSize?: number;\n } = {\n defaultSize: el.defaultSize ?? 50,\n collapsible: el.collapsible,\n };\n if (el.maxSize !== undefined) panelProps.maxSize = el.maxSize;\n if (el.minSize !== undefined) panelProps.minSize = el.minSize;\n\n return (\n <Fragment key={idx}>\n {renderHandle(idx)}\n <ResizablePanel\n {...panelProps}\n className={clsx(sy.panel, className && `${className}-${name}`)}\n >\n <Card cardName={el.content} parentCard={cardName} />\n </ResizablePanel>\n </Fragment>\n );\n }\n\n function renderHandle(idx: number) {\n if (idx === 0) return null;\n const h = _handles[idx - 1];\n return <ResizableHandle {...h} className={clsx(sy.handle)} />;\n }\n\n return (\n <ResizablePanelGroup\n direction={direction ?? \"horizontal\"}\n className={clsx(className, sy.root)}\n data-pihanga={cardName}\n >\n {content.map(renderPanel)}\n </ResizablePanelGroup>\n );\n};\n"],"mappings":";;;;;;;AAeA,IAAa,KACX,MACoB;CACpB,IAAM,EAAC,YAAS,YAAS,cAAW,cAAW,UAAO,gBAAY,GAE5D,IACH,KACD,CAAC,GAEG,IAAgC,MAAM,QAAQ,CAAO,IACvD,IACA,MAAM,EAAQ,SAAS,CAAC,EAAE,KAAK,KAAW,CAAkB;CAEhE,SAAS,EAAY,GAAsB,GAAa;EACtD,IAAM,IAAO,EAAG,QAAQ,QAAQ,KAC1B,IAKF;GACF,aAAa,EAAG,eAAe;GAC/B,aAAa,EAAG;EAClB;EAIA,OAHI,EAAG,YAAY,KAAA,MAAW,EAAW,UAAU,EAAG,UAClD,EAAG,YAAY,KAAA,MAAW,EAAW,UAAU,EAAG,UAGpD,kBAAC,GAAD,EAAA,UAAA,CACG,EAAa,CAAG,GACjB,kBAAC,GAAD;GACE,GAAI;GACJ,WAAW,EAAK,EAAG,OAAO,KAAa,GAAG,EAAU,GAAG,GAAM;aAE7D,kBAAC,GAAD;IAAM,UAAU,EAAG;IAAS,YAAY;GAAW,CAAA;EACrC,CAAA,CACR,EAAA,GARK,CAQL;CAEd;CAEA,SAAS,EAAa,GAAa;EACjC,IAAI,MAAQ,GAAG,OAAO;EACtB,IAAM,IAAI,EAAS,IAAM;EACzB,OAAO,kBAAC,GAAD;GAAiB,GAAI;GAAG,WAAW,EAAK,EAAG,MAAM;EAAI,CAAA;CAC9D;CAEA,OACE,kBAAC,GAAD;EACE,WAAW,KAAa;EACxB,WAAW,EAAK,GAAW,EAAG,IAAI;EAClC,gBAAc;YAEb,EAAQ,IAAI,CAAW;CACL,CAAA;AAEzB"}
@@ -0,0 +1,10 @@
1
+ import { createCardDeclaration as e } from "@pihanga2/core";
2
+ //#region src/cards/resizable/resizable.types.ts
3
+ var t = "shad/resizable", n = e(t), r = n, i = {
4
+ withHandle: !0,
5
+ disabled: !1
6
+ };
7
+ //#endregion
8
+ export { i as DefResizableHandle, t as RESIZABLE_CARD, r as Resizable, n as SdResizable };
9
+
10
+ //# sourceMappingURL=resizable.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizable.types.js","names":[],"sources":["../../../src/cards/resizable/resizable.types.ts"],"sourcesContent":["import {type PiCardRef, createCardDeclaration} from \"@pihanga2/core\";\n\nexport const RESIZABLE_CARD = \"shad/resizable\";\n\nexport const SdResizable =\n createCardDeclaration<ResizableProps>(RESIZABLE_CARD);\n\n/** Convenience alias — same as {@link SdResizable}. */\nexport const Resizable = SdResizable;\n\nexport type ResizableProps<S = unknown> = {\n /** Ordered list of panels to render inside the resizable group. */\n content: PiResizablePanel[];\n /** Layout direction of the panel group. Defaults to `\"horizontal\"`. */\n direction?: \"horizontal\" | \"vertical\";\n /**\n * Handle configuration(s) between panels.\n *\n * - A single `PiResizableHandle` is used for every handle in the group.\n * - An array must have exactly `content.length - 1` entries.\n *\n * Defaults to {@link DefResizableHandle} (visible drag handle).\n */\n handles?: PiResizableHandle | PiResizableHandle[];\n className?: string;\n /**\n * Optional Shadcn-style style overrides.\n *\n * When provided as `{ shad?: { root?: string; panel?: string; handle?: string } }`,\n * the nested class names are applied to the corresponding elements in\n * addition to any `className` already set.\n */\n style?: S;\n};\n\nexport type PiResizablePanel = {\n /** Used for CSS class suffixes and stable ordering. Defaults to `\"panel{idx}\"`. */\n name?: string;\n /** The card to render inside this panel. */\n content: PiCardRef;\n /** Initial size as a percentage of the group. Defaults to `50`. */\n defaultSize?: number;\n minSize?: number;\n maxSize?: number;\n /** When `true`, the panel can be collapsed to zero size. */\n collapsible?: boolean;\n};\n\nexport type PiResizableHandle = {\n /** When `true`, renders a visible drag-handle grip indicator. */\n withHandle?: boolean;\n /** When `true`, the handle is decorative only (not draggable). */\n disabled?: boolean;\n};\n\n/** Default handle configuration: visible grip, draggable. */\nexport const DefResizableHandle: PiResizableHandle = {\n withHandle: true,\n disabled: false,\n};\n"],"mappings":";;AAEA,IAAa,IAAiB,kBAEjB,IACX,EAAsC,CAAc,GAGzC,IAAY,GAgDZ,IAAwC;CACnD,YAAY;CACZ,UAAU;AACZ"}
@@ -0,0 +1,34 @@
1
+ import { cn as e } from "../lib/utils.js";
2
+ import "react";
3
+ import { jsx as t } from "react/jsx-runtime";
4
+ import { GripVerticalIcon as n } from "lucide-react";
5
+ import * as r from "react-resizable-panels";
6
+ //#region src/components/ui/resizable.tsx
7
+ function i({ className: n, ...i }) {
8
+ return /* @__PURE__ */ t(r.PanelGroup, {
9
+ "data-slot": "resizable-panel-group",
10
+ className: e("flex h-full w-full data-[panel-group-direction=vertical]:flex-col", n),
11
+ ...i
12
+ });
13
+ }
14
+ function a({ ...e }) {
15
+ return /* @__PURE__ */ t(r.Panel, {
16
+ "data-slot": "resizable-panel",
17
+ ...e
18
+ });
19
+ }
20
+ function o({ withHandle: i, className: a, ...o }) {
21
+ return /* @__PURE__ */ t(r.PanelResizeHandle, {
22
+ "data-slot": "resizable-handle",
23
+ className: e("bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90", a),
24
+ ...o,
25
+ children: i && /* @__PURE__ */ t("div", {
26
+ className: "bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border",
27
+ children: /* @__PURE__ */ t(n, { className: "size-2.5" })
28
+ })
29
+ });
30
+ }
31
+ //#endregion
32
+ export { o as ResizableHandle, a as ResizablePanel, i as ResizablePanelGroup };
33
+
34
+ //# sourceMappingURL=resizable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resizable.js","names":[],"sources":["../../../src/components/ui/resizable.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { GripVerticalIcon } from \"lucide-react\"\nimport * as ResizablePrimitive from \"react-resizable-panels\"\n\nimport { cn } from \"@/components/lib/utils\"\n\nfunction ResizablePanelGroup({\n className,\n ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {\n return (\n <ResizablePrimitive.PanelGroup\n data-slot=\"resizable-panel-group\"\n className={cn(\n \"flex h-full w-full data-[panel-group-direction=vertical]:flex-col\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction ResizablePanel({\n ...props\n}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {\n return <ResizablePrimitive.Panel data-slot=\"resizable-panel\" {...props} />\n}\n\nfunction ResizableHandle({\n withHandle,\n className,\n ...props\n}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {\n withHandle?: boolean\n}) {\n return (\n <ResizablePrimitive.PanelResizeHandle\n data-slot=\"resizable-handle\"\n className={cn(\n \"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90\",\n className\n )}\n {...props}\n >\n {withHandle && (\n <div className=\"bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border\">\n <GripVerticalIcon className=\"size-2.5\" />\n </div>\n )}\n </ResizablePrimitive.PanelResizeHandle>\n )\n}\n\nexport { ResizablePanelGroup, ResizablePanel, ResizableHandle }\n"],"mappings":";;;;;;AAMA,SAAS,EAAoB,EAC3B,cACA,GAAG,KAC0D;CAC7D,OACE,kBAAC,EAAmB,YAApB;EACE,aAAU;EACV,WAAW,EACT,qEACA,CACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,EAAe,EACtB,GAAG,KACqD;CACxD,OAAO,kBAAC,EAAmB,OAApB;EAA0B,aAAU;EAAkB,GAAI;CAAQ,CAAA;AAC3E;AAEA,SAAS,EAAgB,EACvB,eACA,cACA,GAAG,KAGF;CACD,OACE,kBAAC,EAAmB,mBAApB;EACE,aAAU;EACV,WAAW,EACT,6oBACA,CACF;EACA,GAAI;YAEH,KACC,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD,EAAkB,WAAU,WAAY,CAAA;EACrC,CAAA;CAE6B,CAAA;AAE1C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pihanga2/shadcn",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Pihanga core card components built on shadcn/ui and Radix UI — the npm distribution of pihanga-shadcn.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -60,6 +60,10 @@
60
60
  "import": "./cards/input/index.js",
61
61
  "types": "./cards/input/index.d.ts"
62
62
  },
63
+ "./cards/jsonViewer": {
64
+ "import": "./cards/jsonViewer/index.js",
65
+ "types": "./cards/jsonViewer/index.d.ts"
66
+ },
63
67
  "./cards/list": {
64
68
  "import": "./cards/list/index.js",
65
69
  "types": "./cards/list/index.d.ts"
@@ -92,6 +96,10 @@
92
96
  "import": "./cards/pasteTarget/index.js",
93
97
  "types": "./cards/pasteTarget/index.d.ts"
94
98
  },
99
+ "./cards/resizable": {
100
+ "import": "./cards/resizable/index.js",
101
+ "types": "./cards/resizable/index.d.ts"
102
+ },
95
103
  "./cards/select": {
96
104
  "import": "./cards/select/index.js",
97
105
  "types": "./cards/select/index.d.ts"
@@ -160,6 +168,7 @@
160
168
  "./cards/form/index.js",
161
169
  "./cards/framework/index.js",
162
170
  "./cards/input/index.js",
171
+ "./cards/jsonViewer/index.js",
163
172
  "./cards/list/index.js",
164
173
  "./cards/loadingOverlay/index.js",
165
174
  "./cards/loadingSkeleton/index.js",
@@ -168,6 +177,7 @@
168
177
  "./cards/navbarSearch/index.js",
169
178
  "./cards/pageWithNavbar/index.js",
170
179
  "./cards/pasteTarget/index.js",
180
+ "./cards/resizable/index.js",
171
181
  "./cards/select/index.js",
172
182
  "./cards/slider/index.js",
173
183
  "./cards/sliderValue/index.js",
@@ -216,6 +226,8 @@
216
226
  "lucide-react": "^0.513.0",
217
227
  "radix-ui": "^1.4.3",
218
228
  "react-drag-drop-files": "^3.0.1",
229
+ "react-json-view-lite": "^2.5.0",
230
+ "react-resizable-panels": "^2.0.0",
219
231
  "sonner": "^2.0.7",
220
232
  "tailwind-merge": "^3.3.0"
221
233
  }
@@ -1,2 +1,2 @@
1
- .pi-file-drop-root label{border:2px dashed #999;flex-grow:1;justify-content:space-between;max-width:100%;display:flex}.pi-file-drop-root .dropzone-msg{cursor:pointer;text-align:center;flex-grow:4;margin:3em 0}.pi-file-drop-root .pi-progress{max-width:100%;padding:1.25em}.pi-file-drop-root .pi-progress-label{font-size:1rem;font-weight:600;line-height:1.5rem}.pi-file-drop -root.pi-progress-container{background-color:#dadfe5;border-radius:4px;height:1rem;display:flex;position:relative;overflow:hidden}.pi-file-drop-root .pi-progress-bar{color:#fff;background-color:#0054a6;flex-direction:column;justify-content:center;display:flex;overflow:hidden}.pi-file-drop-root .pi-progress .label{font-size:1em;display:block}.overlay-wrapper{flex-direction:column;width:100%;min-width:0;display:flex;position:relative}.overlay-wrapper.is-loading.fill-parent{min-height:12rem}.overlay-content{flex-direction:column;flex-grow:1;width:100%;min-width:0;display:flex;position:relative;overflow-y:hidden}.overlay-wrapper.fill-parent .overlay-content{min-height:inherit}.loading-overlay{opacity:0;pointer-events:none;z-index:2;background:oklch(100% 0 0/.7);justify-content:center;align-items:center;transition:opacity .15s;display:flex;position:absolute;inset:0}.overlay-wrapper.viewport-centered .loading-overlay{z-index:50;position:fixed;inset:0}.dark .loading-overlay{background:oklch(14.5% 0 0/.5)}.overlay-wrapper.is-loading .loading-overlay{opacity:1;pointer-events:auto}.loading-overlay__content{color:var(--muted-foreground);align-items:center;gap:.5rem;font-size:.875rem;display:flex}.window{justify-content:center;width:100%;height:100%;display:flex}.page{flex-direction:column;flex:1;width:100%;height:100%;display:flex}.header{border-bottom:1px solid var(--border);background-color:var(--background);flex-shrink:0;align-items:center;gap:1rem;padding-left:1rem;padding-right:1rem;display:flex}@media (width>=768px){.header{padding-left:1.5rem;padding-right:1.5rem}}.title-section{align-items:center;gap:.5rem;display:inline-flex}.title-section .title-icon{flex-shrink:0;width:1.5rem;height:1.5rem}.title-section .title-text{color:var(--foreground);font-size:1.125rem;font-weight:600;line-height:1.75rem}.nav-md{display:none}@media (width>=768px){.nav-md{flex-direction:row;align-items:center;gap:1.25rem;display:flex}}@media (width>=1024px){.nav-md{gap:1.5rem}}.nav-md .nav-link{color:var(--muted-foreground);padding-left:0;padding-right:0;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.nav-md .nav-link:hover{color:var(--foreground)}.nav-sm{gap:1.5rem;padding:1rem;font-size:1.125rem;font-weight:500;line-height:1.75rem;display:grid}.nav-sm .nav-link{color:var(--muted-foreground);justify-content:flex-start;padding-left:0;padding-right:0;font-size:1.125rem;line-height:1.75rem}.nav-sm .nav-link:hover{color:var(--foreground)}.header-actions{align-items:center;gap:.5rem;margin-left:auto;display:flex}@media (width>=1024px){.header-actions{gap:1rem}}.main{background-color:var(--background);flex:1;min-height:0;padding:.5rem 1rem;overflow-y:auto}@media (width>=768px){.main{padding:1rem 2rem}}.footer{background-color:var(--background);color:var(--muted-foreground);flex-shrink:0;padding:.5rem 1rem}@media (width>=768px){.footer{padding:1rem 2rem}}.pihanga-paste-target{border:2px dashed #999}.pihanga-paste-target .paste-target-msg-title{font-size:1.2em}.pihanga-paste-target textarea:focus{outline:none}.pihanga-paste-target-focused{border-width:3px;border-color:#007bff}.pihanga-paste-target-focused .paste-target-focus-reminder{display:none}.pi-stepper{--pi-stepper-indicator-size-sm:1.5rem;--pi-stepper-indicator-size-md:2rem;--pi-stepper-indicator-size-lg:2.5rem;--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-md);--pi-stepper-connector-thickness:2px;--pi-stepper-connector-min-height:1.5rem;--pi-stepper-label-max-width:6rem}.pi-stepper--sm{--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-sm)}.pi-stepper--md{--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-md)}.pi-stepper--lg{--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-lg)}.pi-stepper__indicator{transition:background-color .2s,border-color .2s,color .2s,outline-color .2s}.pi-stepper__indicator--clickable{cursor:pointer}.pi-stepper__indicator--clickable:hover{opacity:.8}.pi-stepper__indicator--static{cursor:default}.pi-stepper__connector{transition:background-color .3s}.pi-stepper__connector--vertical{min-height:var(--pi-stepper-connector-min-height)}.pi-stepper__label-title{transition:color .2s}
1
+ .pi-file-drop-root label{border:2px dashed #999;flex-grow:1;justify-content:space-between;max-width:100%;display:flex}.pi-file-drop-root .dropzone-msg{cursor:pointer;text-align:center;flex-grow:4;margin:3em 0}.pi-file-drop-root .pi-progress{max-width:100%;padding:1.25em}.pi-file-drop-root .pi-progress-label{font-size:1rem;font-weight:600;line-height:1.5rem}.pi-file-drop -root.pi-progress-container{background-color:#dadfe5;border-radius:4px;height:1rem;display:flex;position:relative;overflow:hidden}.pi-file-drop-root .pi-progress-bar{color:#fff;background-color:#0054a6;flex-direction:column;justify-content:center;display:flex;overflow:hidden}.pi-file-drop-root .pi-progress .label{font-size:1em;display:block}._GzYRV{white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word;line-height:1.2}._3eOF8{margin-right:5px;font-weight:700}._3eOF8+._3eOF8{margin-left:-5px}._1MFti{cursor:pointer}._f10Tu{-webkit-user-select:none;user-select:none;margin-right:5px;font-size:1.2em}._1UmXx:after{content:"▸"}._1LId0:after{content:"▾"}._1pNG9{margin-right:5px}._1pNG9:after{content:"...";font-size:.8em}._2IvMF{background:#eee}._2bkNM{margin:0;padding:0 10px}._1BXBN{margin:0;padding:0}._1MGIk{color:#000;margin-right:5px;font-weight:600}._3uHL6{color:#000}._2T6PJ,._1Gho6{color:#df113a}._vGjyY{color:#2a3f3c}._1bQdo{color:#0b75f5}._3zQKs{color:#469038}._1xvuR{color:#43413d}._oLqym,._2AXVT,._2KJWg{color:#000}._11RoI{background:#002b36}._17H2C,._3QHg2,._3fDAz{color:#fdf6e3}._2bSDX{color:#fdf6e3;margin-right:5px;font-weight:bolder}._gsbQL{color:#fdf6e3}._LaAZe,._GTKgm{color:#81b5ac}._Chy1W{color:#cb4b16}._2bveF{color:#d33682}._2vRm-{color:#ae81ff}._1prJR{color:#268bd2}.jv-wrapper{position:relative}.jv-copy-btn{z-index:10;width:22px;height:22px;color:hsl(var(--muted-foreground,0 0% 55%));cursor:pointer;opacity:.35;background:0 0;border:1px solid #0000;border-radius:4px;justify-content:center;align-items:center;padding:0;transition:opacity .15s,color .15s,background .15s,border-color .15s;display:inline-flex;position:absolute;top:4px;right:4px}.jv-wrapper:hover .jv-copy-btn,.jv-wrapper:focus-within .jv-copy-btn{opacity:1}.jv-copy-btn:hover{color:hsl(var(--foreground));background:hsl(var(--muted,0 0% 96%));border-color:hsl(var(--border,0 0% 88%))}.jv-copy-btn--done{opacity:1;color:#2e7d32}.dark .jv-copy-btn--done{color:#81c784}.jv-container{white-space:pre-wrap;word-wrap:break-word;color:var(--foreground);background:0 0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,monospace;font-size:12.5px;font-weight:400;line-height:1.55}.jv-basic-child{margin:0;padding:0 0 0 1.1em;display:block;position:relative}.jv-label{color:var(--foreground);margin-right:0;font-weight:400}.jv-clickable-label{color:var(--foreground);cursor:pointer;margin-right:0;font-weight:400}.jv-clickable-label:hover{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.jv-punctuation{color:var(--foreground);font-weight:400}.jv-expand-icon,.jv-collapse-icon{width:1em;color:hsl(var(--muted-foreground,0 0% 55%));-webkit-user-select:none;user-select:none;cursor:pointer;justify-content:center;align-items:center;font-size:.65em;line-height:1;display:inline-flex;position:absolute;top:.775em;left:0;transform:translateY(-50%)}.jv-expand-icon:after{content:"▶"}.jv-collapse-icon:after{content:"▼"}.jv-collapsed-content{cursor:pointer;color:hsl(var(--muted-foreground,0 0% 55%))}.jv-collapsed-content:after{content:" …"}.jv-child-fields-container{border-left:1px solid hsl(var(--border,0 0% 88%));margin:0 0 0 .3em;padding:0 0 0 1.25em}.jv-null,.jv-undefined{color:#c0392b}.jv-string{color:#2e7d32}.jv-number{color:#1565c0}.jv-boolean{color:#00796b}.jv-other{color:var(--foreground)}.dark .jv-string{color:#81c784}.dark .jv-number{color:#64b5f6}.dark .jv-boolean{color:#4db6ac}.dark .jv-null,.dark .jv-undefined{color:#ef9a9a}.dark .jv-child-fields-container{border-left-color:hsl(var(--border,0 0% 25%))}.overlay-wrapper{flex-direction:column;width:100%;min-width:0;display:flex;position:relative}.overlay-wrapper.is-loading.fill-parent{min-height:12rem}.overlay-content{flex-direction:column;flex-grow:1;width:100%;min-width:0;display:flex;position:relative;overflow-y:hidden}.overlay-wrapper.fill-parent .overlay-content{min-height:inherit}.loading-overlay{opacity:0;pointer-events:none;z-index:2;background:oklch(100% 0 0/.7);justify-content:center;align-items:center;transition:opacity .15s;display:flex;position:absolute;inset:0}.overlay-wrapper.viewport-centered .loading-overlay{z-index:50;position:fixed;inset:0}.dark .loading-overlay{background:oklch(14.5% 0 0/.5)}.overlay-wrapper.is-loading .loading-overlay{opacity:1;pointer-events:auto}.loading-overlay__content{color:var(--muted-foreground);align-items:center;gap:.5rem;font-size:.875rem;display:flex}.window{justify-content:center;width:100%;height:100%;display:flex}.page{flex-direction:column;flex:1;width:100%;height:100%;display:flex}.header{border-bottom:1px solid var(--border);background-color:var(--background);flex-shrink:0;align-items:center;gap:1rem;padding-left:1rem;padding-right:1rem;display:flex}@media (width>=768px){.header{padding-left:1.5rem;padding-right:1.5rem}}.title-section{align-items:center;gap:.5rem;display:inline-flex}.title-section .title-icon{flex-shrink:0;width:1.5rem;height:1.5rem}.title-section .title-text{color:var(--foreground);font-size:1.125rem;font-weight:600;line-height:1.75rem}.nav-md{display:none}@media (width>=768px){.nav-md{flex-direction:row;align-items:center;gap:1.25rem;display:flex}}@media (width>=1024px){.nav-md{gap:1.5rem}}.nav-md .nav-link{color:var(--muted-foreground);padding-left:0;padding-right:0;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.nav-md .nav-link:hover{color:var(--foreground)}.nav-sm{gap:1.5rem;padding:1rem;font-size:1.125rem;font-weight:500;line-height:1.75rem;display:grid}.nav-sm .nav-link{color:var(--muted-foreground);justify-content:flex-start;padding-left:0;padding-right:0;font-size:1.125rem;line-height:1.75rem}.nav-sm .nav-link:hover{color:var(--foreground)}.header-actions{align-items:center;gap:.5rem;margin-left:auto;display:flex}@media (width>=1024px){.header-actions{gap:1rem}}.main{background-color:var(--background);flex:1;min-height:0;padding:.5rem 1rem;overflow-y:auto}@media (width>=768px){.main{padding:1rem 2rem}}.footer{background-color:var(--background);color:var(--muted-foreground);flex-shrink:0;padding:.5rem 1rem}@media (width>=768px){.footer{padding:1rem 2rem}}.pihanga-paste-target{border:2px dashed #999}.pihanga-paste-target .paste-target-msg-title{font-size:1.2em}.pihanga-paste-target textarea:focus{outline:none}.pihanga-paste-target-focused{border-width:3px;border-color:#007bff}.pihanga-paste-target-focused .paste-target-focus-reminder{display:none}.pi-stepper{--pi-stepper-indicator-size-sm:1.5rem;--pi-stepper-indicator-size-md:2rem;--pi-stepper-indicator-size-lg:2.5rem;--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-md);--pi-stepper-connector-thickness:2px;--pi-stepper-connector-min-height:1.5rem;--pi-stepper-label-max-width:6rem}.pi-stepper--sm{--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-sm)}.pi-stepper--md{--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-md)}.pi-stepper--lg{--pi-stepper-indicator-size:var(--pi-stepper-indicator-size-lg)}.pi-stepper__indicator{transition:background-color .2s,border-color .2s,color .2s,outline-color .2s}.pi-stepper__indicator--clickable{cursor:pointer}.pi-stepper__indicator--clickable:hover{opacity:.8}.pi-stepper__indicator--static{cursor:default}.pi-stepper__connector{transition:background-color .3s}.pi-stepper__connector--vertical{min-height:var(--pi-stepper-connector-min-height)}.pi-stepper__label-title{transition:color .2s}
2
2
  /*$vite$:1*/