@xenosystem/blocks 0.12.3 → 0.12.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.
@@ -573,12 +573,12 @@ var LayersPanelController = class {
573
573
  selectNode(id, mods = {}) {
574
574
  if (!this.tree.nodes[id]) return;
575
575
  const multi = this.multiSelect;
576
- const toggle = multi && !!(mods.ctrl || mods.meta);
576
+ const toggle2 = multi && !!(mods.ctrl || mods.meta);
577
577
  const range = multi && !!mods.shift;
578
578
  if (range && this.anchorId) {
579
579
  this.selectedIds = rangeBetween(this.getRows(), this.anchorId, id);
580
580
  this.activeId = id;
581
- } else if (toggle) {
581
+ } else if (toggle2) {
582
582
  this.selectedIds = this.selectedIds.includes(id) ? this.selectedIds.filter((s) => s !== id) : [...this.selectedIds, id];
583
583
  this.activeId = this.selectedIds.includes(id) ? id : this.selectedIds[this.selectedIds.length - 1] ?? null;
584
584
  this.anchorId = id;
@@ -1201,6 +1201,204 @@ var LayersPanelController = class {
1201
1201
 
1202
1202
  // src/edit/layers/react/LayersPanelView.tsx
1203
1203
  import { useCallback, useEffect as useEffect2, useRef as useRef2, useState, useSyncExternalStore } from "react";
1204
+
1205
+ // src/edit/layers/declaration.ts
1206
+ function layersViewState(c) {
1207
+ const rows = c.getRows();
1208
+ const window_ = c.getWindow();
1209
+ const columns = c.getColumns();
1210
+ const rowHeight = c.getRowHeight();
1211
+ const renamingId = c.getRenamingId();
1212
+ const drag = c.getDrag();
1213
+ const activeId = c.getActiveId();
1214
+ return {
1215
+ searchShown: c.isSearchShown(),
1216
+ filter: c.getFilter(),
1217
+ totalHeight: window_.totalHeight,
1218
+ offsetTop: window_.offsetTop,
1219
+ rows: c.getWindowRows().map((row) => {
1220
+ const node = row.node;
1221
+ const container = isContainer(node);
1222
+ return {
1223
+ id: row.id,
1224
+ name: node.name,
1225
+ title: node.name,
1226
+ depth: row.depth,
1227
+ height: rowHeight,
1228
+ selected: c.isSelected(row.id),
1229
+ active: activeId === row.id,
1230
+ dimmed: !row.matched,
1231
+ dropHint: drag?.target && drag.target.targetId === row.id ? drag.target.position : null,
1232
+ expanded: container ? row.expanded : void 0,
1233
+ renaming: renamingId === row.id,
1234
+ notRenaming: renamingId !== row.id,
1235
+ hasThumbnail: columns.includes("thumbnail") && !!node.thumbnailKey,
1236
+ noThumbnail: !(columns.includes("thumbnail") && !!node.thumbnailKey),
1237
+ thumbnailKey: node.thumbnailKey,
1238
+ showVisibility: columns.includes("visibility") && node.visible !== void 0,
1239
+ visible: !!node.visible,
1240
+ visibilityLabel: node.visible ? "Hide layer" : "Show layer",
1241
+ showLock: columns.includes("lock") && node.locked !== void 0,
1242
+ locked: !!node.locked,
1243
+ lockLabel: node.locked ? "Unlock layer" : "Lock layer",
1244
+ showSolo: columns.includes("solo") && node.solo !== void 0,
1245
+ solo: !!node.solo,
1246
+ showMute: columns.includes("mute") && node.muted !== void 0,
1247
+ muted: !!node.muted,
1248
+ showArm: columns.includes("arm") && node.armed !== void 0,
1249
+ armed: !!node.armed,
1250
+ showColor: columns.includes("colorLabel") && node.colorLabel !== void 0,
1251
+ colorLabel: node.colorLabel ?? null,
1252
+ adornments: (node.adornments ?? []).map((a, i) => ({ key: a.id ?? `${a.kind}-${i}`, text: a.kind === "spinner" ? "\u2026" : a.label ?? "", title: a.tooltip })),
1253
+ showOpacity: columns.includes("opacity") && node.opacity !== void 0,
1254
+ opacity: node.opacity !== void 0 ? `${Math.round(node.opacity * 100)}%` : ""
1255
+ };
1256
+ }),
1257
+ empty: rows.length === 0,
1258
+ count: `${rows.length} ${rows.length === 1 ? "layer" : "layers"}`,
1259
+ orderLabel: c.getOrderLabel() || void 0,
1260
+ thumbnailSize: c.getThumbnailSize()
1261
+ };
1262
+ }
1263
+ function applyLayersUi(c, intent) {
1264
+ switch (intent.kind) {
1265
+ case "filter":
1266
+ c.setFilter(intent.value);
1267
+ break;
1268
+ case "toggleExpanded":
1269
+ c.toggleExpanded(intent.id);
1270
+ break;
1271
+ case "toggleProp":
1272
+ c.toggleProp(intent.prop, intent.id);
1273
+ break;
1274
+ case "beginRename":
1275
+ c.beginRename(intent.id);
1276
+ break;
1277
+ case "contextMenu":
1278
+ c.requestContextMenu(intent.id, intent.x, intent.y);
1279
+ break;
1280
+ }
1281
+ }
1282
+ var layersViewManifest = {
1283
+ id: layersManifest.id,
1284
+ version: "1.0.0",
1285
+ title: "Layers",
1286
+ category: "edit",
1287
+ layout: { slot: "side" },
1288
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The LayersViewState the runtime derives from the controller on every draw." }],
1289
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, id?, prop?, value?, x?, y? } \u2014 applied by the runtime to the controller." }],
1290
+ commands: []
1291
+ };
1292
+ var EMPTY = { searchShown: true, filter: "", totalHeight: 0, offsetTop: 0, rows: [], empty: true, count: "0 layers", thumbnailSize: 24 };
1293
+ var BEHAVIOURS = {
1294
+ "edit/layersPanelView": (inputs) => inputs.view ?? EMPTY
1295
+ };
1296
+ var text = (b) => ({ component: "text", props: { text: b } });
1297
+ var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
1298
+ var toggle = (prop) => ui("toggleProp", { id: { item: "id" }, prop: { value: prop } });
1299
+ var LAYERS_DECLARATION = {
1300
+ format: "xeno.block@1",
1301
+ manifest: layersViewManifest,
1302
+ behaviour: "edit/layersPanelView",
1303
+ composition: {
1304
+ component: "chrome/Column",
1305
+ props: { className: { value: "xeno-layers-panel" } },
1306
+ children: [
1307
+ {
1308
+ component: "chrome/Toolbar",
1309
+ when: { input: "view.searchShown" },
1310
+ children: [{ component: "chrome/SearchField", props: { value: { input: "view.filter" }, placeholder: { value: "Search layers" }, label: { value: "Search layers" } }, on: { onChange: ui("filter", { value: { arg: "" } }) } }]
1311
+ },
1312
+ {
1313
+ component: "layers/Scroller",
1314
+ children: [
1315
+ {
1316
+ component: "chrome/RowList",
1317
+ props: { role: { value: "tree" }, "aria-label": { value: "Layers" } },
1318
+ children: [
1319
+ {
1320
+ component: "chrome/VirtualWindow",
1321
+ props: { totalHeight: { input: "view.totalHeight" }, offsetTop: { input: "view.offsetTop" } },
1322
+ children: [
1323
+ {
1324
+ component: "chrome/Row",
1325
+ each: { input: "view.rows", key: "id" },
1326
+ props: {
1327
+ "data-layer-row": { item: "id" },
1328
+ height: { item: "height" },
1329
+ depth: { item: "depth" },
1330
+ selected: { item: "selected" },
1331
+ active: { item: "active" },
1332
+ dimmed: { item: "dimmed" },
1333
+ dropHint: { item: "dropHint" },
1334
+ expanded: { item: "expanded" },
1335
+ title: { item: "title" },
1336
+ icon: {
1337
+ node: {
1338
+ component: "fragment",
1339
+ children: [
1340
+ { component: "layers/Thumbnail", when: { item: "hasThumbnail" }, props: { thumbnailKey: { item: "thumbnailKey" }, size: { input: "view.thumbnailSize" } } },
1341
+ { component: "layers/KindGlyph", when: { item: "noThumbnail" } }
1342
+ ]
1343
+ }
1344
+ },
1345
+ leading: {
1346
+ node: {
1347
+ component: "fragment",
1348
+ children: [
1349
+ { component: "chrome/IconButton", when: { item: "showVisibility" }, props: { size: { value: "sm" }, icon: { node: { component: "layers/VisibilityIcon", props: { open: { item: "visible" } } } }, label: { item: "visibilityLabel" }, active: { item: "visible" } }, on: { onClick: toggle("visible") } },
1350
+ { component: "chrome/IconButton", when: { item: "showLock" }, props: { size: { value: "sm" }, icon: { node: { component: "layers/LockStateIcon", props: { locked: { item: "locked" } } } }, label: { item: "lockLabel" }, active: { item: "locked" } }, on: { onClick: toggle("locked") } },
1351
+ { component: "chrome/ChipToggle", when: { item: "showSolo" }, props: { glyph: { value: "S" }, label: { value: "Solo" }, on: { item: "solo" } }, on: { onChange: toggle("solo") } },
1352
+ { component: "chrome/ChipToggle", when: { item: "showMute" }, props: { glyph: { value: "M" }, label: { value: "Mute" }, on: { item: "muted" } }, on: { onChange: toggle("muted") } },
1353
+ { component: "chrome/ChipToggle", when: { item: "showArm" }, props: { glyph: { value: "R" }, label: { value: "Arm" }, on: { item: "armed" } }, on: { onChange: toggle("armed") } },
1354
+ { component: "chrome/ColorTag", when: { item: "showColor" }, props: { color: { item: "colorLabel" } } }
1355
+ ]
1356
+ }
1357
+ },
1358
+ label: {
1359
+ node: {
1360
+ component: "fragment",
1361
+ children: [
1362
+ { component: "layers/RenameField", when: { item: "renaming" }, props: { id: { item: "id" } } },
1363
+ { component: "text", when: { item: "notRenaming" }, props: { text: { item: "name" } } }
1364
+ ]
1365
+ }
1366
+ },
1367
+ trailing: {
1368
+ node: {
1369
+ component: "fragment",
1370
+ children: [
1371
+ { component: "chrome/Badge", each: { item: "adornments", key: "key" }, props: { size: { value: "sm" }, title: { item: "title" } }, children: [text({ item: "text" })] },
1372
+ { component: "chrome/Text", when: { item: "showOpacity" }, props: { role: { value: "number" } }, children: [text({ item: "opacity" })] }
1373
+ ]
1374
+ }
1375
+ }
1376
+ },
1377
+ on: {
1378
+ onExpandedChange: ui("toggleExpanded", { id: { item: "id" } }),
1379
+ onDoubleClick: ui("beginRename", { id: { item: "id" } }),
1380
+ onContextMenuAt: ui("contextMenu", { id: { item: "id" }, x: { arg: "" }, y: { arg: "", index: 1 } })
1381
+ }
1382
+ }
1383
+ ]
1384
+ }
1385
+ ]
1386
+ },
1387
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No layers" } } }
1388
+ ]
1389
+ },
1390
+ {
1391
+ component: "chrome/StatusBar",
1392
+ props: {
1393
+ left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text({ input: "view.count" })] } },
1394
+ right: { node: { component: "chrome/Text", when: { input: "view.orderLabel" }, props: { role: { value: "meta" } }, children: [text({ input: "view.orderLabel" })] } }
1395
+ }
1396
+ }
1397
+ ]
1398
+ }
1399
+ };
1400
+
1401
+ // src/edit/layers/react/LayersPanelView.tsx
1204
1402
  import { Badge, ChipToggle, ColorTag, Column, EmptyState, IconButton, Input, RowList, Row, ScrollArea, SearchField, StatusBar, Text, Toolbar, VirtualWindow } from "@xenosystem/components/chrome";
