cursedbelt 5.56.0 → 5.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/react/charts/tokens.js +4 -4
  2. package/dist/react/code-editor/CodeEditor.d.ts.map +1 -1
  3. package/dist/react/code-editor/CodeEditor.js +2 -1
  4. package/dist/react/code-editor/CodeEditor.js.map +1 -1
  5. package/dist/react/code-editor/lineAbove.d.ts +17 -0
  6. package/dist/react/code-editor/lineAbove.d.ts.map +1 -0
  7. package/dist/react/code-editor/lineAbove.js +34 -0
  8. package/dist/react/code-editor/lineAbove.js.map +1 -0
  9. package/dist/react/companion-link/CompanionLinkStatus.d.ts.map +1 -1
  10. package/dist/react/companion-link/CompanionLinkStatus.js +2 -1
  11. package/dist/react/companion-link/CompanionLinkStatus.js.map +1 -1
  12. package/dist/react/components/Accordion.d.ts.map +1 -1
  13. package/dist/react/components/Accordion.js +2 -2
  14. package/dist/react/components/Accordion.js.map +1 -1
  15. package/dist/react/components/Badge.d.ts +20 -1
  16. package/dist/react/components/Badge.d.ts.map +1 -1
  17. package/dist/react/components/Badge.js +30 -3
  18. package/dist/react/components/Badge.js.map +1 -1
  19. package/dist/react/components/Button.d.ts.map +1 -1
  20. package/dist/react/components/Button.js +8 -2
  21. package/dist/react/components/Button.js.map +1 -1
  22. package/dist/react/components/Collapsible.d.ts.map +1 -1
  23. package/dist/react/components/Collapsible.js +2 -2
  24. package/dist/react/components/Collapsible.js.map +1 -1
  25. package/dist/react/components/DisclosureButton.d.ts +2 -2
  26. package/dist/react/components/DisclosureButton.d.ts.map +1 -1
  27. package/dist/react/components/DisclosureButton.js +9 -6
  28. package/dist/react/components/DisclosureButton.js.map +1 -1
  29. package/dist/react/components/EditorPage.d.ts +78 -0
  30. package/dist/react/components/EditorPage.d.ts.map +1 -0
  31. package/dist/react/components/EditorPage.js +133 -0
  32. package/dist/react/components/EditorPage.js.map +1 -0
  33. package/dist/react/components/IconPicker.d.ts +70 -0
  34. package/dist/react/components/IconPicker.d.ts.map +1 -0
  35. package/dist/react/components/IconPicker.js +175 -0
  36. package/dist/react/components/IconPicker.js.map +1 -0
  37. package/dist/react/components/PaneSplit.d.ts +52 -0
  38. package/dist/react/components/PaneSplit.d.ts.map +1 -0
  39. package/dist/react/components/PaneSplit.js +103 -0
  40. package/dist/react/components/PaneSplit.js.map +1 -0
  41. package/dist/react/components/PointerDrag.d.ts +88 -0
  42. package/dist/react/components/PointerDrag.d.ts.map +1 -0
  43. package/dist/react/components/PointerDrag.js +156 -0
  44. package/dist/react/components/PointerDrag.js.map +1 -0
  45. package/dist/react/components/PropertyList.d.ts +45 -0
  46. package/dist/react/components/PropertyList.d.ts.map +1 -0
  47. package/dist/react/components/PropertyList.js +12 -0
  48. package/dist/react/components/PropertyList.js.map +1 -0
  49. package/dist/react/components/Wizard/ReviewStep.d.ts +1 -1
  50. package/dist/react/components/Wizard/ReviewStep.d.ts.map +1 -1
  51. package/dist/react/components/Wizard/ReviewStep.js +3 -3
  52. package/dist/react/components/Wizard/ReviewStep.js.map +1 -1
  53. package/dist/react/components/editorPage.css +421 -0
  54. package/dist/react/components/formKit.d.ts +3 -1
  55. package/dist/react/components/formKit.d.ts.map +1 -1
  56. package/dist/react/components/formKit.js +3 -1
  57. package/dist/react/components/formKit.js.map +1 -1
  58. package/dist/react/components/paneSplit.css +93 -0
  59. package/dist/react/components/pointerDrag.css +39 -0
  60. package/dist/react/index.d.ts +4 -1
  61. package/dist/react/index.d.ts.map +1 -1
  62. package/dist/react/index.js +3 -0
  63. package/dist/react/index.js.map +1 -1
  64. package/dist/react/line-keys/index.d.ts +2 -0
  65. package/dist/react/line-keys/index.d.ts.map +1 -0
  66. package/dist/react/line-keys/index.js +2 -0
  67. package/dist/react/line-keys/index.js.map +1 -0
  68. package/dist/react/line-keys/lineKeys.d.ts +62 -0
  69. package/dist/react/line-keys/lineKeys.d.ts.map +1 -0
  70. package/dist/react/line-keys/lineKeys.js +159 -0
  71. package/dist/react/line-keys/lineKeys.js.map +1 -0
  72. package/dist/react/master-detail/DetailPane.d.ts +3 -0
  73. package/dist/react/master-detail/DetailPane.d.ts.map +1 -1
  74. package/dist/react/master-detail/DetailPane.js +5 -2
  75. package/dist/react/master-detail/DetailPane.js.map +1 -1
  76. package/dist/react/nav/SideNav.js +1 -1
  77. package/dist/react/nav/SideNav.js.map +1 -1
  78. package/dist/scripts/guardrailsEnforce.d.ts.map +1 -1
  79. package/dist/styles-areas/analytics.css +14 -14
  80. package/dist/styles-areas/charts.css +3 -3
  81. package/dist/styles-areas/companion-link.css +2 -2
  82. package/dist/styles-areas/core.css +50 -45
  83. package/dist/styles-areas/data-table.css +15 -16
  84. package/dist/styles-areas/disk-usage.css +4 -4
  85. package/dist/styles-areas/fields.css +11 -11
  86. package/dist/styles-areas/folder-tree.css +3 -3
  87. package/dist/styles-areas/layout-engine.css +2 -2
  88. package/dist/styles-areas/master-detail.css +4 -5
  89. package/dist/styles-areas/media-gallery.css +9 -9
  90. package/dist/styles-areas/media.css +3 -3
  91. package/dist/styles-areas/nav.css +5 -5
  92. package/dist/styles-areas/notifications.css +3 -3
  93. package/dist/styles-areas/problem-detail.css +3 -3
  94. package/dist/styles-areas/sharing.css +2 -3
  95. package/dist/styles-areas/stats.css +3 -3
  96. package/dist/styles-areas/wizard.css +7 -8
  97. package/dist/styles-areas/workbench.css +4 -4
  98. package/dist/styles-utilities.css +49 -10
  99. package/dist/theme.css +38 -22
  100. package/package.json +28 -2
  101. package/scripts/guardrails-baseline.json +2 -1
  102. package/scripts/guardrailsEnforce.spec.ts +32 -0
  103. package/scripts/guardrailsEnforce.ts +67 -1
  104. package/scripts/styleAreas.ts +6 -0
  105. package/src/componentCssReadsAreDeclared.spec.ts +62 -0
  106. package/src/react/charts/tokens.ts +4 -4
  107. package/src/react/code-editor/CodeEditor.tsx +2 -1
  108. package/src/react/code-editor/lineAbove.spec.ts +36 -0
  109. package/src/react/code-editor/lineAbove.ts +35 -0
  110. package/src/react/companion-link/CompanionLinkStatus.tsx +10 -12
  111. package/src/react/components/Accordion.tsx +5 -4
  112. package/src/react/components/Badge.capsule.spec.ts +1 -1
  113. package/src/react/components/Badge.spec.tsx +22 -1
  114. package/src/react/components/Badge.tsx +66 -11
  115. package/src/react/components/Button.spec.tsx +5 -4
  116. package/src/react/components/Button.tsx +8 -2
  117. package/src/react/components/Collapsible.tsx +5 -4
  118. package/src/react/components/DisclosureButton.spec.tsx +47 -0
  119. package/src/react/components/DisclosureButton.tsx +11 -7
  120. package/src/react/components/EditorPage.spec.tsx +106 -0
  121. package/src/react/components/EditorPage.tsx +281 -0
  122. package/src/react/components/IconPicker.spec.tsx +84 -0
  123. package/src/react/components/IconPicker.tsx +289 -0
  124. package/src/react/components/PaneSplit.spec.tsx +60 -0
  125. package/src/react/components/PaneSplit.tsx +206 -0
  126. package/src/react/components/PointerDrag.spec.tsx +108 -0
  127. package/src/react/components/PointerDrag.tsx +261 -0
  128. package/src/react/components/PropertyList.spec.tsx +110 -0
  129. package/src/react/components/PropertyList.tsx +92 -0
  130. package/src/react/components/Wizard/ReviewStep.tsx +7 -7
  131. package/src/react/components/editorPage.css +421 -0
  132. package/src/react/components/formKit.ts +10 -1
  133. package/src/react/components/formKitStylesheets.spec.ts +47 -0
  134. package/src/react/components/paneSplit.css +93 -0
  135. package/src/react/components/pointerDrag.css +39 -0
  136. package/src/react/index.ts +11 -1
  137. package/src/react/line-keys/index.ts +1 -0
  138. package/src/react/line-keys/lineKeys.spec.tsx +176 -0
  139. package/src/react/line-keys/lineKeys.ts +176 -0
  140. package/src/react/master-detail/DetailPane.tsx +14 -18
  141. package/src/react/nav/SideNav.tsx +1 -1
  142. package/src/styles-areas/analytics.css +14 -14
  143. package/src/styles-areas/charts.css +3 -3
  144. package/src/styles-areas/companion-link.css +2 -2
  145. package/src/styles-areas/core.css +50 -45
  146. package/src/styles-areas/data-table.css +15 -16
  147. package/src/styles-areas/disk-usage.css +4 -4
  148. package/src/styles-areas/fields.css +11 -11
  149. package/src/styles-areas/folder-tree.css +3 -3
  150. package/src/styles-areas/layout-engine.css +2 -2
  151. package/src/styles-areas/master-detail.css +4 -5
  152. package/src/styles-areas/media-gallery.css +9 -9
  153. package/src/styles-areas/media.css +3 -3
  154. package/src/styles-areas/nav.css +5 -5
  155. package/src/styles-areas/notifications.css +3 -3
  156. package/src/styles-areas/problem-detail.css +3 -3
  157. package/src/styles-areas/sharing.css +2 -3
  158. package/src/styles-areas/stats.css +3 -3
  159. package/src/styles-areas/wizard.css +7 -8
  160. package/src/styles-areas/workbench.css +4 -4
  161. package/src/styles-utilities.css +49 -10
  162. package/src/theme.css +38 -22
