@booboo-brain/panel 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -290,10 +290,11 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
290
290
  .pending-item { font-size: 11.5px; color: var(--warn); font-family: var(--mono); }
291
291
  .pnl-err { position: relative; z-index: 2; padding: 10px 26px; color: #e07b6a; font-size: 12.5px; }
292
292
 
293
- /* layout */
294
- .pnl { height: 100vh; }
293
+ /* layout — 100% so a HOST container decides the height (embedded panel);
294
+ the standalone app gives html/body/#root 100%, so served behaviour is unchanged. */
295
+ .pnl { height: 100%; min-height: 480px; }
295
296
  .body { position: relative; z-index: 1; flex: 1; display: flex; min-height: 0; }
296
- .tree { flex: 1; min-width: 0; padding: 24px 26px 24px; overflow: hidden;
297
+ .tree { position: relative; flex: 1; min-width: 0; padding: 24px 26px 24px; overflow: hidden;
297
298
  display: flex; flex-direction: column;
298
299
  /* the ground plane: a faint dot grid (engineered surface, drag affordance)
299
300
  + a verdigris pool anchoring the eye at the root. Static — capture-safe. */
@@ -310,11 +311,23 @@ body { font-family: var(--font); color: var(--ink); background: var(--bg); overf
310
311
  /* the measuring viewport — flex:1 + min-height:0 lets it consume the height
311
312
  .tree gives it; align/justify center the scaled block for narrow orgs. */
312
313
  .chart-fit { position: relative; flex: 1; min-height: 0; display: flex; align-items: flex-start; justify-content: center; overflow: hidden; }
313
- .fit-badge {
314
- position: absolute; right: 10px; bottom: 8px; font-size: 10px; font-family: var(--mono);
315
- color: var(--ink-3); background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px;
316
- padding: 2px 7px; opacity: 0.7; pointer-events: none; animation: settle 0.4s ease both;
314
+ /* user-zoomed: native scroll is the pan; top-left origin keeps the scaled
315
+ overflow fully reachable (top/center origin would clip the left half). */
316
+ .chart-fit.zoomed { overflow: auto; justify-content: flex-start; }
317
+ .chart-fit.zoomed .chart { transform-origin: top left; }
318
+ .zoomer {
319
+ position: absolute; right: 34px; bottom: 32px; z-index: 3; display: flex; align-items: center; gap: 2px;
320
+ font-family: var(--mono); font-size: 11px; color: var(--ink-2);
321
+ background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px; padding: 3px 6px;
322
+ box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
317
323
  }
324
+ .zoomer button {
325
+ all: unset; cursor: pointer; padding: 2px 8px; border-radius: 5px; color: var(--ink-2);
326
+ font-family: var(--mono); font-size: 12px; line-height: 1.4;
327
+ }
328
+ .zoomer button:hover { background: var(--accent-dim); color: var(--ink); }
329
+ .zoomer button.on { color: var(--accent); }
330
+ .zoomer-pct { min-width: 40px; text-align: center; color: var(--ink-3); }
318
331
  /* min-width:max-content keeps connectors geometrically correct at natural size
319
332
  BEFORE scaling; the measured --fit shrinks the whole block toward its head. */
320
333
  .chart {
@@ -1184,6 +1197,8 @@ function OrgScreen({
1184
1197
  const fitRef = useRef(null);
1185
1198
  const chartRef = useRef(null);
1186
1199
  const [fit, setFit] = useState(1);
1200
+ const [zoom, setZoom] = useState(null);
1201
+ const eff = zoom ?? fit;
1187
1202
  useLayoutEffect(() => {
1188
1203
  const vp = fitRef.current, chart = chartRef.current;
1189
1204
  if (!vp || !chart) return;
@@ -1201,31 +1216,47 @@ function OrgScreen({
1201
1216
  ro.observe(chart);
1202
1217
  return () => ro.disconnect();
1203
1218
  }, [draft, health, selected]);
1219
+ const effRef = useRef(eff);
1220
+ effRef.current = eff;
1221
+ useEffect(() => {
1222
+ const vp = fitRef.current;
1223
+ if (!vp) return;
1224
+ const onWheel = (e) => {
1225
+ if (!e.ctrlKey && !e.metaKey) return;
1226
+ e.preventDefault();
1227
+ const k = e.deltaY < 0 ? 1.12 : 1 / 1.12;
1228
+ setZoom(Math.min(2.5, Math.max(0.2, effRef.current * k)));
1229
+ };
1230
+ vp.addEventListener("wheel", onWheel, { passive: false });
1231
+ return () => vp.removeEventListener("wheel", onWheel);
1232
+ }, []);
1204
1233
  const root = draft.agents.find((a) => a.id === draft.root);
1205
1234
  return /* @__PURE__ */ jsxs("div", { className: "body", onClick: () => setSelected(null), children: [
1206
1235
  /* @__PURE__ */ jsxs("main", { className: "tree", onClick: (e) => e.stopPropagation(), children: [
1207
1236
  /* @__PURE__ */ jsx("p", { className: "tree-hint", children: "drag an agent onto its new parent \xB7 click for its dossier \xB7 machine trays show live health" }),
1208
- /* @__PURE__ */ jsxs("div", { className: "chart-fit", ref: fitRef, children: [
1209
- /* @__PURE__ */ jsx("div", { className: "chart", ref: chartRef, style: { ["--fit"]: fit }, children: root && /* @__PURE__ */ jsx(
1210
- ChartNode,
1211
- {
1212
- org: draft,
1213
- a: root,
1214
- depth: 0,
1215
- order: 0,
1216
- selected,
1217
- dragId,
1218
- health,
1219
- onSelect: setSelected,
1220
- onDragStart: setDragId,
1221
- onDropOn: dropOn
1222
- }
1223
- ) }),
1224
- fit < 0.999 && /* @__PURE__ */ jsxs("span", { className: "fit-badge", children: [
1225
- "fit ",
1226
- Math.round(fit * 100),
1237
+ /* @__PURE__ */ jsx("div", { className: `chart-fit${zoom !== null ? " zoomed" : ""}`, ref: fitRef, children: /* @__PURE__ */ jsx("div", { className: "chart", ref: chartRef, style: { ["--fit"]: eff }, children: root && /* @__PURE__ */ jsx(
1238
+ ChartNode,
1239
+ {
1240
+ org: draft,
1241
+ a: root,
1242
+ depth: 0,
1243
+ order: 0,
1244
+ selected,
1245
+ dragId,
1246
+ health,
1247
+ onSelect: setSelected,
1248
+ onDragStart: setDragId,
1249
+ onDropOn: dropOn
1250
+ }
1251
+ ) }) }),
1252
+ /* @__PURE__ */ jsxs("div", { className: "zoomer", children: [
1253
+ /* @__PURE__ */ jsx("button", { type: "button", title: "zoom out (ctrl+wheel)", onClick: () => setZoom(Math.max(0.2, eff / 1.25)), children: "\u2212" }),
1254
+ /* @__PURE__ */ jsxs("span", { className: "zoomer-pct", children: [
1255
+ Math.round(eff * 100),
1227
1256
  "%"
1228
- ] })
1257
+ ] }),
1258
+ /* @__PURE__ */ jsx("button", { type: "button", title: "zoom in (ctrl+wheel)", onClick: () => setZoom(Math.min(2.5, eff * 1.25)), children: "\uFF0B" }),
1259
+ /* @__PURE__ */ jsx("button", { type: "button", className: zoom === null ? "on" : "", title: "fit the whole org in view", onClick: () => setZoom(null), children: "fit" })
1229
1260
  ] })
1230
1261
  ] }),
1231
1262
  selected && /* @__PURE__ */ jsx(