1205
1403
 
1206
1404
  // src/edit/layers/react/icons.tsx
@@ -1244,6 +1442,12 @@ function UnlockIcon({ size = 12, className }) {
1244
1442
  function KindGlyph({ size = 10 }) {
1245
1443
  return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("rect", { x: "4", y: "4", width: "16", height: "16", rx: "2" }) });
1246
1444
  }
1445
+ function VisibilityIcon({ open, size }) {
1446
+ return open ? /* @__PURE__ */ jsx(EyeIcon, { size }) : /* @__PURE__ */ jsx(EyeOffIcon, { size });
1447
+ }
1448
+ function LockStateIcon({ locked, size }) {
1449
+ return locked ? /* @__PURE__ */ jsx(LockIcon, { size }) : /* @__PURE__ */ jsx(UnlockIcon, { size });
1450
+ }
1247
1451
 
1248
1452
  // src/edit/layers/react/Thumbnail.tsx
1249
1453
  import { useEffect, useRef } from "react";
@@ -1308,16 +1512,20 @@ function LayersPanelView({ controller }) {
1308
1512
  const subscribe = useCallback((cb) => controller.subscribe(cb), [controller]);
1309
1513
  const getSnapshot = useCallback(() => controller.getRevision(), [controller]);
1310
1514
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1515
+ const view = layersViewState(controller);
1516
+ const ui4 = (intent) => applyLayersUi(controller, intent);
1517
+ return /* @__PURE__ */ jsxs2(Column, { className: "xeno-layers-panel", children: [
1518
+ view.searchShown ? /* @__PURE__ */ jsx3(Toolbar, { children: /* @__PURE__ */ jsx3(SearchField, { value: view.filter, onChange: (v) => ui4({ kind: "filter", value: v }), placeholder: "Search layers", label: "Search layers" }) }) : null,
1519
+ /* @__PURE__ */ jsxs2(LayersScroller, { controller, children: [
1520
+ /* @__PURE__ */ jsx3(RowList, { role: "tree", "aria-label": "Layers", children: /* @__PURE__ */ jsx3(VirtualWindow, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row) => /* @__PURE__ */ jsx3(LayerRow, { row, thumbnailSize: view.thumbnailSize, controller }, row.id)) }) }),
1521
+ view.empty ? /* @__PURE__ */ jsx3(EmptyState, { title: "No layers" }) : null
1522
+ ] }),
1523
+ /* @__PURE__ */ jsx3(StatusBar, { left: /* @__PURE__ */ jsx3(Text, { role: "number", children: view.count }), right: view.orderLabel ? /* @__PURE__ */ jsx3(Text, { role: "meta", children: view.orderLabel }) : null })
1524
+ ] });
1525
+ }
1526
+ function LayersScroller({ controller, children }) {
1311
1527
  const scrollRef = useRef2(null);
1312
1528
  const pressRef = useRef2(null);
1313
- const [renameDraft, setRenameDraft] = useState("");
1314
- const rows = controller.getRows();
1315
- const window_ = controller.getWindow();
1316
- const windowRows = controller.getWindowRows();
1317
- const columns = controller.getColumns();
1318
- const rowHeight = controller.getRowHeight();
1319
- const renamingId = controller.getRenamingId();
1320
- const drag = controller.getDrag();
1321
1529
  useEffect2(() => {
1322
1530
  const el = scrollRef.current;
1323
1531
  if (!el) return;
@@ -1335,16 +1543,8 @@ function LayersPanelView({ controller }) {
1335
1543
  if (el && Math.abs(el.scrollTop - controller.getScrollTop()) > 1) {
1336
1544
  el.scrollTop = controller.getScrollTop();
1337
1545
  }
1338
- }, [controller, window_.offsetTop]);
1339
- useEffect2(() => {
1340
- if (renamingId) {
1341
- setRenameDraft(controller.getTree().nodes[renamingId]?.name ?? "");
1342
- }
1343
- }, [renamingId, controller]);
1344
- const onScroll = useCallback(
1345
- (e) => controller.setScrollTop(e.currentTarget.scrollTop),
1346
- [controller]
1347
- );
1546
+ });
1547
+ const onScroll = useCallback((e) => controller.setScrollTop(e.currentTarget.scrollTop), [controller]);
1348
1548
  const rowFromPoint = useCallback((clientX, clientY) => {
1349
1549
  const el = document.elementFromPoint(clientX, clientY);
1350
1550
  const rowEl = el?.closest?.("[data-layer-row]");
@@ -1353,17 +1553,21 @@ function LayersPanelView({ controller }) {
1353
1553
  if (!id) return null;
1354
1554
  return { id, offsetY: clientY - rowEl.getBoundingClientRect().top };
1355
1555
  }, []);
1356
- const onRowPointerDown = useCallback(
1357
- (e, row) => {
1556
+ const onPointerDown = useCallback(
1557
+ (e) => {
1358
1558
  if (e.button !== 0) return;
1359
- if (e.target?.closest?.("button,input")) return;
1360
- pressRef.current = { id: row.id, x: e.clientX, y: e.clientY, dragging: false };
1361
- controller.selectNode(row.id, { ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey });
1559
+ const target = e.target;
1560
+ if (target?.closest?.("button,input")) return;
1561
+ const rowEl = target?.closest?.("[data-layer-row]");
1562
+ const id = rowEl?.getAttribute("data-layer-row");
1563
+ if (!id) return;
1564
+ pressRef.current = { id, x: e.clientX, y: e.clientY, dragging: false };
1565
+ controller.selectNode(id, { ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey });
1362
1566
  e.currentTarget.setPointerCapture(e.pointerId);
1363
1567
  },
1364
1568
  [controller]
1365
1569
  );
1366
- const onRowPointerMove = useCallback(
1570
+ const onPointerMove = useCallback(
1367
1571
  (e) => {
1368
1572
  const press = pressRef.current;
1369
1573
  if (!press) return;
@@ -1377,7 +1581,7 @@ function LayersPanelView({ controller }) {
1377
1581
  },
1378
1582
  [controller, rowFromPoint]
1379
1583
  );
1380
- const onRowPointerUp = useCallback(
1584
+ const onPointerUp = useCallback(
1381
1585
  (e) => {
1382
1586
  const press = pressRef.current;
1383
1587
  pressRef.current = null;
@@ -1391,128 +1595,102 @@ function LayersPanelView({ controller }) {
1391
1595
  );
1392
1596
  const onKeyDown = useCallback(
1393
1597
  (e) => {
1394
- if (renamingId) return;
1395
- const handled = controller.handleKey({
1396
- key: e.key,
1397
- ctrl: e.ctrlKey,
1398
- meta: e.metaKey,
1399
- shift: e.shiftKey
1400
- });
1598
+ if (controller.getRenamingId()) return;
1599
+ const handled = controller.handleKey({ key: e.key, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey });
1401
1600
  if (handled) {
1402
1601
  e.preventDefault();
1403
1602
  e.stopPropagation();
1404
1603
  }
1405
1604
  },
1406
- [controller, renamingId]
1605
+ [controller]
1407
1606
  );
1408
- return /* @__PURE__ */ jsxs2(Column, { className: "xeno-layers-panel", children: [
1409
- controller.isSearchShown() ? /* @__PURE__ */ jsx3(Toolbar, { children: /* @__PURE__ */ jsx3(SearchField, { value: controller.getFilter(), onChange: (v) => controller.setFilter(v), placeholder: "Search layers", label: "Search layers" }) }) : null,
1410
- /* @__PURE__ */ jsxs2(ScrollArea, { ref: scrollRef, onScroll, onKeyDown, tabIndex: 0, children: [
1411
- /* @__PURE__ */ jsx3(RowList, { role: "tree", "aria-label": "Layers", children: /* @__PURE__ */ jsx3(VirtualWindow, { totalHeight: window_.totalHeight, offsetTop: window_.offsetTop, children: windowRows.map((row) => /* @__PURE__ */ jsx3(
1412
- LayerRow,
1413
- {
1414
- row,
1415
- controller,
1416
- columns,
1417
- rowHeight,
1418
- selected: controller.isSelected(row.id),
1419
- active: controller.getActiveId() === row.id,
1420
- renaming: renamingId === row.id,
1421
- renameDraft,
1422
- onRenameDraft: setRenameDraft,
1423
- dropHint: drag?.target && drag.target.targetId === row.id ? drag.target.position : null,
1424
- onPointerDown: onRowPointerDown,
1425
- onPointerMove: onRowPointerMove,
1426
- onPointerUp: onRowPointerUp
1427
- },
1428
- row.id
1429
- )) }) }),
1430
- rows.length === 0 ? /* @__PURE__ */ jsx3(EmptyState, { title: "No layers" }) : null
1431
- ] }),
1432
- /* @__PURE__ */ jsx3(
1433
- StatusBar,
1434
- {
1435
- left: /* @__PURE__ */ jsxs2(Text, { role: "number", children: [
1436
- rows.length,
1437
- " ",
1438
- rows.length === 1 ? "layer" : "layers"
1439
- ] }),
1440
- right: controller.getOrderLabel() ? /* @__PURE__ */ jsx3(Text, { role: "meta", children: controller.getOrderLabel() }) : null
1441
- }
1442
- )
1443
- ] });
1607
+ return /* @__PURE__ */ jsx3(ScrollArea, { ref: scrollRef, onScroll, onKeyDown, onPointerDown, onPointerMove, onPointerUp, tabIndex: 0, children });
1444
1608
  }
1445
- function LayerRow(props) {
1446
- const { row, controller, columns, rowHeight, selected, active, renaming, dropHint } = props;
1447
- const node = row.node;
1448
- const container = isContainer(node);
1449
- const thumb = controller.getThumbnail(node.thumbnailKey);
1609
+ function LayersRenameField({ id, controller }) {
1610
+ const [draft, setDraft] = useState(() => controller.getTree().nodes[id]?.name ?? "");
1611
+ return /* @__PURE__ */ jsx3(
1612
+ Input,
1613
+ {
1614
+ autoFocus: true,
1615
+ value: draft,
1616
+ onChange: setDraft,
1617
+ onBlur: () => controller.commitRename(draft),
1618
+ onKeyDown: (e) => {
1619
+ e.stopPropagation();
1620
+ if (e.key === "Enter") controller.commitRename(draft);
1621
+ if (e.key === "Escape") controller.cancelRename();
1622
+ },
1623
+ label: "Layer name"
1624
+ }
1625
+ );
1626
+ }
1627
+ function LayersThumbnail({ thumbnailKey, size, controller }) {
1628
+ return /* @__PURE__ */ jsx3(Thumbnail, { size, data: controller.getThumbnail(thumbnailKey) });
1629
+ }
1630
+ function LayerRow({ row, thumbnailSize, controller }) {
1631
+ const ui4 = (intent) => applyLayersUi(controller, intent);
1450
1632
  return /* @__PURE__ */ jsx3(
1451
1633
  Row,
1452
1634
  {
1453
1635
  "data-layer-row": row.id,
1454
- height: rowHeight,
1636
+ height: row.height,
1455
1637
  depth: row.depth,
1456
- selected,
1457
- active,
1458
- dimmed: !row.matched,
1459
- dropHint,
1460
- expanded: container ? row.expanded : void 0,
1461
- onExpandedChange: container ? () => controller.toggleExpanded(row.id) : void 0,
1462
- icon: columns.includes("thumbnail") && node.thumbnailKey ? /* @__PURE__ */ jsx3(Thumbnail, { size: controller.getThumbnailSize(), data: thumb }) : /* @__PURE__ */ jsx3(KindGlyph, {}),
1638
+ selected: row.selected,
1639
+ active: row.active,
1640
+ dimmed: row.dimmed,
1641
+ dropHint: row.dropHint,
1642
+ expanded: row.expanded,
1643
+ title: row.title,
1644
+ icon: row.hasThumbnail ? /* @__PURE__ */ jsx3(LayersThumbnail, { thumbnailKey: row.thumbnailKey, size: thumbnailSize, controller }) : /* @__PURE__ */ jsx3(KindGlyph, {}),
1463
1645
  leading: /* @__PURE__ */ jsxs2(Fragment, { children: [
1464
- columns.includes("visibility") && node.visible !== void 0 ? /* @__PURE__ */ jsx3(IconButton, { size: "sm", icon: node.visible ? /* @__PURE__ */ jsx3(EyeIcon, {}) : /* @__PURE__ */ jsx3(EyeOffIcon, {}), label: node.visible ? "Hide layer" : "Show layer", active: node.visible, onClick: () => controller.toggleProp("visible", row.id) }) : null,
1465
- columns.includes("lock") && node.locked !== void 0 ? /* @__PURE__ */ jsx3(IconButton, { size: "sm", icon: node.locked ? /* @__PURE__ */ jsx3(LockIcon, {}) : /* @__PURE__ */ jsx3(UnlockIcon, {}), label: node.locked ? "Unlock layer" : "Lock layer", active: node.locked, onClick: () => controller.toggleProp("locked", row.id) }) : null,
1466
- columns.includes("solo") && node.solo !== void 0 ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "S", label: "Solo", on: !!node.solo, onChange: () => controller.toggleProp("solo", row.id) }) : null,
1467
- columns.includes("mute") && node.muted !== void 0 ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "M", label: "Mute", on: !!node.muted, onChange: () => controller.toggleProp("muted", row.id) }) : null,
1468
- columns.includes("arm") && node.armed !== void 0 ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "R", label: "Arm", on: !!node.armed, onChange: () => controller.toggleProp("armed", row.id) }) : null,
1469
- columns.includes("colorLabel") && node.colorLabel !== void 0 ? /* @__PURE__ */ jsx3(ColorTag, { color: node.colorLabel }) : null
1646
+ row.showVisibility ? /* @__PURE__ */ jsx3(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx3(VisibilityIcon, { open: row.visible }), label: row.visibilityLabel, active: row.visible, onClick: () => ui4({ kind: "toggleProp", id: row.id, prop: "visible" }) }) : null,
1647
+ row.showLock ? /* @__PURE__ */ jsx3(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx3(LockStateIcon, { locked: row.locked }), label: row.lockLabel, active: row.locked, onClick: () => ui4({ kind: "toggleProp", id: row.id, prop: "locked" }) }) : null,
1648
+ row.showSolo ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "S", label: "Solo", on: row.solo, onChange: () => ui4({ kind: "toggleProp", id: row.id, prop: "solo" }) }) : null,
1649
+ row.showMute ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "M", label: "Mute", on: row.muted, onChange: () => ui4({ kind: "toggleProp", id: row.id, prop: "muted" }) }) : null,
1650
+ row.showArm ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "R", label: "Arm", on: row.armed, onChange: () => ui4({ kind: "toggleProp", id: row.id, prop: "armed" }) }) : null,
1651
+ row.showColor ? /* @__PURE__ */ jsx3(ColorTag, { color: row.colorLabel }) : null
1470
1652
  ] }),