@@ -0,0 +1,133 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ArrowLeft } from 'lucide-react';
3
+ import { useCallback, useEffect, useId, useRef, useState, } from 'react';
4
+ import { useDiscardGuard } from '../hooks/useDiscardGuard.js';
5
+ import { cn } from '../lib/cn.js';
6
+ import { UnstyledButton } from './UnstyledButton.js';
7
+ import './formKit.css';
8
+ import './editorPage.css';
9
+ /** What answers Escape itself, before the page does. */
10
+ const OWNS_ESCAPE = '[role="listbox"],[role="menu"],[role="combobox"][aria-expanded="true"],[aria-haspopup]:not([aria-haspopup="false"])[aria-expanded="true"]';
11
+ const MIN_SPLIT = 0.25;
12
+ const MAX_SPLIT = 0.75;
13
+ const clampSplit = (value) => Math.min(MAX_SPLIT, Math.max(MIN_SPLIT, value));
14
+ function readSplit(key, fallback) {
15
+ if (!key || typeof localStorage === 'undefined')
16
+ return fallback;
17
+ const stored = Number(localStorage.getItem(key));
18
+ return Number.isFinite(stored) && stored > 0 ? clampSplit(stored) : fallback;
19
+ }
20
+ /**
21
+ * A creation page: the form on the left, a live mock of what it makes on the right, and a
22
+ * divider between them that drags.
23
+ *
24
+ * The owner, 2026-10-05, on desk's template editor: *"instead of opening a modal I want to open a
25
+ * page with a split view where I see the forms and fields … on the left and I see what it looks
26
+ * like so far and can tinker with a mock version of it on the right. I should be able to drag to
27
+ * resize a vertical divider … On mobile it will need an appropriate narrow layout."*
28
+ *
29
+ * - **Wide** (its container ≥ 48rem): two columns and a `separator` between them — drag it,
30
+ * or focus it and use ← / → (Home / End for the limits); a double-click puts it back. The
31
+ * split is kept under `storageKey`. Each side scrolls on its own, so a long form never pushes
32
+ * the preview out of sight.
33
+ * - **Narrow**: one side at a time, chosen by an Edit / Preview switch in the header — a phone
34
+ * has no room for both, and a pinned half-height preview leaves neither usable.
35
+ *
36
+ * The header is the page's own: a Back arrow with the crumb, the title, and the actions
37
+ * top-right where Linear and every desktop editor put Save. Escape anywhere is Back, as it was
38
+ * when the same form was a dialog.
39
+ *
40
+ * Plain stylesheet (`formKit.css`) — draws without Tailwind; `cursedbelt/react/form-kit`.
41
+ */
42
+ export function EditorPage({ title, backLabel = 'Back', onBack, isDirty, actions, children, preview, previewLabel = 'Preview', previewTools, defaultSplit = 0.5, storageKey, quiet = true, presentation = 'overlay', className, }) {
43
+ const [split, setSplit] = useState(() => readSplit(storageKey, clampSplit(defaultSplit)));
44
+ const [mode, setMode] = useState('edit');
45
+ const [dragging, setDragging] = useState(false);
46
+ const splitRef = useRef(null);
47
+ const rootRef = useRef(null);
48
+ const titleId = useId();
49
+ const formId = useId();
50
+ const previewId = useId();
51
+ const { guardedClose, discardOverlay, isConfirming, cancelDiscard } = useDiscardGuard({ onClose: onBack, isDirty });
52
+ const commit = useCallback((value) => {
53
+ const next = clampSplit(value);
54
+ setSplit(next);
55
+ if (storageKey && typeof localStorage !== 'undefined')
56
+ localStorage.setItem(storageKey, String(next));
57
+ }, [storageKey]);
58
+ // An overlay takes focus on open and gives it back on close, so the keyboard is where the
59
+ // eye is; the first field's own `autoFocus` still wins, because it runs first.
60
+ useEffect(() => {
61
+ if (presentation !== 'overlay')
62
+ return;
63
+ const before = document.activeElement;
64
+ const root = rootRef.current;
65
+ if (root && !root.contains(document.activeElement))
66
+ root.focus({ preventScroll: true });
67
+ return () => {
68
+ if (before?.isConnected)
69
+ before.focus({ preventScroll: true });
70
+ };
71
+ }, [presentation]);
72
+ const onPointerDown = (event) => {
73
+ if (event.button !== 0)
74
+ return;
75
+ event.preventDefault();
76
+ event.currentTarget.setPointerCapture(event.pointerId);
77
+ setDragging(true);
78
+ };
79
+ const onPointerMove = (event) => {
80
+ if (!dragging)
81
+ return;
82
+ const box = splitRef.current?.getBoundingClientRect();
83
+ if (!box || box.width === 0)
84
+ return;
85
+ commit((event.clientX - box.left) / box.width);
86
+ };
87
+ const onPointerUp = (event) => {
88
+ if (event.currentTarget.hasPointerCapture(event.pointerId))
89
+ event.currentTarget.releasePointerCapture(event.pointerId);
90
+ setDragging(false);
91
+ };
92
+ const onDividerKey = (event) => {
93
+ const step = event.shiftKey ? 0.1 : 0.02;
94
+ const next = event.key === 'ArrowLeft' ? split - step
95
+ : event.key === 'ArrowRight' ? split + step
96
+ : event.key === 'Home' ? MIN_SPLIT
97
+ : event.key === 'End' ? MAX_SPLIT
98
+ : null;
99
+ if (next === null)
100
+ return;
101
+ event.preventDefault();
102
+ commit(next);
103
+ };
104
+ const onKeyDown = (event) => {
105
+ if (event.key !== 'Escape' || event.defaultPrevented)
106
+ return;
107
+ // A menu, a listbox or an open combobox inside the form closes itself on Escape first. Only a
108
+ // POPUP's trigger counts: a disclosure (a folded row's ▸, `aria-expanded` with no popup) has
109
+ // nothing to close, and Escape on it is the page's.
110
+ if (event.target.closest(OWNS_ESCAPE))
111
+ return;
112
+ event.preventDefault();
113
+ if (isConfirming)
114
+ cancelDiscard();
115
+ else
116
+ guardedClose();
117
+ };
118
+ return (_jsxs("div", { ref: rootRef, className: cn('cbfk-page', className), "data-presentation": presentation, "data-mode": mode, "data-dragging": dragging ? 'true' : undefined, role: presentation === 'overlay' ? 'dialog' : 'region', "aria-modal": presentation === 'overlay' ? true : undefined, "aria-labelledby": titleId, tabIndex: -1, onKeyDown: onKeyDown, children: [_jsxs("header", { className: 'cbfk-page-head', children: [_jsxs(UnstyledButton, { className: 'cbfk-page-back', onClick: guardedClose, "aria-label": backLabel, tooltip: backLabel, children: [_jsx(ArrowLeft, { "aria-hidden": true }), _jsx("span", { className: 'cbfk-page-crumb', children: backLabel })] }), _jsx("span", { className: 'cbfk-page-sep', "aria-hidden": true, children: "/" }), _jsx("h1", { id: titleId, className: 'cbfk-page-title', children: title }), _jsx("div", { className: 'cbfk-page-modes', role: 'tablist', "aria-label": 'Show', children: ['edit', 'preview'].map((each) => (_jsx(UnstyledButton, { role: 'tab', className: 'cbfk-page-mode', "aria-selected": mode === each, "aria-controls": each === 'edit' ? formId : previewId, onClick: () => setMode(each), children: each === 'edit' ? 'Edit' : previewLabel }, each))) }), actions ? _jsx("div", { className: 'cbfk-page-actions', children: actions }) : null] }), _jsxs("div", { ref: splitRef, className: 'cbfk-page-split', style: { '--cbfk-page-split': `${(split * 100).toFixed(2)}%` }, children: [_jsx("div", { id: formId, className: cn('cbfk-page-form', quiet && 'cbfk-quiet'), children: _jsx("div", { className: 'cbfk-page-form-inner', children: children }) }), _jsx("div", { className: 'cbfk-page-divider', role: 'separator', "aria-orientation": 'vertical', "aria-label": 'Resize the form and the preview', "aria-valuemin": MIN_SPLIT * 100, "aria-valuemax": MAX_SPLIT * 100, "aria-valuenow": Math.round(split * 100), "aria-controls": formId, tabIndex: 0, onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, onPointerCancel: onPointerUp, onKeyDown: onDividerKey, onDoubleClick: () => commit(defaultSplit) }), _jsxs("section", { id: previewId, className: 'cbfk-page-preview', "aria-label": previewLabel, children: [_jsxs("div", { className: 'cbfk-page-preview-head', children: [_jsx("p", { className: 'cbfk-page-preview-label', children: previewLabel }), previewTools ? _jsx("div", { className: 'cbfk-page-preview-tools', children: previewTools }) : null] }), _jsx("div", { className: 'cbfk-page-preview-body', children: preview })] })] }), discardOverlay] }));
119
+ }
120
+ /**
121
+ * Linear's form, for any form: inputs, textareas and selects drawn with no box until they are
122
+ * pointed at or focused, labels muted and small, so the values are the only full-strength text.
123
+ *
124
+ * The owner, 2026-10-05, beside a Linear project page: *"I like how it doesn't have boxes around
125
+ * all the fields and doesn't fill in extra text or explanations. I want this minimal look in our
126
+ * apps too."* `EditorPage` puts its form inside one; wrap any other form to get the same.
127
+ * The words are the caller's half: a field that needs a description to be understood has the
128
+ * wrong label or the wrong placeholder.
129
+ */
130
+ export function QuietForm({ children, className }) {
131
+ return _jsx("div", { className: cn('cbfk-quiet', className), children: children });
132
+ }
133
+ //# sourceMappingURL=EditorPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorPage.js","sourceRoot":"","sources":["../../../src/react/components/EditorPage.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ import './formKit.css';
2
+ import './editorPage.css';
3
+ /**
4
+ * An icon somebody picked, with its drawing — so whoever stores it can draw it again with no
5
+ * icon library and no network: `iconDataUrl(icon, color)`.
6
+ */
7
+ export interface PickedIcon {
8
+ /** Iconify's name, `set:icon` — `lucide:code`, `simple-icons:kubernetes`. */
9
+ name: string;
10
+ /** The SVG's inner markup, every colour `currentColor`. */
11
+ body: string;
12
+ width: number;
13
+ height: number;
14
+ }
15
+ export interface IconSet {
16
+ prefix: string;
17
+ label: string;
18
+ }
19
+ /**
20
+ * The sets searched, all single-colour (they tint): the general-purpose four, and Simple Icons
21
+ * for the brands a runbook names — Kubernetes, Datadog, GitHub. Multi-colour logo sets are left
22
+ * out because a colour of the owner's choosing is the point of the mark.
23
+ */
24
+ export declare const ICON_SETS: readonly IconSet[];
25
+ export declare const ICONIFY_API = "https://api.iconify.design";
26
+ /**
27
+ * A picked icon as an `<img>` source, in `color`. An image rather than inline SVG on purpose: a
28
+ * stored body is drawn wherever the record goes, and an SVG inside an `<img>` cannot run script
29
+ * or reach the page, whatever the stored markup says. `color` must be a hex or a colour's name —
30
+ * anything else is left out, and the icon draws in the SVG's default black.
31
+ */
32
+ export declare function iconDataUrl(icon: Pick<PickedIcon, 'body' | 'width' | 'height'>, color: string): string;
33
+ /** Iconify's search, then every hit's drawing, one request per set. */
34
+ export declare function searchIcons(query: string, prefixes: readonly string[], fetcher?: typeof fetch, signal?: AbortSignal): Promise<PickedIcon[]>;
35
+ export interface IconPickerProps {
36
+ /** The chosen icon's name, drawn as selected. */
37
+ value?: string | null;
38
+ onPick: (icon: PickedIcon) => void;
39
+ /**
40
+ * The colour the results are drawn in — the mark's own, so the choice is made in it. A hex
41
+ * or a colour's name: the hits are images, which cannot inherit the page's `currentColor`.
42
+ */
43
+ color: string;
44
+ /**
45
+ * What to search for until somebody types — the thing's name is a good first guess. Followed
46
+ * as it changes (a name typed after the picker opened still searches), and let go of for good
47
+ * the moment the search field is typed in.
48
+ */
49
+ initialQuery?: string;
50
+ sets?: readonly IconSet[];
51
+ /** The request function — a test's seam. */
52
+ fetcher?: typeof fetch;
53
+ className?: string;
54
+ }
55
+ /**
56
+ * Search big icon sets and pick one: a search field, a row of sets, and a grid of hits drawn in
57
+ * the mark's own colour.
58
+ *
59
+ * The owner, 2026-10-05: *"For the mark I want a way to search in icon lists to choose a good
60
+ * icon … if I make a code snippet template I want to be able to pick an icon instead of letters
61
+ * like "CS". If we can have a search field and access to big icon sets like at shadcn.io/icons or
62
+ * iconify that will be good."* So Iconify's public API: 200,000 icons across the sets above,
63
+ * searched where they live rather than shipped in every app's bundle. A picked icon carries its
64
+ * own drawing (`PickedIcon.body`), so drawing it later needs neither the API nor the network.
65
+ *
66
+ * Keys: type to search; ↓ from the field enters the grid; arrows move in it; Enter or Space
67
+ * picks. Plain stylesheet (`formKit.css`); `cursedbelt/react/form-kit`.
68
+ */
69
+ export declare function IconPicker({ value, onPick, color, initialQuery, sets, fetcher, className, }: IconPickerProps): import("react").JSX.Element;
70
+ //# sourceMappingURL=IconPicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconPicker.d.ts","sourceRoot":"","sources":["../../../src/react/components/IconPicker.tsx"],"names":[],"mappings":"AAIA,OAAO,eAAe,CAAC;AACvB,OAAO,kBAAkB,CAAC;AAE1B;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAMvC,CAAC;AAEF,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAKxD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAItG;AASD,uEAAuE;AACvE,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,GAAE,OAAO,KAAa,EAC7B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAoCvB;AAED,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACnC;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAC1B,4CAA4C;IAC5C,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,KAAK,EACL,YAAiB,EACjB,IAAgB,EAChB,OAAO,EACP,SAAS,GACV,EAAE,eAAe,+BA2IjB"}
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { cn } from '../lib/cn.js';
4
+ import { SearchInput } from './SearchInput.js';
5
+ import { UnstyledButton } from './UnstyledButton.js';
6
+ import './formKit.css';
7
+ import './editorPage.css';
8
+ /**
9
+ * The sets searched, all single-colour (they tint): the general-purpose four, and Simple Icons
10
+ * for the brands a runbook names — Kubernetes, Datadog, GitHub. Multi-colour logo sets are left
11
+ * out because a colour of the owner's choosing is the point of the mark.
12
+ */
13
+ export const ICON_SETS = [
14
+ { prefix: 'lucide', label: 'Lucide' },
15
+ { prefix: 'tabler', label: 'Tabler' },
16
+ { prefix: 'ph', label: 'Phosphor' },
17
+ { prefix: 'mdi', label: 'Material' },
18
+ { prefix: 'simple-icons', label: 'Brands' },
19
+ ];
20
+ export const ICONIFY_API = 'https://api.iconify.design';
21
+ const RESULTS = 96;
22
+ /** A hex, or a named colour — nothing that could close the attribute it is spliced into. */
23
+ const SAFE_COLOR = /^(?:#[0-9a-f]{3,8}|[a-z]+)$/i;
24
+ /**
25
+ * A picked icon as an `<img>` source, in `color`. An image rather than inline SVG on purpose: a
26
+ * stored body is drawn wherever the record goes, and an SVG inside an `<img>` cannot run script
27
+ * or reach the page, whatever the stored markup says. `color` must be a hex or a colour's name —
28
+ * anything else is left out, and the icon draws in the SVG's default black.
29
+ */
30
+ export function iconDataUrl(icon, color) {
31
+ const tint = SAFE_COLOR.test(color) ? ` color="${color}"` : '';
32
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${icon.width} ${icon.height}" width="${icon.width}" height="${icon.height}"${tint}>${icon.body}</svg>`;
33
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
34
+ }
35
+ /** Iconify's search, then every hit's drawing, one request per set. */
36
+ export async function searchIcons(query, prefixes, fetcher = fetch, signal) {
37
+ const q = query.trim();
38
+ if (q === '')
39
+ return [];
40
+ const search = new URL('/search', ICONIFY_API);
41
+ search.searchParams.set('query', q);
42
+ search.searchParams.set('limit', String(RESULTS));
43
+ search.searchParams.set('prefixes', prefixes.join(','));
44
+ const found = await fetcher(search.toString(), { signal });
45
+ if (!found.ok)
46
+ throw new Error(`The icon search answered ${found.status}.`);
47
+ const names = (await found.json()).icons ?? [];
48
+ const bySet = new Map();
49
+ for (const name of names) {
50
+ const [prefix, icon] = name.split(':');
51
+ if (!prefix || !icon)
52
+ continue;
53
+ bySet.set(prefix, [...(bySet.get(prefix) ?? []), icon]);
54
+ }
55
+ const drawn = new Map();
56
+ await Promise.all([...bySet].map(async ([prefix, icons]) => {
57
+ const url = new URL(`/${prefix}.json`, ICONIFY_API);
58
+ url.searchParams.set('icons', icons.join(','));
59
+ const answer = await fetcher(url.toString(), { signal });
60
+ if (!answer.ok)
61
+ return;
62
+ const set = (await answer.json());
63
+ for (const [icon, data] of Object.entries(set.icons ?? {})) {
64
+ drawn.set(`${prefix}:${icon}`, {
65
+ name: `${prefix}:${icon}`,
66
+ body: data.body,
67
+ width: data.width ?? set.width ?? 24,
68
+ height: data.height ?? set.height ?? 24,
69
+ });
70
+ }
71
+ }));
72
+ // In the search's own order — its ranking is the useful part of it.
73
+ return names.map((name) => drawn.get(name)).filter((icon) => icon !== undefined);
74
+ }
75
+ /**
76
+ * Search big icon sets and pick one: a search field, a row of sets, and a grid of hits drawn in
77
+ * the mark's own colour.
78
+ *
79
+ * The owner, 2026-10-05: *"For the mark I want a way to search in icon lists to choose a good
80
+ * icon … if I make a code snippet template I want to be able to pick an icon instead of letters
81
+ * like "CS". If we can have a search field and access to big icon sets like at shadcn.io/icons or
82
+ * iconify that will be good."* So Iconify's public API: 200,000 icons across the sets above,
83
+ * searched where they live rather than shipped in every app's bundle. A picked icon carries its
84
+ * own drawing (`PickedIcon.body`), so drawing it later needs neither the API nor the network.
85
+ *
86
+ * Keys: type to search; ↓ from the field enters the grid; arrows move in it; Enter or Space
87
+ * picks. Plain stylesheet (`formKit.css`); `cursedbelt/react/form-kit`.
88
+ */
89
+ export function IconPicker({ value, onPick, color, initialQuery = '', sets = ICON_SETS, fetcher, className, }) {
90
+ const [query, setQuery] = useState(initialQuery);
91
+ const [typed, setTyped] = useState(false);
92
+ const [only, setOnly] = useState(null);
93
+ const [results, setResults] = useState([]);
94
+ const [state, setState] = useState('idle');
95
+ const [active, setActive] = useState(0);
96
+ const grid = useRef(null);
97
+ useEffect(() => {
98
+ if (!typed)
99
+ setQuery(initialQuery);
100
+ }, [initialQuery, typed]);
101
+ const prefixes = useMemo(() => (only ? [only] : sets.map((set) => set.prefix)), [only, sets]);
102
+ useEffect(() => {
103
+ if (query.trim() === '') {
104
+ setResults([]);
105
+ setState('idle');
106
+ return;
107
+ }
108
+ const abort = new AbortController();
109
+ setState('loading');
110
+ const timer = setTimeout(() => {
111
+ searchIcons(query, prefixes, fetcher, abort.signal).then((found) => {
112
+ setResults(found);
113
+ setActive(0);
114
+ setState('done');
115
+ }, (cause) => {
116
+ if (cause?.name === 'AbortError')
117
+ return;
118
+ setState('error');
119
+ });
120
+ }, 200);
121
+ return () => {
122
+ clearTimeout(timer);
123
+ abort.abort();
124
+ };
125
+ }, [query, prefixes, fetcher]);
126
+ const focusAt = (index) => {
127
+ const buttons = grid.current?.querySelectorAll('[data-icon]');
128
+ if (!buttons || buttons.length === 0)
129
+ return;
130
+ const next = Math.max(0, Math.min(buttons.length - 1, index));
131
+ setActive(next);
132
+ buttons[next]?.focus();
133
+ };
134
+ const columns = () => {
135
+ const buttons = grid.current?.querySelectorAll('[data-icon]');
136
+ if (!buttons || buttons.length === 0)
137
+ return 1;
138
+ const top = buttons[0].offsetTop;
139
+ let count = 0;
140
+ for (const button of buttons) {
141
+ if (button.offsetTop !== top)
142
+ break;
143
+ count += 1;
144
+ }
145
+ return Math.max(1, count);
146
+ };
147
+ const onGridKey = (event) => {
148
+ const step = event.key === 'ArrowRight' ? 1
149
+ : event.key === 'ArrowLeft' ? -1
150
+ : event.key === 'ArrowDown' ? columns()
151
+ : event.key === 'ArrowUp' ? -columns()
152
+ : null;
153
+ if (step === null)
154
+ return;
155
+ event.preventDefault();
156
+ focusAt(active + step);
157
+ };
158
+ return (_jsxs("div", { className: cn('cbfk-icons', className), children: [_jsx(SearchInput, { value: query, onChange: (next) => {
159
+ setTyped(true);
160
+ setQuery(next);
161
+ }, syntax: false, placeholder: 'Search icons \u2014 code, server, chart, kubernetes\u2026', "aria-label": 'Search icons', onKeyDown: (event) => {
162
+ if (event.key === 'ArrowDown' && results.length > 0) {
163
+ event.preventDefault();
164
+ focusAt(0);
165
+ }
166
+ } }), _jsx("fieldset", { className: 'cbfk-icons-sets', "aria-label": 'Icon sets', children: [{ prefix: null, label: 'All' }, ...sets].map((set) => (_jsx(UnstyledButton, { className: 'cbfk-icons-set', "aria-pressed": only === set.prefix, onClick: () => setOnly(set.prefix), children: set.label }, set.prefix ?? 'all'))) }), _jsx("div", { ref: grid, className: 'cbfk-icons-grid', role: 'radiogroup', "aria-label": 'Icons', "aria-busy": state === 'loading', onKeyDown: onGridKey, children: results.map((icon, index) => {
167
+ const [set, name] = icon.name.split(':');
168
+ return (_jsx(UnstyledButton, { role: 'radio', "data-icon": icon.name, className: 'cbfk-icons-cell', "aria-checked": icon.name === value, "aria-label": `${name} (${sets.find((each) => each.prefix === set)?.label ?? set})`, tooltip: icon.name, tabIndex: index === active ? 0 : -1, onFocus: () => setActive(index), onClick: () => onPick(icon), children: _jsx("img", { src: iconDataUrl(icon, color), alt: '', width: 22, height: 22, draggable: false }) }, icon.name));
169
+ }) }), _jsx("p", { className: 'cbfk-icons-note', role: 'status', children: state === 'idle' ? 'Type to search Lucide, Tabler, Phosphor, Material and brand icons.'
170
+ : state === 'loading' ? 'Searching…'
171
+ : state === 'error' ? 'Icon search needs the internet — api.iconify.design did not answer.'
172
+ : results.length === 0 ? `No icon matches “${query.trim()}”.`
173
+ : `${results.length} icons${results.length === RESULTS ? ' — the closest; type more to narrow' : ''}.` })] }));
174
+ }
175
+ //# sourceMappingURL=IconPicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IconPicker.js","sourceRoot":"","sources":["../../../src/react/components/IconPicker.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * `PaneSplit` — an editor's pane tree, drawn VS Code's way: panes that meet edge to edge, square,
3
+ * with one hairline between them.
4
+ *
5
+ * Graduated from desk's `WorkspaceSplit` on the owner's word: *"Panels and panes should be separated
6
+ * by sharp corners and thin border lines just like they are in vscode"* — and *"since these changes
7
+ * apply to layouts and cards they should be changed in cb and should impact desk through cb"*.
8
+ *
9
+ * The model is the app's — a tree of leaves and splits, each split carrying the fractions of its
10
+ * children. This component is only geometry: flex tracks, one seam between each pair of children,
11
+ * and the arithmetic that turns a pointer into a pair of fractions. Unlike `PanelGrid` it owns no
12
+ * sizes and persists nothing, which is what an app that stores its layout as a setting needs.
13
+ *
14
+ * 🔴 **The seam is keyboard-operable.** Arrow keys move it by 2%, Home/End take it to the bounds.
15
+ *
16
+ * 🔴 **The hit strip lives inside the seam's own track** — a real flex item, never a bar hung over
17
+ * the boundary. An invisible strip centred on the seam sits over the first pixels of the pane beside
18
+ * it and swallows clicks nobody aimed at it (desk's file `21`, where a 44px hit area ate a fill
19
+ * handle). So the track is a few pixels wide for the hand, painted the panes' own ground
20
+ * (`--cb-panesplit-ground`) with the hairline drawn down its middle: to the eye, two panes and one
21
+ * line, which is VS Code's picture; to the pointer, a target that touches no pane.
22
+ *
23
+ * Plain CSS (`paneSplit.css`), prefix `cb-panesplit`, so it draws in an app with no Tailwind pass.
24
+ */
25
+ import type { JSX } from 'react';
26
+ import './paneSplit.css';
27
+ export type PaneSplitDirection = 'row' | 'column';
28
+ export type PaneSplitNode<P> = {
29
+ kind: 'leaf';
30
+ pane: P;
31
+ } | {
32
+ kind: 'split';
33
+ direction: PaneSplitDirection;
34
+ sizes: readonly number[];
35
+ children: readonly PaneSplitNode<P>[];
36
+ };
37
+ /** Where a node sits in the tree: the child index at each level, from the root. */
38
+ export type PaneSplitPath = readonly number[];
39
+ export interface PaneSplitProps<P> {
40
+ node: PaneSplitNode<P>;
41
+ /** A key unique across the whole tree — what keeps a pane's subtree when its siblings close. */
42
+ paneKey: (pane: P) => string;
43
+ renderPane: (pane: P) => JSX.Element;
44
+ /** A seam moved: the split at `path` now has these fractions. */
45
+ onResize: (path: PaneSplitPath, sizes: number[]) => void;
46
+ /** The smallest share of an axis a pane can be dragged down to. Default 0.12. */
47
+ minFraction?: number;
48
+ /** Where `node` sits — only for a tree drawn from below its root. */
49
+ path?: PaneSplitPath;
50
+ }
51
+ export declare function PaneSplit<P>(props: PaneSplitProps<P>): JSX.Element;
52
+ //# sourceMappingURL=PaneSplit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaneSplit.d.ts","sourceRoot":"","sources":["../../../src/react/components/PaneSplit.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAiB,GAAG,EAA0E,MAAM,OAAO,CAAC;AAExH,OAAO,iBAAiB,CAAC;AAEzB,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,QAAQ,CAAC;AAElD,MAAM,MAAM,aAAa,CAAC,CAAC,IACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,SAAS,aAAa,CAAC,CAAC,CAAC,EAAE,CAAA;CAAE,CAAC;AAEtH,mFAAmF;AACnF,MAAM,MAAM,aAAa,GAAG,SAAS,MAAM,EAAE,CAAC;AAE9C,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACvB,gGAAgG;IAChG,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAC7B,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC;IACrC,iEAAiE;IACjE,QAAQ,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACzD,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,IAAI,CAAC,EAAE,aAAa,CAAC;CACtB;AAaD,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAIlE"}
@@ -0,0 +1,103 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useRef } from 'react';
3
+ import './paneSplit.css';
4
+ const DEFAULT_MIN_FRACTION = 0.12;
5
+ /**
6
+ * A key for a child that follows its content rather than its position: closing a pane in the middle
7
+ * of a row shifts every later child's index, and `key={index}` would hand React the wrong subtree.
8
+ */
9
+ function childKey(node, paneKey) {
10
+ if (node.kind === 'leaf')
11
+ return paneKey(node.pane);
12
+ return node.children.map((child) => childKey(child, paneKey)).join('|');
13
+ }
14
+ export function PaneSplit(props) {
15
+ const { node } = props;
16
+ if (node.kind === 'leaf')
17
+ return props.renderPane(node.pane);
18
+ return _jsx(SplitNode, { ...props, node: node, path: props.path ?? [] });
19
+ }
20
+ function SplitNode({ node, path, paneKey, renderPane, onResize, minFraction = DEFAULT_MIN_FRACTION, }) {
21
+ const frame = useRef(null);
22
+ const dragging = useRef(null);
23
+ /** Set the share of the pair either side of seam `index`; only those two panes move. */
24
+ const setShare = useCallback((index, share) => {
25
+ const pair = (node.sizes[index] ?? 0) + (node.sizes[index + 1] ?? 0);
26
+ if (pair <= 0)
27
+ return;
28
+ const bounded = Math.min(Math.max(share, minFraction), 1 - minFraction);
29
+ const sizes = [...node.sizes];
30
+ sizes[index] = pair * bounded;
31
+ sizes[index + 1] = pair * (1 - bounded);
32
+ onResize(path, sizes);
33
+ }, [node.sizes, minFraction, onResize, path]);
34
+ const moveSeam = (index, position) => {
35
+ const box = frame.current?.getBoundingClientRect();
36
+ if (!box)
37
+ return;
38
+ const total = node.direction === 'row' ? box.width : box.height;
39
+ if (total <= 0)
40
+ return;
41
+ const offset = (node.direction === 'row' ? position - box.left : position - box.top) / total;
42
+ const before = node.sizes.slice(0, index).reduce((sum, size) => sum + size, 0);
43
+ const pair = (node.sizes[index] ?? 0) + (node.sizes[index + 1] ?? 0);
44
+ if (pair <= 0)
45
+ return;
46
+ setShare(index, (offset - before) / pair);
47
+ };
48
+ const nudgeSeam = (index, delta) => {
49
+ const pair = (node.sizes[index] ?? 0) + (node.sizes[index + 1] ?? 0);
50
+ if (pair <= 0)
51
+ return;
52
+ setShare(index, (node.sizes[index] ?? 0) / pair + delta);
53
+ };
54
+ const onKeyDown = (event, index) => {
55
+ const back = node.direction === 'row' ? 'ArrowLeft' : 'ArrowUp';
56
+ const forward = node.direction === 'row' ? 'ArrowRight' : 'ArrowDown';
57
+ if (event.key === back)
58
+ nudgeSeam(index, -0.02);
59
+ else if (event.key === forward)
60
+ nudgeSeam(index, 0.02);
61
+ else if (event.key === 'Home')
62
+ nudgeSeam(index, -1);
63
+ else if (event.key === 'End')
64
+ nudgeSeam(index, 1);
65
+ else
66
+ return;
67
+ event.preventDefault();
68
+ };
69
+ const onPointerDown = (event, index) => {
70
+ if (event.button !== 0)
71
+ return;
72
+ dragging.current = index;
73
+ event.currentTarget.setPointerCapture(event.pointerId);
74
+ event.preventDefault();
75
+ };
76
+ const onPointerMove = (event) => {
77
+ if (dragging.current === null)
78
+ return;
79
+ moveSeam(dragging.current, node.direction === 'row' ? event.clientX : event.clientY);
80
+ };
81
+ const endDrag = (event) => {
82
+ if (dragging.current === null)
83
+ return;
84
+ dragging.current = null;
85
+ if (event.currentTarget.hasPointerCapture?.(event.pointerId))
86
+ event.currentTarget.releasePointerCapture(event.pointerId);
87
+ };
88
+ const minPercent = Math.round(minFraction * 100);
89
+ return (_jsx("div", { className: "cb-panesplit", "data-direction": node.direction, ref: frame, children: node.children.map((child, index) => (_jsx(ChildAndSeam, { last: index === node.children.length - 1, size: node.sizes[index] ?? 1 / node.children.length, seam: {
90
+ direction: node.direction,
91
+ percent: Math.round((node.sizes[index] ?? 0) * 100),
92
+ min: minPercent,
93
+ max: 100 - minPercent,
94
+ onKeyDown: (event) => onKeyDown(event, index),
95
+ onPointerDown: (event) => onPointerDown(event, index),
96
+ onPointerMove,
97
+ endDrag,
98
+ }, children: _jsx(PaneSplit, { node: child, path: [...path, index], paneKey: paneKey, renderPane: renderPane, onResize: onResize, minFraction: minFraction }) }, childKey(child, paneKey)))) }));
99
+ }
100
+ function ChildAndSeam({ last, size, seam, children }) {
101
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "cb-panesplit__cell", style: { '--cb-panesplit-share': size }, children: children }), !last && (_jsx("div", { className: "cb-panesplit__seam", role: "separator", tabIndex: 0, "aria-orientation": seam.direction === 'row' ? 'vertical' : 'horizontal', "aria-label": seam.direction === 'row' ? 'Width of the pane on the left' : 'Height of the pane above', "aria-valuenow": seam.percent, "aria-valuemin": seam.min, "aria-valuemax": seam.max, onKeyDown: seam.onKeyDown, onPointerDown: seam.onPointerDown, onPointerMove: seam.onPointerMove, onPointerUp: seam.endDrag, onPointerCancel: seam.endDrag }))] }));
102
+ }
103
+ //# sourceMappingURL=PaneSplit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PaneSplit.js","sourceRoot":"","sources":["../../../src/react/components/PaneSplit.tsx"],"names":[],"mappings":";AA0BA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,iBAAiB,CAAC;AAwBzB,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAElC;;;GAGG;AACH,SAAS,QAAQ,CAAI,IAAsB,EAAE,OAA4B;IACvE,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,SAAS,CAAI,KAAwB;IACnD,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,OAAO,KAAC,SAAS,OAAK,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,GAAI,CAAC;AACtE,CAAC;AAED,SAAS,SAAS,CAAI,EACpB,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,UAAU,EACV,QAAQ,EACR,WAAW,GAAG,oBAAoB,GAC8D;IAChG,MAAM,KAAK,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IAE7C,wFAAwF;IACxF,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,KAAa,EAAE,KAAa,EAAE,EAAE;QAC/B,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC;QACxE,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9B,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC;QAC9B,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;QACxC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACxB,CAAC,EACD,CAAC,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,CAAC,CAC1C,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,QAAgB,EAAE,EAAE;QACnD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,EAAE,qBAAqB,EAAE,CAAC;QACnD,IAAI,CAAC,GAAG;YAAE,OAAO;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QAChE,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO;QACvB,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAC7F,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/E,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO;QACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,EAAE;QACjD,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,IAAI,IAAI,IAAI,CAAC;YAAE,OAAO;QACtB,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,KAAyC,EAAE,KAAa,EAAE,EAAE;QAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;QAChE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QACtE,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI;YAAE,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC;aAC3C,IAAI,KAAK,CAAC,GAAG,KAAK,OAAO;YAAE,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;aAClD,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM;YAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;aAC/C,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK;YAAE,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;;YAC7C,OAAO;QACZ,KAAK,CAAC,cAAc,EAAE,CAAC;IACzB,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,KAAwC,EAAE,KAAa,EAAE,EAAE;QAChF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,QAAQ,CAAC,OAAO,GAAG,KAAK,CAAC;QACzB,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACvD,KAAK,CAAC,cAAc,EAAE,CAAC;IACzB,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,KAAwC,EAAE,EAAE;QACjE,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QACtC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvF,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,KAAwC,EAAE,EAAE;QAC3D,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI;YAAE,OAAO;QACtC,QAAQ,CAAC,OAAO,GAAG,IAAI,CAAC;QACxB,IAAI,KAAK,CAAC,aAAa,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC;YAAE,KAAK,CAAC,aAAa,CAAC,qBAAqB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC3H,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC;IACjD,OAAO,CACL,cAAK,SAAS,EAAC,cAAc,oBAAiB,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,KAAK,YACrE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CACnC,KAAC,YAAY,IAEX,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EACxC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EACnD,IAAI,EAAE;gBACJ,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC;gBACnD,GAAG,EAAE,UAAU;gBACf,GAAG,EAAE,GAAG,GAAG,UAAU;gBACrB,SAAS,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;gBAC7C,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC;gBACrD,aAAa;gBACb,OAAO;aACR,YAED,KAAC,SAAS,IAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAI,IAdrI,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAehB,CAChB,CAAC,GACE,CACP,CAAC;AACJ,CAAC;AAaD,SAAS,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAA2E;IAC3H,OAAO,CACL,8BACE,cAAK,SAAS,EAAC,oBAAoB,EAAC,KAAK,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAmB,YACzF,QAAQ,GACL,EACL,CAAC,IAAI,IAAI,CAIR,cACE,SAAS,EAAC,oBAAoB,EAC9B,IAAI,EAAC,WAAW,EAChB,QAAQ,EAAE,CAAC,sBACO,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,gBAC1D,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,0BAA0B,mBACpF,IAAI,CAAC,OAAO,mBACZ,IAAI,CAAC,GAAG,mBACR,IAAI,CAAC,GAAG,EACvB,SAAS,EAAE,IAAI,CAAC,SAAS,EACzB,aAAa,EAAE,IAAI,CAAC,aAAa,EACjC,aAAa,EAAE,IAAI,CAAC,aAAa,EACjC,WAAW,EAAE,IAAI,CAAC,OAAO,EACzB,eAAe,EAAE,IAAI,CAAC,OAAO,GAC7B,CACH,IACA,CACJ,CAAC;AACJ,CAAC"}