@pihanga2/shadcn 0.2.22 → 0.2.23

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,27 @@ import "@pihanga2/shadcn/cards/dataTable";
62
62
 
63
63
  ---
64
64
 
65
- ## Included cards (53)
65
+ ## Card catalogue (for code generators)
66
+
67
+ Every build also writes `dist-lib/cards.schema.json` (`@pihanga2/shadcn/cards.schema.json`):
68
+ a machine-readable catalogue of every card type in this package — a JSON
69
+ Schema for its props, and for each event payload plus the event's action
70
+ type. It's generated straight from the compiled cards and their `.d.ts`
71
+ files, so it can never drift from what's actually in `dist-lib/`.
72
+
73
+ Backends/other languages can use it to generate typed proxies instead of
74
+ hand-maintaining bindings. See `scripts/schema/extract-card-schemas.mjs` for
75
+ how it's produced, and the `pihanga-remote` project's Python code generator
76
+ (`pihanga_remote.codegen`) for a consumer.
77
+
78
+ ```ts
79
+ import catalogue from "@pihanga2/shadcn/cards.schema.json";
80
+ // catalogue.cards["shad/button"].props / .events...
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Included cards (54)
66
86
 
67
87
  - `attachment`
68
88
  - `avatar`
@@ -110,6 +130,7 @@ import "@pihanga2/shadcn/cards/dataTable";
110
130
  - `stack`
111
131
  - `stepper`
112
132
  - `suspense`
133
+ - `swimlane`
113
134
  - `switch`
114
135
  - `tabs`
115
136
  - `textArea`
@@ -1,6 +1,5 @@
1
1
  import { getIconElement as e } from "../icons.js";
2
- import './jsonViewer.css';import '../../node_modules/react-json-view-lite/dist/index.css';/* empty css */
3
- /* empty css */
2
+ import './jsonViewer.css';/* empty css */
4
3
  import { useCallback as t, useLayoutEffect as n, useRef as r, useState as i } from "react";
5
4
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
6
5
  import { Check as s, Copy as c } from "lucide-react";
@@ -1 +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"}
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\";\n// Note: react-json-view-lite's own CSS is intentionally NOT imported here.\n// Every style-name it defines is overridden below by `compactStyles` (the\n// `jv-*` classes), whose rules live entirely in \"./jsonViewer.css\". Importing\n// the library's own dist/index.css pulled in dead styles and, worse, caused\n// Vite to copy that CSS file into our build tree, which rollup then\n// referenced via a broken \"../../node_modules/react-json-view-lite/...\"\n// relative path once published to npm (external deps aren't republished with\n// the package). See jsonViewer.css for the real, already-overridden styles.\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":";;;;;;;AAyBA,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,2 @@
1
+ export * from './swimlane.types';
2
+ export * from './swimlane.model';
@@ -0,0 +1,14 @@
1
+ import { DEF_GUTTER_WIDTH as e, DEF_HEADER_HEIGHT as t, DEF_LANE_WIDTH as n, DEF_MAX_ROWS as r, DEF_ROW_HEIGHT as i, SWIMLANE_ACTION as a, SWIMLANE_CARD as o, SWIMLANE_DATA_ACTION as s, Swimlane as c, dispatchSwimlaneEvents as l, dispatchSwimlaneRowsCleared as u, onSwimlaneEventsReceived as d, onSwimlaneHoverChanged as f, onSwimlaneItemClicked as p, onSwimlaneRowsCleared as m } from "./swimlane.types.js";
2
+ import { clearSwimlaneRows as h, createSwimlaneModel as g, describeSwimlaneTarget as _, formatTs as v, ingestSwimlaneEvents as y, projectPending as b, registerSwimlaneModel as x, relatedCallIds as S, rowTouches as C } from "./swimlane.model.js";
3
+ import { SwimlaneComponent as w } from "./swimlane.component.js";
4
+ import { actionTypesToEvents as T, registerCardComponent as E } from "@pihanga2/core";
5
+ //#region src/cards/swimlane/index.ts
6
+ E({
7
+ name: o,
8
+ component: w,
9
+ events: T(a)
10
+ });
11
+ //#endregion
12
+ export { e as DEF_GUTTER_WIDTH, t as DEF_HEADER_HEIGHT, n as DEF_LANE_WIDTH, r as DEF_MAX_ROWS, i as DEF_ROW_HEIGHT, a as SWIMLANE_ACTION, o as SWIMLANE_CARD, s as SWIMLANE_DATA_ACTION, c as Swimlane, h as clearSwimlaneRows, g as createSwimlaneModel, _ as describeSwimlaneTarget, l as dispatchSwimlaneEvents, u as dispatchSwimlaneRowsCleared, v as formatTs, y as ingestSwimlaneEvents, d as onSwimlaneEventsReceived, f as onSwimlaneHoverChanged, p as onSwimlaneItemClicked, m as onSwimlaneRowsCleared, b as projectPending, x as registerSwimlaneModel, S as relatedCallIds, C as rowTouches };
13
+
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/cards/swimlane/index.ts"],"sourcesContent":["import {actionTypesToEvents, registerCardComponent} from \"@pihanga2/core\";\n\nimport {SwimlaneComponent} from \"./swimlane.component\";\nimport {SWIMLANE_ACTION, SWIMLANE_CARD} from \"./swimlane.types\";\n\nexport * from \"./swimlane.types\";\nexport * from \"./swimlane.model\";\n\nregisterCardComponent({\n name: SWIMLANE_CARD,\n component: SwimlaneComponent,\n events: actionTypesToEvents(SWIMLANE_ACTION),\n});\n"],"mappings":";;;;;AAQA,EAAsB;CACpB,MAAM;CACN,WAAW;CACX,QAAQ,EAAoB,CAAe;AAC7C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { default as React } from 'react';
2
+ import { PiCardProps } from '@pihanga2/core';
3
+ import { SwimlaneEvents, SwimlaneProps } from './swimlane.types';
4
+ export declare const SwimlaneComponent: (props: PiCardProps<SwimlaneProps, SwimlaneEvents>) => React.ReactNode;
@@ -0,0 +1,520 @@
1
+ import "./swimlane.types.js";
2
+ import { describeSwimlaneTarget as e, formatTs as t, projectPending as n, relatedCallIds as r, rowTouches as i } from "./swimlane.model.js";
3
+ import './swimlane.css';/* empty css */
4
+ import { memo as a, useEffect as o, useId as s, useLayoutEffect as c, useMemo as l, useRef as u, useState as d } from "react";
5
+ import f from "clsx";
6
+ import { Fragment as p, jsx as m, jsxs as h } from "react/jsx-runtime";
7
+ //#region src/cards/swimlane/swimlane.component.tsx
8
+ var g = (e, t) => e.gutter + t * e.laneW + e.laneW / 2;
9
+ function _(e, t, n, r, i, a, o, s = null) {
10
+ n.forEach((n, c) => {
11
+ let l = g(t, c);
12
+ [[
13
+ r,
14
+ 0,
15
+ t.mid
16
+ ], [
17
+ i,
18
+ t.mid,
19
+ t.rowH
20
+ ]].forEach(([t, o, c], u) => {
21
+ let d = t[n];
22
+ d && (e.visuals.push(/* @__PURE__ */ m("line", {
23
+ className: `swl-ll swl-ll-${d.state}`,
24
+ x1: l,
25
+ x2: l,
26
+ y1: o,
27
+ y2: c
28
+ }, `ll-${n}-${u}`)), d.acts.forEach((t, d) => {
29
+ let p = l - 5 + d * 5, g = u === 1 && !r[n]?.acts.includes(t), _ = u === 0 && !i[n]?.acts.includes(t), v = `M${p},${o} V${c} M${p + 10},${o} V${c}` + (g ? ` M${p},${o} H${p + 10}` : "") + (_ ? ` M${p},${c} H${p + 10}` : "");
30
+ e.visuals.push(/* @__PURE__ */ h("g", {
31
+ className: f("swl-act", a === t ? "swl-hl" : s === t && "swl-hl-proc"),
32
+ children: [/* @__PURE__ */ m("rect", {
33
+ x: p,
34
+ y: o,
35
+ width: 10,
36
+ height: c - o
37
+ }), /* @__PURE__ */ m("path", { d: v })]
38
+ }, `a-${t}-${u}`)), e.hits.push(/* @__PURE__ */ m("rect", {
39
+ className: "swl-hit",
40
+ x: p - 8,
41
+ y: o,
42
+ width: 26,
43
+ height: c - o,
44
+ "data-kind": "activity",
45
+ "data-id": t,
46
+ "data-lane": n
47
+ }, `ha-${t}-${u}`));
48
+ }));
49
+ }), !r[n] && i[n] && e.visuals.push(/* @__PURE__ */ m("rect", {
50
+ className: "swl-lane-head",
51
+ x: l - 16,
52
+ y: t.mid - 5,
53
+ width: 32,
54
+ height: 10,
55
+ rx: 2
56
+ }, `h-${n}`), /* @__PURE__ */ h("text", {
57
+ className: "swl-lbl-muted",
58
+ x: l + 20,
59
+ y: t.mid + 3,
60
+ children: [
61
+ "+ ",
62
+ o[n] ?? n,
63
+ " joined"
64
+ ]
65
+ }, `ht-${n}`));
66
+ });
67
+ }
68
+ function v(e, t, n, r) {
69
+ let i = n(r.from), a = n(r.to);
70
+ if (i < 0 || a < 0) return;
71
+ let o = g(t, i), s = g(t, a), c = f("swl-ar", r.cls, r.hl ? "swl-hl" : r.rel && "swl-hl-rel"), l = `url(#${t.mk}-${r.marker})`, u = {
72
+ "data-kind": r.kind,
73
+ "data-id": r.hitId,
74
+ "data-row": r.rowId
75
+ };
76
+ if (r.from === r.to) {
77
+ let n = `M${o + 6},${t.mid - 9} h26 v16 h-20`;
78
+ e.visuals.push(/* @__PURE__ */ m("path", {
79
+ className: c,
80
+ d: n,
81
+ markerEnd: l
82
+ }, "ar"), /* @__PURE__ */ m("text", {
83
+ className: "swl-lbl",
84
+ x: o + 38,
85
+ y: t.mid + 3,
86
+ children: r.label
87
+ }, "lb")), e.hits.push(/* @__PURE__ */ m("path", {
88
+ className: "swl-hit",
89
+ d: n,
90
+ strokeWidth: 14,
91
+ pointerEvents: "stroke",
92
+ ...u
93
+ }, "hit"));
94
+ return;
95
+ }
96
+ let d = Math.sign(s - o), p = o + d * 6, h = s - d * 6;
97
+ e.visuals.push(/* @__PURE__ */ m("line", {
98
+ className: c,
99
+ x1: p,
100
+ x2: h,
101
+ y1: t.mid,
102
+ y2: t.mid,
103
+ markerEnd: l
104
+ }, "ar"), /* @__PURE__ */ m("text", {
105
+ className: "swl-lbl",
106
+ x: Math.min(p, h) + 8,
107
+ y: t.mid - 5,
108
+ children: r.label
109
+ }, "lb")), e.hits.push(/* @__PURE__ */ m("line", {
110
+ className: "swl-hit",
111
+ x1: p,
112
+ x2: h,
113
+ y1: t.mid,
114
+ y2: t.mid,
115
+ strokeWidth: 16,
116
+ pointerEvents: "stroke",
117
+ ...u
118
+ }, "hit"));
119
+ }
120
+ var y = {
121
+ sync: ["swl-ar-sync", "sync"],
122
+ async: ["swl-ar-async", "async"],
123
+ event: ["swl-ar-event", "event"]
124
+ }, b = a(function({ row: e, g: n, laneOrder: r, laneLabels: i, width: a, hl: o, rel: s, proc: c }) {
125
+ let l = {
126
+ visuals: [],
127
+ hits: []
128
+ }, u = (e) => r.indexOf(e);
129
+ _(l, n, r, e.pre, e.post, o, i, c);
130
+ let d = e.event;
131
+ switch (d.type) {
132
+ case "call": {
133
+ let [t, r] = y[d.kind];
134
+ v(l, n, u, {
135
+ from: d.from,
136
+ to: d.to,
137
+ cls: t,
138
+ marker: r,
139
+ label: d.label,
140
+ hitId: d.id,
141
+ kind: "transition",
142
+ rowId: e.id,
143
+ hl: o === d.id,
144
+ rel: s
145
+ });
146
+ break;
147
+ }
148
+ case "reply":
149
+ e.route && v(l, n, u, {
150
+ from: e.route.to,
151
+ to: e.route.from,
152
+ cls: d.ok ? "swl-ar-reply" : "swl-ar-error",
153
+ marker: d.ok ? "reply" : "error",
154
+ label: d.label,
155
+ hitId: d.id,
156
+ kind: "transition",
157
+ rowId: e.id,
158
+ hl: o === d.id,
159
+ rel: s
160
+ });
161
+ break;
162
+ case "done":
163
+ e.route && l.visuals.push(/* @__PURE__ */ h("text", {
164
+ className: "swl-lbl-muted",
165
+ x: g(n, u(e.route.to)) + 14,
166
+ y: n.mid + 3,
167
+ children: ["✓ processed ", e.route.label]
168
+ }, "dn"));
169
+ break;
170
+ case "state": {
171
+ let t = g(n, u(d.lane));
172
+ l.visuals.push(/* @__PURE__ */ m("circle", {
173
+ cx: t,
174
+ cy: n.mid,
175
+ r: 5,
176
+ className: `swl-dot-${d.state}`
177
+ }, "st"), /* @__PURE__ */ h("text", {
178
+ className: "swl-lbl-muted",
179
+ x: t + 10,
180
+ y: n.mid + 3,
181
+ children: ["→ ", d.state]
182
+ }, "stl")), l.hits.push(/* @__PURE__ */ m("circle", {
183
+ className: "swl-hit",
184
+ cx: t,
185
+ cy: n.mid,
186
+ r: 12,
187
+ "data-kind": "state",
188
+ "data-id": `state-${e.id}`,
189
+ "data-lane": d.lane,
190
+ "data-row": e.id
191
+ }, "sth"));
192
+ break;
193
+ }
194
+ }
195
+ return /* @__PURE__ */ h("svg", {
196
+ className: f("swl-row", e.fulfilled && "swl-arrived"),
197
+ width: a,
198
+ height: n.rowH,
199
+ "data-row": e.id,
200
+ children: [
201
+ /* @__PURE__ */ m("text", {
202
+ className: "swl-ts",
203
+ x: 0,
204
+ y: n.mid + 3,
205
+ children: t(d.ts)
206
+ }),
207
+ e.fulfilled && /* @__PURE__ */ h("text", {
208
+ className: "swl-ok",
209
+ x: n.gutter - 16,
210
+ y: n.mid + 4,
211
+ children: [/* @__PURE__ */ m("title", { children: "replaced an outstanding message" }), "✓"]
212
+ }),
213
+ l.visuals,
214
+ l.hits
215
+ ]
216
+ });
217
+ }), x = a(function({ row: e, g: t, laneOrder: n, laneLabels: r, width: i, hl: a, rel: o, proc: s, now: c }) {
218
+ let l = {
219
+ visuals: [],
220
+ hits: []
221
+ }, u = e.pending, d = c - u.since, p = u.timeoutMs != null && d > u.timeoutMs;
222
+ return _(l, t, n, e.pre, e.post, a, r, s), v(l, t, (e) => n.indexOf(e), {
223
+ from: u.from,
224
+ to: u.to,
225
+ cls: f("swl-ar-ghost", p && "swl-overdue"),
226
+ marker: "ghost",
227
+ label: `⌛ ${u.label}`,
228
+ hitId: u.id,
229
+ kind: "pending",
230
+ hl: a === u.id,
231
+ rel: o
232
+ }), /* @__PURE__ */ h("svg", {
233
+ className: "swl-row swl-ghost",
234
+ width: i,
235
+ height: t.rowH,
236
+ children: [
237
+ /* @__PURE__ */ h("text", {
238
+ className: f("swl-ts", p && "swl-overdue"),
239
+ x: 0,
240
+ y: t.mid + 3,
241
+ children: [
242
+ "waiting ",
243
+ (d / 1e3).toFixed(1),
244
+ "s"
245
+ ]
246
+ }),
247
+ l.visuals,
248
+ l.hits
249
+ ]
250
+ });
251
+ }), S = a(function({ snap: e, count: t, g: n, laneOrder: r, laneLabels: i, width: a, proc: o }) {
252
+ let s = {
253
+ visuals: [],
254
+ hits: []
255
+ };
256
+ return _(s, n, r, e, e, null, i, o), /* @__PURE__ */ h("svg", {
257
+ className: "swl-row swl-now",
258
+ width: a,
259
+ height: n.rowH,
260
+ children: [
261
+ /* @__PURE__ */ m("text", {
262
+ className: "swl-now-lbl",
263
+ x: 0,
264
+ y: n.mid + 3,
265
+ children: "now"
266
+ }),
267
+ s.visuals,
268
+ /* @__PURE__ */ m("line", {
269
+ className: "swl-now-line",
270
+ x1: n.gutter - 6,
271
+ x2: a - 10,
272
+ y1: n.mid,
273
+ y2: n.mid
274
+ }),
275
+ /* @__PURE__ */ h("text", {
276
+ className: "swl-now-lbl",
277
+ x: a - 136,
278
+ y: n.mid - 4,
279
+ children: [
280
+ "▼ ",
281
+ t,
282
+ " outstanding"
283
+ ]
284
+ })
285
+ ]
286
+ });
287
+ });
288
+ function C(e) {
289
+ let { model: t, hid: r, related: a, procId: s, clock: c, ...u } = e, [f, g] = d(() => Date.now()), _ = c ?? f, v = t.pending.length > 0;
290
+ o(() => {
291
+ if (!v || c != null) return;
292
+ let e = setInterval(() => g(Date.now()), 250);
293
+ return () => clearInterval(e);
294
+ }, [v, c]);
295
+ let { current: y, pending: b } = t, C = l(() => n(y, b), [y, b]);
296
+ return /* @__PURE__ */ h(p, { children: [/* @__PURE__ */ m(S, {
297
+ snap: t.current,
298
+ count: t.pending.length,
299
+ proc: s && i({
300
+ pre: t.current,
301
+ post: t.current
302
+ }, void 0, s) ? s : null,
303
+ ...u
304
+ }), C.map((e) => /* @__PURE__ */ m(x, {
305
+ row: e,
306
+ now: _,
307
+ hl: r && i(e, e.pending.id, r) ? r : null,
308
+ rel: a.has(e.pending.id),
309
+ proc: s && i(e, void 0, s) ? s : null,
310
+ ...u
311
+ }, e.id))] });
312
+ }
313
+ function w({ model: e, g: t, headH: n, width: r }) {
314
+ return /* @__PURE__ */ m("svg", {
315
+ width: r,
316
+ height: n,
317
+ children: e.laneOrder.map((r, i) => {
318
+ let a = g(t, i), o = e.current[r], s = n - 24;
319
+ return /* @__PURE__ */ h("g", { children: [
320
+ /* @__PURE__ */ m("text", {
321
+ className: "swl-lane-name",
322
+ x: a + 4,
323
+ y: s,
324
+ transform: `rotate(-90 ${a + 4} ${s})`,
325
+ children: e.laneLabels[r] ?? r
326
+ }),
327
+ /* @__PURE__ */ m("circle", {
328
+ cx: a,
329
+ cy: n - 12,
330
+ r: 5,
331
+ className: `swl-dot-${o?.state}`,
332
+ children: /* @__PURE__ */ m("title", { children: o?.state })
333
+ }),
334
+ o && o.acts.length > 0 && /* @__PURE__ */ m("text", {
335
+ className: "swl-lbl-muted",
336
+ x: a + 9,
337
+ y: n - 8,
338
+ children: o.acts.length
339
+ })
340
+ ] }, r);
341
+ })
342
+ });
343
+ }
344
+ function T({ mk: e }) {
345
+ let t = (t, n, r) => /* @__PURE__ */ m("marker", {
346
+ id: `${e}-${t}`,
347
+ viewBox: "0 0 10 10",
348
+ refX: 9,
349
+ refY: 5,
350
+ markerWidth: 9,
351
+ markerHeight: 9,
352
+ markerUnits: "userSpaceOnUse",
353
+ orient: "auto-start-reverse",
354
+ children: r ? /* @__PURE__ */ m("path", {
355
+ d: "M1,1 L9,5 L1,9",
356
+ className: n
357
+ }) : /* @__PURE__ */ m("path", {
358
+ d: "M0,0 L10,5 L0,10 z",
359
+ className: n
360
+ })
361
+ });
362
+ return /* @__PURE__ */ m("svg", {
363
+ width: 0,
364
+ height: 0,
365
+ style: { position: "absolute" },
366
+ "aria-hidden": "true",
367
+ children: /* @__PURE__ */ h("defs", { children: [
368
+ t("sync", "swl-mk-sync", !1),
369
+ t("async", "swl-mk-async", !0),
370
+ t("event", "swl-mk-event", !0),
371
+ t("reply", "swl-mk-reply", !0),
372
+ t("error", "swl-mk-error", !1),
373
+ t("ghost", "swl-mk-ghost", !0)
374
+ ] })
375
+ });
376
+ }
377
+ function E(e, t) {
378
+ return t.kind !== "transition" && t.kind !== "pending" ? null : e.spans[t.id]?.parent ?? null;
379
+ }
380
+ function D(e) {
381
+ for (let t = e?.parentElement; t; t = t.parentElement) {
382
+ let e = getComputedStyle(t).overflowY;
383
+ if (e === "auto" || e === "scroll") return t;
384
+ }
385
+ return null;
386
+ }
387
+ var O = (t) => {
388
+ let { cardName: n, model: a, showPending: p = !0, autoFollow: g = !0, showTooltip: _ = !0, highlightId: v, laneWidth: y = 104, rowHeight: x = 30, headerHeight: S = 130, gutterWidth: O = 92, stickyTop: k = 0, now: A, className: j, onHoverChanged: M, onItemClicked: ee } = t, N = "swl" + s().replace(/[^a-zA-Z0-9_-]/g, ""), P = l(() => ({
389
+ laneW: y,
390
+ rowH: x,
391
+ gutter: O,
392
+ mid: x / 2,
393
+ mk: N
394
+ }), [
395
+ y,
396
+ x,
397
+ O,
398
+ N
399
+ ]), F = O + a.laneOrder.length * y + 140, I = {
400
+ g: P,
401
+ laneOrder: a.laneOrder,
402
+ laneLabels: a.laneLabels,
403
+ width: F
404
+ }, L = u(null), R = u(null), z = u(a);
405
+ z.current = a;
406
+ let [B, V] = d(null), H = u(null), U = (e) => {
407
+ let t = e?.closest?.(".swl-hit");
408
+ if (!t) return null;
409
+ let n = t.dataset;
410
+ return {
411
+ kind: n.kind,
412
+ id: n.id,
413
+ lane: n.lane,
414
+ rowId: n.row == null ? void 0 : Number(n.row)
415
+ };
416
+ }, W = (e, t) => e === t || !!e && !!t && e.kind === t.kind && e.id === t.id, G = (e) => {
417
+ let t = z.current, n = e.kind === "state" ? void 0 : r(t, e.id), i = E(t, e) ?? void 0;
418
+ return e.kind === "pending" ? {
419
+ pending: t.pending.find((t) => t.id === e.id),
420
+ span: t.spans[e.id],
421
+ related: n,
422
+ process: i
423
+ } : {
424
+ span: t.spans[e.id],
425
+ related: n,
426
+ process: i
427
+ };
428
+ }, K = (e, t) => {
429
+ let n = R.current;
430
+ n && (n.style.left = `${e + 14}px`, n.style.top = `${t + 14}px`);
431
+ }, q = (t, n) => {
432
+ let r = H.current;
433
+ if (W(r, t)) return;
434
+ H.current = t, V(t);
435
+ let i = R.current;
436
+ i && (t && _ ? (i.textContent = e(z.current, t, A ?? Date.now()), i.style.display = "block", K(n.clientX, n.clientY)) : i.style.display = "none"), M({
437
+ target: t,
438
+ previous: r,
439
+ ...t ? G(t) : {},
440
+ clientX: n.clientX,
441
+ clientY: n.clientY
442
+ });
443
+ }, J = (e) => {
444
+ let t = U(e.target);
445
+ t && q(t, e);
446
+ }, te = (e) => {
447
+ let t = U(e.relatedTarget);
448
+ (!t || !W(t, H.current)) && q(t, e);
449
+ }, ne = (e) => {
450
+ H.current && _ && K(e.clientX, e.clientY);
451
+ }, re = (e) => {
452
+ let t = U(e.target);
453
+ t && ee({
454
+ target: t,
455
+ ...G(t)
456
+ });
457
+ }, Y = B && B.kind !== "state" ? B.id : v ?? null, { spans: X } = a, Z = l(() => new Set(Y ? r({ spans: X }, Y) : []), [Y, X]), Q = B ? E(a, B) : null, $ = u(!0);
458
+ return o(() => {
459
+ if (!g) return;
460
+ let e = D(L.current), t = e ?? window, n = () => {
461
+ $.current = e ? e.scrollTop + e.clientHeight >= e.scrollHeight - 40 : window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 40;
462
+ };
463
+ return t.addEventListener("scroll", n, { passive: !0 }), () => t.removeEventListener("scroll", n);
464
+ }, [g]), c(() => {
465
+ if (!g || !$.current) return;
466
+ let e = D(L.current);
467
+ e ? e.scrollTop = e.scrollHeight : window.scrollTo(0, document.documentElement.scrollHeight);
468
+ }, [
469
+ g,
470
+ a.rows.length,
471
+ a.pending.length
472
+ ]), /* @__PURE__ */ h("div", {
473
+ ref: L,
474
+ className: f("pihanga-swimlane", j),
475
+ "data-pihanga": n,
476
+ children: [
477
+ /* @__PURE__ */ m(T, { mk: N }),
478
+ /* @__PURE__ */ m("div", {
479
+ className: "swl-header",
480
+ style: { top: k },
481
+ children: /* @__PURE__ */ m(w, {
482
+ model: a,
483
+ g: P,
484
+ headH: S,
485
+ width: F
486
+ })
487
+ }),
488
+ /* @__PURE__ */ h("div", {
489
+ className: "swl-rows",
490
+ onPointerOver: J,
491
+ onPointerOut: te,
492
+ onPointerMove: ne,
493
+ onClick: re,
494
+ children: [a.rows.map((e) => /* @__PURE__ */ m(b, {
495
+ row: e,
496
+ hl: Y && i(e, "id" in e.event ? e.event.id : void 0, Y) ? Y : null,
497
+ rel: "id" in e.event && e.event.type !== "done" && Z.has(e.event.id),
498
+ proc: Q && i(e, void 0, Q) ? Q : null,
499
+ ...I
500
+ }, e.id)), p && /* @__PURE__ */ m(C, {
501
+ model: a,
502
+ hid: Y,
503
+ related: Z,
504
+ procId: Q,
505
+ clock: A,
506
+ ...I
507
+ })]
508
+ }),
509
+ /* @__PURE__ */ m("div", {
510
+ ref: R,
511
+ className: "swl-tooltip",
512
+ role: "tooltip"
513
+ })
514
+ ]
515
+ });
516
+ };
517
+ //#endregion
518
+ export { O as SwimlaneComponent };
519
+
520
+ //# sourceMappingURL=swimlane.component.js.map