1471
- label: renaming ? /* @__PURE__ */ jsx3(
1472
- Input,
1473
- {
1474
- autoFocus: true,
1475
- value: props.renameDraft,
1476
- onChange: (v) => props.onRenameDraft(v),
1477
- onBlur: () => controller.commitRename(props.renameDraft),
1478
- onKeyDown: (e) => {
1479
- e.stopPropagation();
1480
- if (e.key === "Enter") controller.commitRename(props.renameDraft);
1481
- if (e.key === "Escape") controller.cancelRename();
1482
- },
1483
- label: "Layer name"
1484
- }
1485
- ) : node.name,
1653
+ label: row.renaming ? /* @__PURE__ */ jsx3(LayersRenameField, { id: row.id, controller }) : row.name,
1486
1654
  trailing: /* @__PURE__ */ jsxs2(Fragment, { children: [
1487
- node.adornments?.map((a, i) => /* @__PURE__ */ jsx3(Badge, { size: "sm", title: a.tooltip, children: a.kind === "spinner" ? "\u2026" : a.label ?? "" }, a.id ?? `${a.kind}-${i}`)),
1488
- columns.includes("opacity") && node.opacity !== void 0 ? /* @__PURE__ */ jsxs2(Text, { role: "number", children: [
1489
- Math.round(node.opacity * 100),
1490
- "%"
1491
- ] }) : null
1655
+ row.adornments.map((a) => /* @__PURE__ */ jsx3(Badge, { size: "sm", title: a.title, children: a.text }, a.key)),
1656
+ row.showOpacity ? /* @__PURE__ */ jsx3(Text, { role: "number", children: row.opacity }) : null
1492
1657
  ] }),
1493
- onPointerDown: (e) => props.onPointerDown(e, row),
1494
- onPointerMove: props.onPointerMove,
1495
- onPointerUp: props.onPointerUp,
1496
- onDoubleClick: () => controller.beginRename(row.id),
1497
- onContextMenu: (e) => {
1498
- e.preventDefault();
1499
- controller.requestContextMenu(row.id, e.clientX, e.clientY);
1500
- },
1501
- title: node.name
1658
+ onExpandedChange: () => ui4({ kind: "toggleExpanded", id: row.id }),
1659
+ onDoubleClick: () => ui4({ kind: "beginRename", id: row.id }),
1660
+ onContextMenuAt: (x, y) => ui4({ kind: "contextMenu", id: row.id, x, y })
1502
1661
  }
1503
1662
  );
1504
1663
  }
1664
+ function layersRuntimeComponents(controller) {
1665
+ return {
1666
+ "layers/Scroller": ({ children }) => /* @__PURE__ */ jsx3(LayersScroller, { controller, children }),
1667
+ "layers/RenameField": ({ id }) => /* @__PURE__ */ jsx3(LayersRenameField, { id, controller }),
1668
+ "layers/Thumbnail": ({ thumbnailKey, size }) => /* @__PURE__ */ jsx3(LayersThumbnail, { thumbnailKey, size, controller }),
1669
+ "layers/KindGlyph": KindGlyph,
1670
+ "layers/VisibilityIcon": VisibilityIcon,
1671
+ "layers/LockStateIcon": LockStateIcon
1672
+ };
1673
+ }
1505
1674
 
1506
1675
  // src/edit/layers/panel.ts
1507
1676
  function createLayersPanel(options = {}) {
1508
1677
  return {
1509
1678
  manifest: layersManifest,
1510
1679
  activate(host) {
1511
- const controller = new LayersPanelController(host, options);
1680
+ const { render: customRender, ...controllerOptions } = options;
1681
+ const controller = new LayersPanelController(host, controllerOptions);
1512
1682
  void controller.load();
1513
1683
  let root = null;
1684
+ let unrender = null;
1514
1685
  return {
1515
1686
  render(el) {
1687
+ if (customRender) {
1688
+ unrender = customRender(el, controller);
1689
+ return () => {
1690
+ unrender?.();
1691
+ unrender = null;
1692
+ };
1693
+ }
1516
1694
  root = createRoot(el);
1517
1695
  root.render(createElement(LayersPanelView, { controller }));
1518
1696
  return () => {
@@ -1535,6 +1713,8 @@ function createLayersPanel(options = {}) {
1535
1713
  dispose() {
1536
1714
  root?.unmount();
1537
1715
  root = null;
1716
+ unrender?.();
1717
+ unrender = null;
1538
1718
  controller.dispose();
1539
1719
  }
1540
1720
  };
@@ -1871,8 +2051,8 @@ function constraintsOf(field) {
1871
2051
  scale: field.scale
1872
2052
  };
1873
2053
  }
1874
- function parseNumeric(text2) {
1875
- const trimmed = String(text2).trim();
2054
+ function parseNumeric(text4) {
2055
+ const trimmed = String(text4).trim();
1876
2056
  if (trimmed.length === 0) return { ok: false, reason: "empty" };
1877
2057
  const match = /^[+-]?(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(trimmed);
1878
2058
  if (!match) return { ok: false, reason: "nan" };
@@ -1897,8 +2077,8 @@ function normalizeNumeric(value, c) {
1897
2077
  function formatNumeric(value, field) {
1898
2078
  if (!Number.isFinite(value)) return "";
1899
2079
  const precision = field.precision;
1900
- const text2 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
1901
- return field.unit ? `${text2}${field.unit}` : text2;
2080
+ const text4 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
2081
+ return field.unit ? `${text4}${field.unit}` : text4;
1902
2082
  }
1903
2083
  function usesLogScale(c) {
1904
2084
  return c.scale === "log" && typeof c.min === "number" && typeof c.max === "number" && c.min > 0 && c.max > c.min;
@@ -2232,44 +2412,44 @@ var InspectorPanelController = class {
2232
2412
  if (!field || !isFieldEditable(field)) return;
2233
2413
  const mixed = isFieldMixed(field);
2234
2414
  const value = this.getFieldValue(fieldId);
2235
- const text2 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2236
- this.drafts.set(fieldId, { text: text2, original: value });
2415
+ const text4 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2416
+ this.drafts.set(fieldId, { text: text4, original: value });
2237
2417
  this.focusedFieldId = fieldId;
2238
2418
  this.emitFieldFocus(fieldId);
2239
2419
  this.notify();
2240
2420
  }
2241
2421
  /** Update the draft text (typing). Emits nothing under the `blur` policy. */
2242
- setDraft(fieldId, text2) {
2422
+ setDraft(fieldId, text4) {
2243
2423
  const draft = this.drafts.get(fieldId);
2244
2424
  if (!draft) {
2245
- this.drafts.set(fieldId, { text: text2, original: this.getFieldValue(fieldId) });
2425
+ this.drafts.set(fieldId, { text: text4, original: this.getFieldValue(fieldId) });
2246
2426
  } else {
2247
- draft.text = text2;
2427
+ draft.text = text4;
2248
2428
  }
2249
2429
  const field = this.getField(fieldId);
2250
2430
  if (field && commitPolicyOf(field) === "live") {
2251
- const parsed = this.parseDraft(fieldId, text2);
2431
+ const parsed = this.parseDraft(fieldId, text4);
2252
2432
  if (parsed.ok) this.emitSet(fieldId, parsed.value);
2253
2433
  }
2254
2434
  this.notify();
2255
2435
  }
2256
2436
  /** Parse a draft string into a field-appropriate value. */
2257
- parseDraft(fieldId, text2) {
2437
+ parseDraft(fieldId, text4) {
2258
2438
  const field = this.getField(fieldId);
2259
2439
  if (!field) return { ok: false };
2260
2440
  if (field.type === "number") {
2261
- const parsed = parseNumeric(text2);
2441
+ const parsed = parseNumeric(text4);
2262
2442
  if (!parsed.ok) return { ok: false };
2263
2443
  return { ok: true, value: normalizeNumeric(parsed.value, constraintsOf(field)) };
2264
2444
  }
2265
2445
  if (field.type === "json") {
2266
2446
  try {
2267
- return { ok: true, value: JSON.parse(text2) };
2447
+ return { ok: true, value: JSON.parse(text4) };
2268
2448
  } catch {
2269
2449
  return { ok: false };
2270
2450
  }
2271
2451
  }
2272
- return { ok: true, value: text2 };
2452
+ return { ok: true, value: text4 };
2273
2453
  }
2274
2454
  /**
2275
2455
  * Commit the draft (Enter / blur). Invalid input reverts instead of committing garbage.
@@ -2938,12 +3118,12 @@ var inspectorViewManifest = {
2938
3118
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, sectionId?, fieldId?, \u2026 } \u2014 applied by the runtime to the controller." }],
2939
3119
  commands: []
2940
3120
  };
2941
- var EMPTY = { searchShown: true, filter: "", hasTarget: false, targetLabel: "", multi: false, banner: "", sections: [], empty: true, emptyTitle: "Nothing selected" };
2942
- var BEHAVIOURS = {
2943
- "edit/inspectorPanelView": (inputs) => inputs.view ?? EMPTY
3121
+ var EMPTY2 = { searchShown: true, filter: "", hasTarget: false, targetLabel: "", multi: false, banner: "", sections: [], empty: true, emptyTitle: "Nothing selected" };
3122
+ var BEHAVIOURS2 = {
3123
+ "edit/inspectorPanelView": (inputs) => inputs.view ?? EMPTY2
2944
3124
  };
2945
- var text = (b) => ({ component: "text", props: { text: b } });
2946
- var ui = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
3125
+ var text2 = (b) => ({ component: "text", props: { text: b } });
3126
+ var ui2 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
2947
3127
  var iconButton = (glyph, label, emit, extra = {}) => ({
2948
3128
  component: "chrome/IconButton",
2949
3129
  ...extra.when ? { when: extra.when } : {},
@@ -2963,11 +3143,11 @@ var INSPECTOR_DECLARATION = {
2963
3143
  when: { input: "view.searchShown" },
2964
3144
  props: { direction: { value: "row" }, gap: { value: 6 } },
2965
3145
  children: [
2966
- { component: "chrome/SearchField", props: { value: { input: "view.filter" }, placeholder: { value: "Search properties" }, label: { value: "Search properties" } }, on: { onChange: ui("filter", { value: { arg: "" } }) } },
2967
- { component: "chrome/Badge", when: { input: "view.hasTarget" }, children: [text({ input: "view.targetLabel" })] }
3146
+ { component: "chrome/SearchField", props: { value: { input: "view.filter" }, placeholder: { value: "Search properties" }, label: { value: "Search properties" } }, on: { onChange: ui2("filter", { value: { arg: "" } }) } },
3147
+ { component: "chrome/Badge", when: { input: "view.hasTarget" }, children: [text2({ input: "view.targetLabel" })] }
2968
3148
  ]
2969
3149
  },
2970
- { component: "chrome/Stack", when: { input: "view.multi" }, props: { pad: { value: "inset" } }, children: [{ component: "chrome/Text", props: { role: { value: "caption" } }, children: [text({ input: "view.banner" })] }] },
3150
+ { component: "chrome/Stack", when: { input: "view.multi" }, props: { pad: { value: "inset" } }, children: [{ component: "chrome/Text", props: { role: { value: "caption" } }, children: [text2({ input: "view.banner" })] }] },
2971
3151
  {
2972
3152
  component: "chrome/ScrollArea",
2973
3153
  children: [
@@ -2983,7 +3163,7 @@ var INSPECTOR_DECLARATION = {
2983
3163
  collapsible: { item: "collapsible" },
2984
3164
  collapsed: { item: "collapsed" },
2985
3165
  enabled: { item: "enabled" },
2986
- badge: { node: { component: "chrome/Badge", when: { item: "badge" }, children: [text({ item: "badge" })] } },
3166
+ badge: { node: { component: "chrome/Badge", when: { item: "badge" }, children: [text2({ item: "badge" })] } },
2987
3167
  actions: {
2988
3168
  node: {
2989
3169
  component: "fragment",
@@ -2993,24 +3173,24 @@ var INSPECTOR_DECLARATION = {
2993
3173
  component: "chrome/IconButton",
2994
3174
  each: { item: "actions", key: "id" },
2995
3175
  props: { size: { value: "sm" }, icon: { node: { component: "inspector/ActionGlyph", props: { glyph: { item: "glyph" } } } }, label: { item: "label" }, disabled: { item: "disabled" } },
2996
- on: { onClick: ui("runAction", { sectionId: { item: "sectionId" }, actionId: { item: "id" } }) }
3176
+ on: { onClick: ui2("runAction", { sectionId: { item: "sectionId" }, actionId: { item: "id" } }) }
2997
3177
  },
2998
3178
  {
2999
3179
  component: "chrome/Select",
3000
3180
  when: { item: "hasAddMenu" },
3001
3181
  props: { size: { value: "sm" }, placeholder: { value: "\uFF0B" }, options: { item: "addOptions" }, value: { value: "" }, label: { item: "addLabel" } },
3002
- on: { onChange: ui("addItem", { sectionId: { item: "id" }, itemId: { arg: "" } }) }
3182
+ on: { onChange: ui2("addItem", { sectionId: { item: "id" }, itemId: { arg: "" } }) }
3003
3183
  }
3004
3184
  ]
3005
3185
  }
3006
3186
  }
3007
3187
  },
3008
3188
  on: {
3009
- onCollapsedChange: ui("toggleSectionCollapsed", { sectionId: { item: "id" } }),
3010
- onEnabledChange: ui("toggleSectionEnabled", { sectionId: { item: "id" } })
3189
+ onCollapsedChange: ui2("toggleSectionCollapsed", { sectionId: { item: "id" } }),
3190
+ onEnabledChange: ui2("toggleSectionEnabled", { sectionId: { item: "id" } })
3011
3191
  },
3012
3192
  children: [
3013
- { component: "chrome/Stack", when: { item: "showEmptyHint" }, props: { pad: { value: "inset" } }, children: [{ component: "chrome/Text", props: { role: { value: "caption" } }, children: [text({ item: "emptyHint" })] }] },
3193
+ { component: "chrome/Stack", when: { item: "showEmptyHint" }, props: { pad: { value: "inset" } }, children: [{ component: "chrome/Text", props: { role: { value: "caption" } }, children: [text2({ item: "emptyHint" })] }] },
3014
3194
  {
3015
3195
  component: "chrome/FieldGroup",
3016
3196
  props: { labelWidth: { item: "labelWidth" } },
@@ -3030,10 +3210,10 @@ var INSPECTOR_DECLARATION = {
3030
3210
  component: "fragment",
3031
3211
  when: { item: "showKeyframes" },
3032
3212
  children: [
3033
- iconButton("StopwatchIcon", { value: "Toggle animation" }, ui("keyframe", { fieldId: { item: "id" }, op: { value: "toggleAnim" } }), { props: { active: { item: "animated" } } }),
3034
- iconButton("PrevIcon", { value: "Previous keyframe" }, ui("keyframe", { fieldId: { item: "id" }, op: { value: "prev" } }), { when: { item: "animated" }, props: { disabled: { item: "prevDisabled" } } }),
3035
- { component: "chrome/IconButton", when: { item: "animated" }, props: { size: { value: "sm" }, icon: { node: { component: "inspector/DiamondIcon", props: { filled: { item: "onKeyframe" } } } }, label: { item: "keyframeLabel" } }, on: { onClick: ui("keyframe", { fieldId: { item: "id" }, op: { item: "keyframeOp" } }) } },
3036
- iconButton("NextIcon", { value: "Next keyframe" }, ui("keyframe", { fieldId: { item: "id" }, op: { value: "next" } }), { when: { item: "animated" }, props: { disabled: { item: "nextDisabled" } } })
3213
+ iconButton("StopwatchIcon", { value: "Toggle animation" }, ui2("keyframe", { fieldId: { item: "id" }, op: { value: "toggleAnim" } }), { props: { active: { item: "animated" } } }),
3214
+ iconButton("PrevIcon", { value: "Previous keyframe" }, ui2("keyframe", { fieldId: { item: "id" }, op: { value: "prev" } }), { when: { item: "animated" }, props: { disabled: { item: "prevDisabled" } } }),
3215
+ { component: "chrome/IconButton", when: { item: "animated" }, props: { size: { value: "sm" }, icon: { node: { component: "inspector/DiamondIcon", props: { filled: { item: "onKeyframe" } } } }, label: { item: "keyframeLabel" } }, on: { onClick: ui2("keyframe", { fieldId: { item: "id" }, op: { item: "keyframeOp" } }) } },
3216
+ iconButton("NextIcon", { value: "Next keyframe" }, ui2("keyframe", { fieldId: { item: "id" }, op: { value: "next" } }), { when: { item: "animated" }, props: { disabled: { item: "nextDisabled" } } })
3037
3217
  ]
3038
3218
  }
3039
3219
  },
@@ -3041,24 +3221,24 @@ var INSPECTOR_DECLARATION = {
3041
3221
  node: {
3042
3222
  component: "fragment",
3043
3223
  children: [
3044
- { component: "chrome/Select", when: { item: "showEasing" }, props: { size: { value: "sm" }, options: { item: "easingOptions" }, value: { item: "easing" }, label: { value: "Easing" } }, on: { onChange: ui("setEasing", { fieldId: { item: "id" }, easing: { arg: "" } }) } },
3045
- { component: "chrome/Badge", when: { item: "showKeyframes" }, props: { size: { value: "sm" } }, children: [text({ item: "keyframeChip" })] },
3046
- iconButton("LinkIcon", { item: "tokenActionLabel" }, ui("bindToken", { fieldId: { item: "id" } }), { when: { item: "showToken" }, props: { active: { item: "bound" } } }),
3047
- iconButton("ResetIcon", { value: "Reset to default" }, ui("resetField", { fieldId: { item: "id" } }), { when: { item: "showReset" } })
3224
+ { component: "chrome/Select", when: { item: "showEasing" }, props: { size: { value: "sm" }, options: { item: "easingOptions" }, value: { item: "easing" }, label: { value: "Easing" } }, on: { onChange: ui2("setEasing", { fieldId: { item: "id" }, easing: { arg: "" } }) } },
3225
+ { component: "chrome/Badge", when: { item: "showKeyframes" }, props: { size: { value: "sm" } }, children: [text2({ item: "keyframeChip" })] },
3226
+ iconButton("LinkIcon", { item: "tokenActionLabel" }, ui2("bindToken", { fieldId: { item: "id" } }), { when: { item: "showToken" }, props: { active: { item: "bound" } } }),
3227
+ iconButton("ResetIcon", { value: "Reset to default" }, ui2("resetField", { fieldId: { item: "id" } }), { when: { item: "showReset" } })
3048
3228
  ]
3049
3229
  }
3050
3230
  }
3051
3231
  },
3052
3232
  on: {
3053
- onLabelClick: ui("focusField", { fieldId: { item: "id" } }),
3054
- onContextMenu: ui("contextMenu", { fieldId: { item: "id" }, x: { arg: "" }, y: { arg: "", index: 1 } })
3233
+ onLabelClick: ui2("focusField", { fieldId: { item: "id" } }),
3234
+ onContextMenu: ui2("contextMenu", { fieldId: { item: "id" }, x: { arg: "" }, y: { arg: "", index: 1 } })
3055
3235
  },
3056
3236
  children: [
3057
- { component: "chrome/Badge", when: { item: "bound" }, props: { title: { item: "tokenTitle" } }, children: [text({ item: "tokenLabel" })] },
3237
+ { component: "chrome/Badge", when: { item: "bound" }, props: { title: { item: "tokenTitle" } }, children: [text2({ item: "tokenLabel" })] },
3058
3238
  { component: "inspector/FieldWidget", when: { item: "unbound" }, props: { field: { item: "field" } } }
3059
3239
  ]
3060
3240
  },
3061
- { component: "chrome/Stack", when: { item: "showInfoNote" }, props: { pad: { value: "inset" } }, children: [{ component: "chrome/Text", props: { role: { value: "caption" } }, children: [text({ item: "infoNote" })] }] }
3241
+ { component: "chrome/Stack", when: { item: "showInfoNote" }, props: { pad: { value: "inset" } }, children: [{ component: "chrome/Text", props: { role: { value: "caption" } }, children: [text2({ item: "infoNote" })] }] }
3062
3242
  ]
3063
3243
  }
3064
3244
  ]
@@ -3244,7 +3424,7 @@ function NumberWidget({ field, controller, component }) {
3244
3424
  },
3245
3425
  [controller, editable, field.id, component]
3246
3426
  );
3247
- const text2 = component !== void 0 ? (() => {
3427
+ const text4 = component !== void 0 ? (() => {
3248
3428
  const raw = controller.getFieldValue(field.id);
3249
3429
  const v = Array.isArray(raw) ? raw[component] : void 0;
3250
3430
  return typeof v === "number" ? formatNumeric(v, field) : "";
@@ -3256,7 +3436,7 @@ function NumberWidget({ field, controller, component }) {
3256
3436
  Input2,
3257
3437
  {
3258
3438
  inputMode: "decimal",
3259
- value: text2,
3439
+ value: text4,
3260
3440
  placeholder: mixed ? controller.getMixedPlaceholder() : field.placeholder,
3261
3441
  mixed,
3262
3442
  readOnly: !editable,
@@ -3277,16 +3457,16 @@ function VectorWidget({ field, controller }) {
3277
3457
  const expanded = controller.isExpanded(field.id) || !controller.isUniformVector(field.id);
3278
3458
  const value = controller.getFieldValue(field.id);
3279
3459
  const count = Array.isArray(value) ? value.length : field.components?.length ?? 0;
3280
- const toggle = (label, title) => /* @__PURE__ */ jsx5(IconButton2, { size: "sm", icon: /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\u22EF" }), label, onClick: () => controller.toggleExpanded(field.id) });
3460
+ const toggle2 = (label, title) => /* @__PURE__ */ jsx5(IconButton2, { size: "sm", icon: /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: "\u22EF" }), label, onClick: () => controller.toggleExpanded(field.id) });
3281
3461
  if (!expanded && field.expandable) {
3282
3462
  return /* @__PURE__ */ jsxs4(Stack, { direction: "row", gap: 3, pad: "none", children: [
3283
3463
  /* @__PURE__ */ jsx5(NumberWidget, { field, controller, component: 0 }),
3284
- toggle("Expand", "Edit each axis separately")
3464
+ toggle2("Expand", "Edit each axis separately")
3285
3465
  ] });
3286
3466
  }
3287
3467
  return /* @__PURE__ */ jsxs4(Stack, { direction: "row", gap: 3, pad: "none", children: [
3288
3468
  Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx5(NumberWidget, { field, controller, component: i }, i)),
3289
- field.expandable && controller.isUniformVector(field.id) ? toggle("Collapse", "Link axes") : null
3469
+ field.expandable && controller.isUniformVector(field.id) ? toggle2("Collapse", "Link axes") : null
3290
3470
  ] });
3291
3471
  }
3292
3472
  function TextWidget({ field, controller }) {
@@ -3474,7 +3654,7 @@ function ActionGlyph({ glyph }) {
3474
3654
  return /* @__PURE__ */ jsx6("span", { "aria-hidden": true, children: glyph });
3475
3655
  }
3476
3656
  function SectionView({ section, controller }) {
3477
- const ui2 = (intent) => applyInspectorUi(controller, intent);
3657
+ const ui4 = (intent) => applyInspectorUi(controller, intent);
3478
3658
  const hasActions = section.actions.length > 0 || section.hasAddMenu;
3479
3659
  return /* @__PURE__ */ jsxs5(
3480
3660
  Section,
@@ -3483,13 +3663,13 @@ function SectionView({ section, controller }) {
3483
3663
  flush: true,
3484
3664
  collapsible: section.collapsible,
3485
3665
  collapsed: section.collapsed,
3486
- onCollapsedChange: () => ui2({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3666
+ onCollapsedChange: () => ui4({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3487
3667
  enabled: section.enabled,
3488
- onEnabledChange: () => ui2({ kind: "toggleSectionEnabled", sectionId: section.id }),
3668
+ onEnabledChange: () => ui4({ kind: "toggleSectionEnabled", sectionId: section.id }),
3489
3669
  badge: section.badge ? /* @__PURE__ */ jsx6(Badge2, { children: section.badge }) : null,
3490
3670
  actions: hasActions ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3491
- section.actions.map((action) => /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui2({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3492
- section.hasAddMenu ? /* @__PURE__ */ jsx6(Select2, { size: "sm", placeholder: "\uFF0B", options: section.addOptions, value: "", label: section.addLabel, onChange: (id) => ui2({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3671
+ section.actions.map((action) => /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui4({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3672
+ section.hasAddMenu ? /* @__PURE__ */ jsx6(Select2, { size: "sm", placeholder: "\uFF0B", options: section.addOptions, value: "", label: section.addLabel, onChange: (id) => ui4({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3493
3673
  ] }) : null,
3494
3674
  children: [
3495
3675
  section.showEmptyHint ? /* @__PURE__ */ jsx6(Stack2, { pad: "inset", children: /* @__PURE__ */ jsx6(Text3, { role: "caption", children: section.emptyHint }) }) : null,
@@ -3499,7 +3679,7 @@ function SectionView({ section, controller }) {
3499
3679
  );
3500
3680
  }
3501
3681
  function FieldRow({ row, controller }) {
3502
- const ui2 = (intent) => applyInspectorUi(controller, intent);
3682
+ const ui4 = (intent) => applyInspectorUi(controller, intent);
3503
3683
  return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3504
3684
  /* @__PURE__ */ jsxs5(
3505
3685
  Field,
@@ -3507,19 +3687,19 @@ function FieldRow({ row, controller }) {
3507
3687
  label: row.label,
3508
3688
  title: row.title,
3509
3689
  focused: row.focused,
3510
- onLabelClick: () => ui2({ kind: "focusField", fieldId: row.id }),
3511
- onContextMenu: (x, y) => ui2({ kind: "contextMenu", fieldId: row.id, x, y }),
3690
+ onLabelClick: () => ui4({ kind: "focusField", fieldId: row.id }),
3691
+ onContextMenu: (x, y) => ui4({ kind: "contextMenu", fieldId: row.id, x, y }),
3512
3692
  leading: row.showKeyframes ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3513
- /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(StopwatchIcon, {}), label: "Toggle animation", active: row.animated, onClick: () => ui2({ kind: "keyframe", fieldId: row.id, op: "toggleAnim" }) }),
3514
- row.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(PrevIcon, {}), label: "Previous keyframe", disabled: row.prevDisabled, onClick: () => ui2({ kind: "keyframe", fieldId: row.id, op: "prev" }) }) : null,
3515
- row.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(DiamondIcon, { filled: row.onKeyframe }), label: row.keyframeLabel, onClick: () => ui2({ kind: "keyframe", fieldId: row.id, op: row.keyframeOp }) }) : null,
3516
- row.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(NextIcon, {}), label: "Next keyframe", disabled: row.nextDisabled, onClick: () => ui2({ kind: "keyframe", fieldId: row.id, op: "next" }) }) : null
3693
+ /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(StopwatchIcon, {}), label: "Toggle animation", active: row.animated, onClick: () => ui4({ kind: "keyframe", fieldId: row.id, op: "toggleAnim" }) }),
3694
+ row.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(PrevIcon, {}), label: "Previous keyframe", disabled: row.prevDisabled, onClick: () => ui4({ kind: "keyframe", fieldId: row.id, op: "prev" }) }) : null,
3695
+ row.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(DiamondIcon, { filled: row.onKeyframe }), label: row.keyframeLabel, onClick: () => ui4({ kind: "keyframe", fieldId: row.id, op: row.keyframeOp }) }) : null,
3696
+ row.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(NextIcon, {}), label: "Next keyframe", disabled: row.nextDisabled, onClick: () => ui4({ kind: "keyframe", fieldId: row.id, op: "next" }) }) : null
3517
3697
  ] }) : null,
3518
3698
  trailing: /* @__PURE__ */ jsxs5(Fragment3, { children: [
3519
- row.showEasing ? /* @__PURE__ */ jsx6(Select2, { size: "sm", options: row.easingOptions, value: row.easing, label: "Easing", onChange: (v) => ui2({ kind: "setEasing", fieldId: row.id, easing: v }) }) : null,
3699
+ row.showEasing ? /* @__PURE__ */ jsx6(Select2, { size: "sm", options: row.easingOptions, value: row.easing, label: "Easing", onChange: (v) => ui4({ kind: "setEasing", fieldId: row.id, easing: v }) }) : null,
3520
3700
  row.showKeyframes ? /* @__PURE__ */ jsx6(Badge2, { size: "sm", children: row.keyframeChip }) : null,
3521
- row.showToken ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(LinkIcon, {}), label: row.tokenActionLabel, active: row.bound, onClick: () => ui2({ kind: "bindToken", fieldId: row.id }) }) : null,
3522
- row.showReset ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(ResetIcon, {}), label: "Reset to default", onClick: () => ui2({ kind: "resetField", fieldId: row.id }) }) : null
3701
+ row.showToken ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(LinkIcon, {}), label: row.tokenActionLabel, active: row.bound, onClick: () => ui4({ kind: "bindToken", fieldId: row.id }) }) : null,
3702
+ row.showReset ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(ResetIcon, {}), label: "Reset to default", onClick: () => ui4({ kind: "resetField", fieldId: row.id }) }) : null
3523
3703
  ] }),
3524
3704
  children: [
3525
3705
  row.bound ? /* @__PURE__ */ jsx6(Badge2, { title: row.tokenTitle, children: row.tokenLabel }) : null,
@@ -4539,6 +4719,231 @@ var HistoryPanelController = class {
4539
4719
 
4540
4720
  // src/edit/history/react/HistoryPanelView.tsx
4541
4721
  import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4, useSyncExternalStore as useSyncExternalStore3 } from "react";
4722
+
4723
+ // src/edit/history/react/format.ts
4724
+ function formatTime(at) {
4725
+ const date = typeof at === "number" ? new Date(at) : new Date(at);
4726
+ if (Number.isNaN(date.getTime())) return typeof at === "string" ? at : "";
4727
+ return date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
4728
+ }
4729
+
4730
+ // src/edit/history/declaration.ts
4731
+ function historyViewState(c) {
4732
+ const rows = c.getRows();
4733
+ const window_ = c.getWindow();
4734
+ const rowHeight = c.getRowHeight();
4735
+ const memory = c.getMemory();
4736
+ const focused = c.getFocused();
4737
+ const showIcons = c.areIconsShown();
4738
+ const showSource = c.isSourceShown();
4739
+ const showTime = c.areTimestampsShown();
4740
+ const jumpAllowed = c.isJumpAllowed();
4741
+ return {
4742
+ canUndo: c.canUndo(),
4743
+ canRedo: c.canRedo(),
4744
+ cannotUndo: !c.canUndo(),
4745
+ cannotRedo: !c.canRedo(),
4746
+ searchShown: c.isSearchShown(),
4747
+ filter: c.getFilter(),
4748
+ counter: `${c.getAppliedCount()} / ${c.getTotalCount()}`,
4749
+ clearAllowed: c.isClearAllowed(),
4750
+ confirmingClear: c.isConfirmingClear(),
4751
+ pruned: c.isPruned(),
4752
+ totalHeight: window_.totalHeight,
4753
+ offsetTop: window_.offsetTop,
4754
+ rows: c.getWindowRows().map((row) => {
4755
+ const entry = row.entry;
4756
+ const pending = entry?.status === "pending";
4757
+ const source = entry?.source;
4758
+ return {
4759
+ key: row.entryId ?? "__original__",
4760
+ entryId: row.entryId,
4761
+ label: row.label,
4762
+ meta: row.kind === "entry" ? row.ordinal : void 0,
4763
+ current: row.current,
4764
+ future: row.future,
4765
+ focused: focused === row.entryId,
4766
+ jumpable: jumpAllowed && entry?.caps?.jumpable !== false,
4767
+ showIcon: showIcons,
4768
+ hideIcon: !showIcons,
4769
+ iconName: row.kind === "original" ? "original" : c.getIconName(entry?.kind),
4770
+ pending,
4771
+ failed: entry?.status === "failed",
4772
+ showSource: showSource && !!source && source !== "user",
4773
+ sourceGlyph: source === "agent" ? "AI" : "S",
4774
+ sourceTitle: `Source: ${source ?? ""}`,
4775
+ showCoalesced: !!entry?.coalesced && entry.coalesced > 1,
4776
+ coalescedText: `\xD7${entry?.coalesced ?? 0}`,
4777
+ coalescedTitle: `${entry?.coalesced ?? 0} actions coalesced`,
4778
+ showTime: showTime && entry?.at !== void 0,
4779
+ time: entry?.at !== void 0 ? formatTime(entry.at) : "",
4780
+ height: rowHeight,
4781
+ cancelId: pending ? entry?.id : void 0
4782
+ };
4783
+ }),
4784
+ empty: rows.length === 0,
4785
+ hasMemory: !!memory,
4786
+ memoryText: memory ? `${memory.usedMB.toFixed(1)} MB${memory.limitMB ? ` / ${memory.limitMB} MB` : ""}` : "",
4787
+ memoryDisk: memory?.onDiskCount !== void 0 ? `${memory.onDiskCount} on disk` : void 0
4788
+ };
4789
+ }
4790
+ function applyHistoryUi(c, intent) {
4791
+ switch (intent.kind) {
4792
+ case "undo":
4793
+ c.undo();
4794
+ break;
4795
+ case "redo":
4796
+ c.redo();
4797
+ break;
4798
+ case "filter":
4799
+ c.setFilter(intent.value);
4800
+ break;
4801
+ case "requestClear":
4802
+ c.requestClear();
4803
+ break;
4804
+ case "confirmClear":
4805
+ c.confirmClearNow();
4806
+ break;
4807
+ case "cancelClear":
4808
+ c.cancelClear();
4809
+ break;
4810
+ case "jump":
4811
+ c.jumpTo(intent.entryId);
4812
+ break;
4813
+ case "hover":
4814
+ c.setHovered(intent.entryId);
4815
+ break;
4816
+ case "contextMenu":
4817
+ c.requestContextMenu(intent.entryId, intent.x, intent.y);
4818
+ break;
4819
+ case "cancelPending":
4820
+ c.cancelPending(intent.entryId);
4821
+ break;
4822
+ }
4823
+ }
4824
+ var historyViewManifest = {
4825
+ id: historyManifest.id,
4826
+ version: "1.0.0",
4827
+ title: "History",
4828
+ category: "edit",
4829
+ layout: { slot: "side" },
4830
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The HistoryViewState the runtime derives from the controller on every draw." }],
4831
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, entryId?, value?, x?, y? } \u2014 applied by the runtime to the controller." }],
4832
+ commands: []
4833
+ };
4834
+ var EMPTY3 = { canUndo: false, canRedo: false, cannotUndo: true, cannotRedo: true, searchShown: true, filter: "", counter: "0 / 0", clearAllowed: false, confirmingClear: false, pruned: false, totalHeight: 0, offsetTop: 0, rows: [], empty: true, hasMemory: false, memoryText: "" };
4835
+ var BEHAVIOURS3 = {
4836
+ "edit/historyPanelView": (inputs) => inputs.view ?? EMPTY3
4837
+ };
4838
+ var text3 = (b) => ({ component: "text", props: { text: b } });
4839
+ var ui3 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4840
+ var HISTORY_DECLARATION = {
4841
+ format: "xeno.block@1",
4842
+ manifest: historyViewManifest,
4843
+ behaviour: "edit/historyPanelView",
4844
+ composition: {
4845
+ component: "chrome/Column",
4846
+ props: { className: { value: "xeno-history-panel" } },
4847
+ children: [
4848
+ {
4849
+ component: "chrome/Toolbar",
4850
+ props: {
4851
+ left: {
4852
+ node: {
4853
+ component: "fragment",
4854
+ children: [
4855
+ { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "history/UndoIcon" } }, label: { value: "Undo" }, disabled: { input: "view.cannotUndo" } }, on: { onClick: ui3("undo") } },
4856
+ { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "history/RedoIcon" } }, label: { value: "Redo" }, disabled: { input: "view.cannotRedo" } }, on: { onClick: ui3("redo") } }
4857
+ ]
4858
+ }
4859
+ },
4860
+ right: {
4861
+ node: {
4862
+ component: "fragment",
4863
+ children: [
4864
+ { component: "chrome/Text", props: { role: { value: "number" } }, children: [text3({ input: "view.counter" })] },
4865
+ { component: "chrome/IconButton", when: { input: "view.clearAllowed" }, props: { size: { value: "sm" }, icon: { node: { component: "history/TrashIcon" } }, label: { value: "Clear history" } }, on: { onClick: ui3("requestClear") } }
4866
+ ]
4867
+ }
4868
+ }
4869
+ },
4870
+ children: [
4871
+ { component: "chrome/SearchField", when: { input: "view.searchShown" }, props: { value: { input: "view.filter" }, placeholder: { value: "Search history" }, label: { value: "Search history" } }, on: { onChange: ui3("filter", { value: { arg: "" } }) } }
4872
+ ]
4873
+ },
4874
+ {
4875
+ component: "chrome/InlineConfirm",
4876
+ when: { input: "view.confirmingClear" },
4877
+ props: { message: { value: "Clear all history?" }, label: { value: "Confirm clear history" }, confirmLabel: { value: "Clear" } },
4878
+ on: { onConfirm: ui3("confirmClear"), onCancel: ui3("cancelClear") }
4879
+ },
4880
+ { component: "chrome/Text", when: { input: "view.pruned" }, props: { role: { value: "caption" } }, children: [text3({ value: "Earlier history discarded (limit reached)" })] },
4881
+ {
4882
+ component: "history/Scroller",
4883
+ children: [
4884
+ {
4885
+ component: "chrome/RowList",
4886
+ props: { role: { value: "listbox" }, "aria-label": { value: "History" } },
4887
+ children: [
4888
+ {
4889
+ component: "chrome/VirtualWindow",
4890
+ props: { totalHeight: { input: "view.totalHeight" }, offsetTop: { input: "view.offsetTop" } },
4891
+ children: [
4892
+ {
4893
+ component: "chrome/Row",
4894
+ each: { input: "view.rows", key: "key" },
4895
+ props: {
4896
+ height: { item: "height" },
4897
+ selected: { item: "current" },
4898
+ focused: { item: "focused" },
4899
+ struck: { item: "future" },
4900
+ noIcon: { item: "hideIcon" },
4901
+ interactive: { item: "jumpable" },
4902
+ icon: { node: { component: "history/EntryIcon", props: { name: { item: "iconName" } } } },
4903
+ label: { item: "label" },
4904
+ meta: { item: "meta" },
4905
+ "data-history-row": { item: "key" },
4906
+ title: { item: "label" },
4907
+ trailing: {
4908
+ node: {
4909
+ component: "fragment",
4910
+ children: [
4911
+ { component: "history/PendingGlyph", when: { item: "pending" } },
4912
+ { component: "chrome/Badge", when: { item: "failed" }, props: { size: { value: "sm" }, tone: { value: "error" } }, children: [text3({ value: "failed" })] },
4913
+ { component: "chrome/Badge", when: { item: "showSource" }, props: { size: { value: "sm" }, title: { item: "sourceTitle" } }, children: [text3({ item: "sourceGlyph" })] },
4914
+ { component: "chrome/Badge", when: { item: "showCoalesced" }, props: { size: { value: "sm" }, title: { item: "coalescedTitle" } }, children: [text3({ item: "coalescedText" })] },
4915
+ { component: "chrome/Text", when: { item: "showTime" }, props: { role: { value: "meta" } }, children: [text3({ item: "time" })] },
4916
+ { component: "history/CancelButton", when: { item: "pending" }, props: { entryId: { item: "cancelId" } } }
4917
+ ]
4918
+ }
4919
+ }
4920
+ },
4921
+ on: {
4922
+ onClick: ui3("jump", { entryId: { item: "entryId" } }),
4923
+ onMouseEnter: ui3("hover", { entryId: { item: "entryId" } }),
4924
+ onContextMenuAt: ui3("contextMenu", { entryId: { item: "entryId" }, x: { arg: "" }, y: { arg: "", index: 1 } })
4925
+ }
4926
+ }
4927
+ ]
4928
+ }
4929
+ ]
4930
+ },
4931
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No history" } } }
4932
+ ]
4933
+ },
4934
+ {
4935
+ component: "chrome/StatusBar",
4936
+ when: { input: "view.hasMemory" },
4937
+ props: {
4938
+ left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text3({ input: "view.memoryText" })] } },
4939
+ right: { node: { component: "chrome/Text", when: { input: "view.memoryDisk" }, props: { role: { value: "meta" } }, children: [text3({ input: "view.memoryDisk" })] } }
4940
+ }
4941
+ }
4942
+ ]
4943
+ }
4944
+ };
4945
+
4946
+ // src/edit/history/react/HistoryPanelView.tsx
4542
4947
  import { Badge as Badge3, Column as Column3, EmptyState as EmptyState3, IconButton as IconButton4, InlineConfirm, RowList as RowList2, Row as Row2, ScrollArea as ScrollArea3, SearchField as SearchField3, StatusBar as StatusBar2, Text as Text4, Toolbar as Toolbar2, VirtualWindow as VirtualWindow2 } from "@xenosystem/components/chrome";
4543
4948
 
4544
4949
  // src/edit/history/react/icons.tsx
@@ -4640,12 +5045,34 @@ function HistoryPanelView({ controller }) {
4640
5045
  const subscribe = useCallback4((cb) => controller.subscribe(cb), [controller]);
4641
5046
  const getSnapshot = useCallback4(() => controller.getRevision(), [controller]);
4642
5047
  useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
5048
+ const view = historyViewState(controller);
5049
+ const ui4 = (intent) => applyHistoryUi(controller, intent);
5050
+ return /* @__PURE__ */ jsxs7(Column3, { className: "xeno-history-panel", children: [
5051
+ /* @__PURE__ */ jsx8(
5052
+ Toolbar2,
5053
+ {
5054
+ left: /* @__PURE__ */ jsxs7(Fragment4, { children: [
5055
+ /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(UndoIcon, {}), label: "Undo", disabled: view.cannotUndo, onClick: () => ui4({ kind: "undo" }) }),
5056
+ /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(RedoIcon, {}), label: "Redo", disabled: view.cannotRedo, onClick: () => ui4({ kind: "redo" }) })
5057
+ ] }),
5058
+ right: /* @__PURE__ */ jsxs7(Fragment4, { children: [
5059
+ /* @__PURE__ */ jsx8(Text4, { role: "number", children: view.counter }),
5060
+ view.clearAllowed ? /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(TrashIcon, {}), label: "Clear history", onClick: () => ui4({ kind: "requestClear" }) }) : null
5061
+ ] }),
5062
+ children: view.searchShown ? /* @__PURE__ */ jsx8(SearchField3, { value: view.filter, onChange: (v) => ui4({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
5063
+ }
5064
+ ),
5065
+ view.confirmingClear ? /* @__PURE__ */ jsx8(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui4({ kind: "confirmClear" }), onCancel: () => ui4({ kind: "cancelClear" }) }) : null,
5066
+ view.pruned ? /* @__PURE__ */ jsx8(Text4, { role: "caption", children: "Earlier history discarded (limit reached)" }) : null,
5067
+ /* @__PURE__ */ jsxs7(HistoryScroller, { controller, children: [
5068
+ /* @__PURE__ */ jsx8(RowList2, { role: "listbox", "aria-label": "History", children: /* @__PURE__ */ jsx8(VirtualWindow2, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row) => /* @__PURE__ */ jsx8(HistoryRowView, { row, controller }, row.key)) }) }),
5069
+ view.empty ? /* @__PURE__ */ jsx8(EmptyState3, { title: "No history" }) : null
5070
+ ] }),
5071
+ view.hasMemory ? /* @__PURE__ */ jsx8(StatusBar2, { left: /* @__PURE__ */ jsx8(Text4, { role: "number", children: view.memoryText }), right: view.memoryDisk ? /* @__PURE__ */ jsx8(Text4, { role: "meta", children: view.memoryDisk }) : null }) : null
5072
+ ] });
5073
+ }
5074
+ function HistoryScroller({ controller, children }) {
4643
5075
  const scrollRef = useRef4(null);
4644
- const rows = controller.getRows();
4645
- const window_ = controller.getWindow();
4646
- const windowRows = controller.getWindowRows();
4647
- const rowHeight = controller.getRowHeight();
4648
- const memory = controller.getMemory();
4649
5076
  useEffect3(() => {
4650
5077
  const el = scrollRef.current;
4651
5078
  if (!el) return;
@@ -4665,18 +5092,10 @@ function HistoryPanelView({ controller }) {
4665
5092
  el.scrollTop = controller.getScrollTop();
4666
5093
  }
4667
5094
  });
4668
- const onScroll = useCallback4(
4669
- (e) => controller.setScrollTop(e.currentTarget.scrollTop),
4670
- [controller]
4671
- );
5095
+ const onScroll = useCallback4((e) => controller.setScrollTop(e.currentTarget.scrollTop), [controller]);
4672
5096
  const onKeyDown = useCallback4(
4673
5097
  (e) => {
4674
- const handled = controller.handleKey({
4675
- key: e.key,
4676
- ctrl: e.ctrlKey,
4677
- meta: e.metaKey,
4678
- shift: e.shiftKey
4679
- });
5098
+ const handled = controller.handleKey({ key: e.key, ctrl: e.ctrlKey, meta: e.metaKey, shift: e.shiftKey });
4680
5099
  if (handled) {
4681
5100
  e.preventDefault();
4682
5101
  e.stopPropagation();
@@ -4684,109 +5103,62 @@ function HistoryPanelView({ controller }) {
4684
5103
  },
4685
5104
  [controller]
4686
5105
  );
4687
- return /* @__PURE__ */ jsxs7(Column3, { className: "xeno-history-panel", children: [
4688
- /* @__PURE__ */ jsx8(
4689
- Toolbar2,
4690
- {
4691
- left: /* @__PURE__ */ jsxs7(Fragment4, { children: [
4692
- /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(UndoIcon, {}), label: "Undo", disabled: !controller.canUndo(), onClick: () => controller.undo() }),
4693
- /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(RedoIcon, {}), label: "Redo", disabled: !controller.canRedo(), onClick: () => controller.redo() })
4694
- ] }),
4695
- right: /* @__PURE__ */ jsxs7(Fragment4, { children: [
4696
- /* @__PURE__ */ jsxs7(Text4, { role: "number", children: [
4697
- controller.getAppliedCount(),
4698
- " / ",
4699
- controller.getTotalCount()
4700
- ] }),
4701
- controller.isClearAllowed() ? /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(TrashIcon, {}), label: "Clear history", onClick: () => controller.requestClear() }) : null
4702
- ] }),
4703
- children: controller.isSearchShown() ? /* @__PURE__ */ jsx8(SearchField3, { value: controller.getFilter(), onChange: (v) => controller.setFilter(v), placeholder: "Search history", label: "Search history" }) : null
4704
- }
4705
- ),
4706
- controller.isConfirmingClear() ? /* @__PURE__ */ jsx8(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => controller.confirmClearNow(), onCancel: () => controller.cancelClear() }) : null,
4707
- controller.isPruned() ? /* @__PURE__ */ jsx8(Text4, { role: "caption", children: "Earlier history discarded (limit reached)" }) : null,
4708
- /* @__PURE__ */ jsxs7(ScrollArea3, { ref: scrollRef, onScroll, onKeyDown, onMouseLeave: () => controller.setHovered(null), tabIndex: 0, children: [
4709
- /* @__PURE__ */ jsx8(RowList2, { role: "listbox", "aria-label": "History", children: /* @__PURE__ */ jsx8(VirtualWindow2, { totalHeight: window_.totalHeight, offsetTop: window_.offsetTop, children: windowRows.map((row) => /* @__PURE__ */ jsx8(
4710
- HistoryRowView,
4711
- {
4712
- row,
4713
- controller,
4714
- rowHeight,
4715
- focused: controller.getFocused() === row.entryId
4716
- },
4717
- row.entryId ?? "__original__"
4718
- )) }) }),
4719
- rows.length === 0 ? /* @__PURE__ */ jsx8(EmptyState3, { title: "No history" }) : null
4720
- ] }),
4721
- memory ? /* @__PURE__ */ jsx8(
4722
- StatusBar2,
4723
- {
4724
- left: /* @__PURE__ */ jsxs7(Text4, { role: "number", children: [
4725
- memory.usedMB.toFixed(1),
4726
- " MB",
4727
- memory.limitMB ? ` / ${memory.limitMB} MB` : ""
4728
- ] }),
4729
- right: memory.onDiskCount !== void 0 ? /* @__PURE__ */ jsxs7(Text4, { role: "meta", children: [
4730
- memory.onDiskCount,
4731
- " on disk"
4732
- ] }) : null
5106
+ return /* @__PURE__ */ jsx8(ScrollArea3, { ref: scrollRef, onScroll, onKeyDown, onMouseLeave: () => controller.setHovered(null), tabIndex: 0, children });
5107
+ }
5108
+ function HistoryCancelButton({ entryId, controller }) {
5109
+ return /* @__PURE__ */ jsx8(
5110
+ IconButton4,
5111
+ {
5112
+ size: "sm",
5113
+ icon: /* @__PURE__ */ jsx8(XIcon2, {}),
5114
+ label: "Cancel operation",
5115
+ onClick: (e) => {
5116
+ e.stopPropagation();
5117
+ controller.cancelPending(entryId);
4733
5118
  }
4734
- ) : null
4735
- ] });
5119
+ }
5120
+ );
4736
5121
  }
4737
- function HistoryRowView({ row, controller, rowHeight, focused }) {
4738
- const entry = row.entry;
4739
- const pending = entry?.status === "pending";
4740
- const failed = entry?.status === "failed";
4741
- const jumpable = controller.isJumpAllowed() && entry?.caps?.jumpable !== false;
5122
+ function HistoryRowView({ row, controller }) {
5123
+ const ui4 = (intent) => applyHistoryUi(controller, intent);
4742
5124
  return /* @__PURE__ */ jsx8(
4743
5125
  Row2,
4744
5126
  {
4745
- height: rowHeight,
5127
+ height: row.height,
4746
5128
  selected: row.current,
4747
- focused,
5129
+ focused: row.focused,
4748
5130
  struck: row.future,
4749
- noIcon: !controller.areIconsShown(),
4750
- icon: /* @__PURE__ */ jsx8(EntryIcon, { name: row.kind === "original" ? "original" : controller.getIconName(entry?.kind) }),
5131
+ noIcon: row.hideIcon,
5132
+ interactive: row.jumpable,
5133
+ icon: /* @__PURE__ */ jsx8(EntryIcon, { name: row.iconName }),
4751
5134
  label: row.label,
4752
- meta: row.kind === "entry" ? row.ordinal : void 0,
4753
- "data-history-row": row.entryId ?? "__original__",
4754
- onClick: jumpable ? () => controller.jumpTo(row.entryId) : void 0,
4755
- onMouseEnter: () => controller.setHovered(row.entryId),
4756
- onContextMenu: (e) => {
4757
- e.preventDefault();
4758
- controller.requestContextMenu(row.entryId, e.clientX, e.clientY);
4759
- },
5135
+ meta: row.meta,
5136
+ "data-history-row": row.key,
4760
5137
  title: row.label,
4761
5138
  trailing: /* @__PURE__ */ jsxs7(Fragment4, { children: [
4762
- pending ? /* @__PURE__ */ jsx8(PendingGlyph, {}) : null,
4763
- failed ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", tone: "error", children: "failed" }) : null,
4764
- controller.isSourceShown() && entry?.source && entry.source !== "user" ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: `Source: ${entry.source}`, children: entry.source === "agent" ? "AI" : "S" }) : null,
4765
- entry?.coalesced && entry.coalesced > 1 ? /* @__PURE__ */ jsxs7(Badge3, { size: "sm", title: `${entry.coalesced} actions coalesced`, children: [
4766
- "\xD7",
4767
- entry.coalesced
4768
- ] }) : null,
4769
- controller.areTimestampsShown() && entry?.at !== void 0 ? /* @__PURE__ */ jsx8(Text4, { role: "meta", children: formatTime(entry.at) }) : null,
4770
- pending ? /* @__PURE__ */ jsx8(
4771
- IconButton4,
4772
- {
4773
- size: "sm",
4774
- icon: /* @__PURE__ */ jsx8(XIcon2, {}),
4775
- label: "Cancel operation",
4776
- onClick: (e) => {
4777
- e.stopPropagation();
4778
- controller.cancelPending(entry?.id);
4779
- }
4780
- }
4781
- ) : null
4782
- ] })
5139
+ row.pending ? /* @__PURE__ */ jsx8(PendingGlyph, {}) : null,
5140
+ row.failed ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", tone: "error", children: "failed" }) : null,
5141
+ row.showSource ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: row.sourceTitle, children: row.sourceGlyph }) : null,
5142
+ row.showCoalesced ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: row.coalescedTitle, children: row.coalescedText }) : null,
5143
+ row.showTime ? /* @__PURE__ */ jsx8(Text4, { role: "meta", children: row.time }) : null,
5144
+ row.pending ? /* @__PURE__ */ jsx8(HistoryCancelButton, { entryId: row.cancelId, controller }) : null
5145
+ ] }),
5146
+ onClick: () => ui4({ kind: "jump", entryId: row.entryId }),
5147
+ onMouseEnter: () => ui4({ kind: "hover", entryId: row.entryId }),
5148
+ onContextMenuAt: (x, y) => ui4({ kind: "contextMenu", entryId: row.entryId, x, y })
4783
5149
  }
4784
5150
  );
4785
5151
  }
4786
- function formatTime(at) {
4787
- const date = typeof at === "number" ? new Date(at) : new Date(at);
4788
- if (Number.isNaN(date.getTime())) return typeof at === "string" ? at : "";
4789
- return date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
5152
+ function historyRuntimeComponents(controller) {
5153
+ return {
5154
+ "history/Scroller": ({ children }) => /* @__PURE__ */ jsx8(HistoryScroller, { controller, children }),
5155
+ "history/CancelButton": ({ entryId }) => /* @__PURE__ */ jsx8(HistoryCancelButton, { entryId, controller }),
5156
+ "history/EntryIcon": EntryIcon,
5157
+ "history/PendingGlyph": PendingGlyph,
5158
+ "history/UndoIcon": UndoIcon,
5159
+ "history/RedoIcon": RedoIcon,
5160
+ "history/TrashIcon": TrashIcon
5161
+ };
4790
5162
  }
4791
5163
 
4792
5164
  // src/edit/history/panel.ts
@@ -4794,11 +5166,20 @@ function createHistoryPanel(options = {}) {
4794
5166
  return {
4795
5167
  manifest: historyManifest,
4796
5168
  activate(host) {
4797
- const controller = new HistoryPanelController(host, options);
5169
+ const { render: customRender, ...controllerOptions } = options;
5170
+ const controller = new HistoryPanelController(host, controllerOptions);
4798
5171
  void controller.load();
4799
5172
  let root = null;
5173
+ let unrender = null;
4800
5174
  return {
4801
5175
  render(el) {
5176
+ if (customRender) {
5177
+ unrender = customRender(el, controller);
5178
+ return () => {
5179
+ unrender?.();
5180
+ unrender = null;
5181
+ };
5182
+ }
4802
5183
  root = createRoot3(el);
4803
5184
  root.render(createElement3(HistoryPanelView, { controller }));
4804
5185
  return () => {
@@ -4821,6 +5202,8 @@ function createHistoryPanel(options = {}) {
4821
5202
  dispose() {
4822
5203
  root?.unmount();
4823
5204
  root = null;
5205
+ unrender?.();
5206
+ unrender = null;
4824
5207
  controller.dispose();
4825
5208
  }
4826
5209
  };
@@ -8598,11 +8981,11 @@ var FindController = class {
8598
8981
  * @param text - The new query text.
8599
8982
  * @param immediate - Skip the debounce (a paste, or pressing Enter).
8600
8983
  */
8601
- setText(text2, immediate = false) {
8602
- if (text2 === this.text) return;
8603
- this.text = text2;
8984
+ setText(text4, immediate = false) {
8985
+ if (text4 === this.text) return;
8986
+ this.text = text4;
8604
8987
  this.index = -1;
8605
- if (text2 === "") {
8988
+ if (text4 === "") {
8606
8989
  this.clearResults("idle");
8607
8990
  this.emitQuery(true);
8608
8991
  return;
@@ -8630,8 +9013,8 @@ var FindController = class {
8630
9013
  this.notify();
8631
9014
  }
8632
9015
  /** The replacement text. Never emitted on its own — a replacement is not a search. */
8633
- setReplaceText(text2) {
8634
- this.replaceText = text2;
9016
+ setReplaceText(text4) {
9017
+ this.replaceText = text4;
8635
9018
  this.notify();
8636
9019
  }
8637
9020
  /** Show or hide the replace row. */
@@ -12267,12 +12650,12 @@ var TreeController = class {
12267
12650
  }
12268
12651
  if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
12269
12652
  const fresh = now - this.typeAhead.at > this.typeAheadMs;
12270
- const text2 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12271
- this.typeAhead = { text: text2, at: now };
12653
+ const text4 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12654
+ this.typeAhead = { text: text4, at: now };
12272
12655
  const from = fresh ? current : current + 1;
12273
12656
  for (let i = 0; i < rows.length; i += 1) {
12274
12657
  const candidate = rows[(from + i) % rows.length];
12275
- if (candidate.node.label.toLowerCase().startsWith(text2)) {
12658
+ if (candidate.node.label.toLowerCase().startsWith(text4)) {
12276
12659
  this.focusedId = candidate.id;
12277
12660
  this.select([candidate.id]);
12278
12661
  return true;
@@ -12298,9 +12681,9 @@ var TreeController = class {
12298
12681
  return true;
12299
12682
  }
12300
12683
  /** Update the rename draft. */
12301
- updateRenameDraft(text2) {
12684
+ updateRenameDraft(text4) {
12302
12685
  if (!this.renaming) return;
12303
- this.renaming = { ...this.renaming, draft: text2 };
12686
+ this.renaming = { ...this.renaming, draft: text4 };
12304
12687
  this.notify();
12305
12688
  }
12306
12689
  /**
@@ -13489,13 +13872,17 @@ export {
13489
13872
  FindPanelView,
13490
13873
  GENERIC_ICON,
13491
13874
  GraphController,
13875
+ BEHAVIOURS3 as HISTORY_BEHAVIOURS,
13876
+ HISTORY_DECLARATION,
13492
13877
  HistoryPanelController,
13493
13878
  HistoryPanelView,
13494
13879
  IMPLEMENTED_TREE_MODES,
13495
- BEHAVIOURS as INSPECTOR_BEHAVIOURS,
13880
+ BEHAVIOURS2 as INSPECTOR_BEHAVIOURS,
13496
13881
  INSPECTOR_DECLARATION,
13497
13882
  InspectorPanelController,
13498
13883
  InspectorPanelView,
13884
+ BEHAVIOURS as LAYERS_BEHAVIOURS,
13885
+ LAYERS_DECLARATION,
13499
13886
  LAYER_COLUMNS,
13500
13887
  LayersPanelController,
13501
13888
  LayersPanelView,
@@ -13519,7 +13906,9 @@ export {
13519
13906
  allIds,
13520
13907
  anchorStateOf,
13521
13908
  appliedCount,
13909
+ applyHistoryUi,
13522
13910
  applyInspectorUi,
13911
+ applyLayersUi,
13523
13912
  applyPrecision,
13524
13913
  applyTangentDrag,
13525
13914
  ariaSliderProps,
@@ -13588,6 +13977,9 @@ export {
13588
13977
  highlightSegments,
13589
13978
  historyManifest,
13590
13979
  historyPanel,
13980
+ historyRuntimeComponents,
13981
+ historyViewManifest,
13982
+ historyViewState,
13591
13983
  hslToRgb,
13592
13984
  hsvFromHex,
13593
13985
  hsvToRgb,
@@ -13616,6 +14008,9 @@ export {
13616
14008
  keyRefId,
13617
14009
  layersManifest,
13618
14010
  layersPanel,
14011
+ layersRuntimeComponents,
14012
+ layersViewManifest,
14013
+ layersViewState,
13619
14014
  liveEdges,
13620
14015
  makeColorValue,
13621
14016
  matchesFilter,