@xenosystem/blocks 0.12.4 → 0.12.6

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;
@@ -882,15 +882,15 @@ var LayersPanelController = class {
882
882
  requestVisibleThumbnails() {
883
883
  if (!this.thumbnailsEnabled) return;
884
884
  if (!this.getColumns().includes("thumbnail")) return;
885
- for (const row of this.getWindowRows()) {
886
- const key = row.node.thumbnailKey;
885
+ for (const row2 of this.getWindowRows()) {
886
+ const key = row2.node.thumbnailKey;
887
887
  if (!key || this.thumbnails.has(key) || this.requestedThumbs.has(key)) continue;
888
888
  this.requestedThumbs.add(key);
889
889
  const request = {
890
890
  key,
891
891
  width: this.thumbnailSize,
892
892
  height: this.thumbnailSize,
893
- nodeId: row.id
893
+ nodeId: row2.id
894
894
  };
895
895
  this.host.emit("thumbnailRequest", request);
896
896
  }
@@ -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((row2) => {
1220
+ const node = row2.node;
1221
+ const container = isContainer(node);
1222
+ return {
1223
+ id: row2.id,
1224
+ name: node.name,
1225
+ title: node.name,
1226
+ depth: row2.depth,
1227
+ height: rowHeight,
1228
+ selected: c.isSelected(row2.id),
1229
+ active: activeId === row2.id,
1230
+ dimmed: !row2.matched,
1231
+ dropHint: drag?.target && drag.target.targetId === row2.id ? drag.target.position : null,
1232
+ expanded: container ? row2.expanded : void 0,
1233
+ renaming: renamingId === row2.id,
1234
+ notRenaming: renamingId !== row2.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 ui5 = (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) => ui5({ 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((row2) => /* @__PURE__ */ jsx3(LayerRow, { row: row2, thumbnailSize: view.thumbnailSize, controller }, row2.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: row2, thumbnailSize, controller }) {
1631
+ const ui5 = (intent) => applyLayersUi(controller, intent);
1450
1632
  return /* @__PURE__ */ jsx3(
1451
1633
  Row,
1452
1634
  {
1453
- "data-layer-row": row.id,
1454
- height: rowHeight,
1455
- 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, {}),
1635
+ "data-layer-row": row2.id,
1636
+ height: row2.height,
1637
+ depth: row2.depth,
1638
+ selected: row2.selected,
1639
+ active: row2.active,
1640
+ dimmed: row2.dimmed,
1641
+ dropHint: row2.dropHint,
1642
+ expanded: row2.expanded,
1643
+ title: row2.title,
1644
+ icon: row2.hasThumbnail ? /* @__PURE__ */ jsx3(LayersThumbnail, { thumbnailKey: row2.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
+ row2.showVisibility ? /* @__PURE__ */ jsx3(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx3(VisibilityIcon, { open: row2.visible }), label: row2.visibilityLabel, active: row2.visible, onClick: () => ui5({ kind: "toggleProp", id: row2.id, prop: "visible" }) }) : null,
1647
+ row2.showLock ? /* @__PURE__ */ jsx3(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx3(LockStateIcon, { locked: row2.locked }), label: row2.lockLabel, active: row2.locked, onClick: () => ui5({ kind: "toggleProp", id: row2.id, prop: "locked" }) }) : null,
1648
+ row2.showSolo ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "S", label: "Solo", on: row2.solo, onChange: () => ui5({ kind: "toggleProp", id: row2.id, prop: "solo" }) }) : null,
1649
+ row2.showMute ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "M", label: "Mute", on: row2.muted, onChange: () => ui5({ kind: "toggleProp", id: row2.id, prop: "muted" }) }) : null,
1650
+ row2.showArm ? /* @__PURE__ */ jsx3(ChipToggle, { glyph: "R", label: "Arm", on: row2.armed, onChange: () => ui5({ kind: "toggleProp", id: row2.id, prop: "armed" }) }) : null,
1651
+ row2.showColor ? /* @__PURE__ */ jsx3(ColorTag, { color: row2.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: row2.renaming ? /* @__PURE__ */ jsx3(LayersRenameField, { id: row2.id, controller }) : row2.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
+ row2.adornments.map((a) => /* @__PURE__ */ jsx3(Badge, { size: "sm", title: a.title, children: a.text }, a.key)),
1656
+ row2.showOpacity ? /* @__PURE__ */ jsx3(Text, { role: "number", children: row2.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: () => ui5({ kind: "toggleExpanded", id: row2.id }),
1659
+ onDoubleClick: () => ui5({ kind: "beginRename", id: row2.id }),
1660
+ onContextMenuAt: (x, y) => ui5({ kind: "contextMenu", id: row2.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(text3) {
1875
- const trimmed = String(text3).trim();
2054
+ function parseNumeric(text5) {
2055
+ const trimmed = String(text5).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 text3 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
1901
- return field.unit ? `${text3}${field.unit}` : text3;
2080
+ const text5 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
2081
+ return field.unit ? `${text5}${field.unit}` : text5;
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 text3 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2236
- this.drafts.set(fieldId, { text: text3, original: value });
2415
+ const text5 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2416
+ this.drafts.set(fieldId, { text: text5, 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, text3) {
2422
+ setDraft(fieldId, text5) {
2243
2423
  const draft = this.drafts.get(fieldId);
2244
2424
  if (!draft) {
2245
- this.drafts.set(fieldId, { text: text3, original: this.getFieldValue(fieldId) });
2425
+ this.drafts.set(fieldId, { text: text5, original: this.getFieldValue(fieldId) });
2246
2426
  } else {
2247
- draft.text = text3;
2427
+ draft.text = text5;
2248
2428
  }
2249
2429
  const field = this.getField(fieldId);
2250
2430
  if (field && commitPolicyOf(field) === "live") {
2251
- const parsed = this.parseDraft(fieldId, text3);
2431
+ const parsed = this.parseDraft(fieldId, text5);
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, text3) {
2437
+ parseDraft(fieldId, text5) {
2258
2438
  const field = this.getField(fieldId);
2259
2439
  if (!field) return { ok: false };
2260
2440
  if (field.type === "number") {
2261
- const parsed = parseNumeric(text3);
2441
+ const parsed = parseNumeric(text5);
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(text3) };
2447
+ return { ok: true, value: JSON.parse(text5) };
2268
2448
  } catch {
2269
2449
  return { ok: false };
2270
2450
  }
2271
2451
  }
2272
- return { ok: true, value: text3 };
2452
+ return { ok: true, value: text5 };
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 text3 = component !== void 0 ? (() => {
3427
+ const text5 = 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: text3,
3439
+ value: text5,
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 ui3 = (intent) => applyInspectorUi(controller, intent);
3657
+ const ui5 = (intent) => applyInspectorUi(controller, intent);
3478
3658
  const hasActions = section.actions.length > 0 || section.hasAddMenu;
3479
3659
  return /* @__PURE__ */ jsxs5(
3480
3660
  Section,
@@ -3483,51 +3663,51 @@ function SectionView({ section, controller }) {
3483
3663
  flush: true,
3484
3664
  collapsible: section.collapsible,
3485
3665
  collapsed: section.collapsed,
3486
- onCollapsedChange: () => ui3({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3666
+ onCollapsedChange: () => ui5({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3487
3667
  enabled: section.enabled,
3488
- onEnabledChange: () => ui3({ kind: "toggleSectionEnabled", sectionId: section.id }),
3668
+ onEnabledChange: () => ui5({ 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: () => ui3({ 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) => ui3({ 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: () => ui5({ 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) => ui5({ 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,
3496
- /* @__PURE__ */ jsx6(FieldGroup, { labelWidth: section.labelWidth, children: section.fields.map((row) => /* @__PURE__ */ jsx6(FieldRow, { row, controller }, row.id)) })
3676
+ /* @__PURE__ */ jsx6(FieldGroup, { labelWidth: section.labelWidth, children: section.fields.map((row2) => /* @__PURE__ */ jsx6(FieldRow, { row: row2, controller }, row2.id)) })
3497
3677
  ]
3498
3678
  }
3499
3679
  );
3500
3680
  }
3501
- function FieldRow({ row, controller }) {
3502
- const ui3 = (intent) => applyInspectorUi(controller, intent);
3681
+ function FieldRow({ row: row2, controller }) {
3682
+ const ui5 = (intent) => applyInspectorUi(controller, intent);
3503
3683
  return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3504
3684
  /* @__PURE__ */ jsxs5(
3505
3685
  Field,
3506
3686
  {
3507
- label: row.label,
3508
- title: row.title,
3509
- focused: row.focused,
3510
- onLabelClick: () => ui3({ kind: "focusField", fieldId: row.id }),
3511
- onContextMenu: (x, y) => ui3({ kind: "contextMenu", fieldId: row.id, x, y }),
3512
- leading: row.showKeyframes ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3513
- /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(StopwatchIcon, {}), label: "Toggle animation", active: row.animated, onClick: () => ui3({ 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: () => ui3({ 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: () => ui3({ 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: () => ui3({ kind: "keyframe", fieldId: row.id, op: "next" }) }) : null
3687
+ label: row2.label,
3688
+ title: row2.title,
3689
+ focused: row2.focused,
3690
+ onLabelClick: () => ui5({ kind: "focusField", fieldId: row2.id }),
3691
+ onContextMenu: (x, y) => ui5({ kind: "contextMenu", fieldId: row2.id, x, y }),
3692
+ leading: row2.showKeyframes ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
3693
+ /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(StopwatchIcon, {}), label: "Toggle animation", active: row2.animated, onClick: () => ui5({ kind: "keyframe", fieldId: row2.id, op: "toggleAnim" }) }),
3694
+ row2.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(PrevIcon, {}), label: "Previous keyframe", disabled: row2.prevDisabled, onClick: () => ui5({ kind: "keyframe", fieldId: row2.id, op: "prev" }) }) : null,
3695
+ row2.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(DiamondIcon, { filled: row2.onKeyframe }), label: row2.keyframeLabel, onClick: () => ui5({ kind: "keyframe", fieldId: row2.id, op: row2.keyframeOp }) }) : null,
3696
+ row2.animated ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(NextIcon, {}), label: "Next keyframe", disabled: row2.nextDisabled, onClick: () => ui5({ kind: "keyframe", fieldId: row2.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) => ui3({ kind: "setEasing", fieldId: row.id, easing: v }) }) : null,
3520
- 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: () => ui3({ kind: "bindToken", fieldId: row.id }) }) : null,
3522
- row.showReset ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(ResetIcon, {}), label: "Reset to default", onClick: () => ui3({ kind: "resetField", fieldId: row.id }) }) : null
3699
+ row2.showEasing ? /* @__PURE__ */ jsx6(Select2, { size: "sm", options: row2.easingOptions, value: row2.easing, label: "Easing", onChange: (v) => ui5({ kind: "setEasing", fieldId: row2.id, easing: v }) }) : null,
3700
+ row2.showKeyframes ? /* @__PURE__ */ jsx6(Badge2, { size: "sm", children: row2.keyframeChip }) : null,
3701
+ row2.showToken ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(LinkIcon, {}), label: row2.tokenActionLabel, active: row2.bound, onClick: () => ui5({ kind: "bindToken", fieldId: row2.id }) }) : null,
3702
+ row2.showReset ? /* @__PURE__ */ jsx6(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx6(ResetIcon, {}), label: "Reset to default", onClick: () => ui5({ kind: "resetField", fieldId: row2.id }) }) : null
3523
3703
  ] }),
3524
3704
  children: [
3525
- row.bound ? /* @__PURE__ */ jsx6(Badge2, { title: row.tokenTitle, children: row.tokenLabel }) : null,
3526
- row.unbound ? /* @__PURE__ */ jsx6(FieldWidget, { field: row.field, controller }) : null
3705
+ row2.bound ? /* @__PURE__ */ jsx6(Badge2, { title: row2.tokenTitle, children: row2.tokenLabel }) : null,
3706
+ row2.unbound ? /* @__PURE__ */ jsx6(FieldWidget, { field: row2.field, controller }) : null
3527
3707
  ]
3528
3708
  }
3529
3709
  ),
3530
- row.showInfoNote ? /* @__PURE__ */ jsx6(Stack2, { pad: "inset", children: /* @__PURE__ */ jsx6(Text3, { role: "caption", children: row.infoNote }) }) : null
3710
+ row2.showInfoNote ? /* @__PURE__ */ jsx6(Stack2, { pad: "inset", children: /* @__PURE__ */ jsx6(Text3, { role: "caption", children: row2.infoNote }) }) : null
3531
3711
  ] });
3532
3712
  }
3533
3713
  function FieldWidget({
@@ -4318,9 +4498,9 @@ var HistoryPanelController = class {
4318
4498
  case "ArrowUp": {
4319
4499
  const delta = event.key === "ArrowDown" ? 1 : -1;
4320
4500
  const next = focusIndex < 0 ? 0 : Math.min(rows.length - 1, Math.max(0, focusIndex + delta));
4321
- const row = rows[next];
4322
- if (row) {
4323
- this.focusedEntryId = row.entryId;
4501
+ const row2 = rows[next];
4502
+ if (row2) {
4503
+ this.focusedEntryId = row2.entryId;
4324
4504
  this.scrollIndexIntoView(next, rows.length);
4325
4505
  }
4326
4506
  this.notify();
@@ -4329,9 +4509,9 @@ var HistoryPanelController = class {
4329
4509
  case "Home":
4330
4510
  case "End": {
4331
4511
  const index = event.key === "Home" ? 0 : rows.length - 1;
4332
- const row = rows[index];
4333
- if (row) {
4334
- this.focusedEntryId = row.entryId;
4512
+ const row2 = rows[index];
4513
+ if (row2) {
4514
+ this.focusedEntryId = row2.entryId;
4335
4515
  this.scrollIndexIntoView(index, rows.length);
4336
4516
  }
4337
4517
  this.notify();
@@ -4571,22 +4751,22 @@ function historyViewState(c) {
4571
4751
  pruned: c.isPruned(),
4572
4752
  totalHeight: window_.totalHeight,
4573
4753
  offsetTop: window_.offsetTop,
4574
- rows: c.getWindowRows().map((row) => {
4575
- const entry = row.entry;
4754
+ rows: c.getWindowRows().map((row2) => {
4755
+ const entry = row2.entry;
4576
4756
  const pending = entry?.status === "pending";
4577
4757
  const source = entry?.source;
4578
4758
  return {
4579
- key: row.entryId ?? "__original__",
4580
- entryId: row.entryId,
4581
- label: row.label,
4582
- meta: row.kind === "entry" ? row.ordinal : void 0,
4583
- current: row.current,
4584
- future: row.future,
4585
- focused: focused === row.entryId,
4759
+ key: row2.entryId ?? "__original__",
4760
+ entryId: row2.entryId,
4761
+ label: row2.label,
4762
+ meta: row2.kind === "entry" ? row2.ordinal : void 0,
4763
+ current: row2.current,
4764
+ future: row2.future,
4765
+ focused: focused === row2.entryId,
4586
4766
  jumpable: jumpAllowed && entry?.caps?.jumpable !== false,
4587
4767
  showIcon: showIcons,
4588
4768
  hideIcon: !showIcons,
4589
- iconName: row.kind === "original" ? "original" : c.getIconName(entry?.kind),
4769
+ iconName: row2.kind === "original" ? "original" : c.getIconName(entry?.kind),
4590
4770
  pending,
4591
4771
  failed: entry?.status === "failed",
4592
4772
  showSource: showSource && !!source && source !== "user",
@@ -4651,12 +4831,12 @@ var historyViewManifest = {
4651
4831
  outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, entryId?, value?, x?, y? } \u2014 applied by the runtime to the controller." }],
4652
4832
  commands: []
4653
4833
  };
4654
- var EMPTY2 = { 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: "" };
4655
- var BEHAVIOURS2 = {
4656
- "edit/historyPanelView": (inputs) => inputs.view ?? EMPTY2
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
4657
4837
  };
4658
- var text2 = (b) => ({ component: "text", props: { text: b } });
4659
- var ui2 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4838
+ var text3 = (b) => ({ component: "text", props: { text: b } });
4839
+ var ui3 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
4660
4840
  var HISTORY_DECLARATION = {
4661
4841
  format: "xeno.block@1",
4662
4842
  manifest: historyViewManifest,
@@ -4672,8 +4852,8 @@ var HISTORY_DECLARATION = {
4672
4852
  node: {
4673
4853
  component: "fragment",
4674
4854
  children: [
4675
- { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "history/UndoIcon" } }, label: { value: "Undo" }, disabled: { input: "view.cannotUndo" } }, on: { onClick: ui2("undo") } },
4676
- { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "history/RedoIcon" } }, label: { value: "Redo" }, disabled: { input: "view.cannotRedo" } }, on: { onClick: ui2("redo") } }
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") } }
4677
4857
  ]
4678
4858
  }
4679
4859
  },
@@ -4681,23 +4861,23 @@ var HISTORY_DECLARATION = {
4681
4861
  node: {
4682
4862
  component: "fragment",
4683
4863
  children: [
4684
- { component: "chrome/Text", props: { role: { value: "number" } }, children: [text2({ input: "view.counter" })] },
4685
- { component: "chrome/IconButton", when: { input: "view.clearAllowed" }, props: { size: { value: "sm" }, icon: { node: { component: "history/TrashIcon" } }, label: { value: "Clear history" } }, on: { onClick: ui2("requestClear") } }
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") } }
4686
4866
  ]
4687
4867
  }
4688
4868
  }
4689
4869
  },
4690
4870
  children: [
4691
- { component: "chrome/SearchField", when: { input: "view.searchShown" }, props: { value: { input: "view.filter" }, placeholder: { value: "Search history" }, label: { value: "Search history" } }, on: { onChange: ui2("filter", { value: { arg: "" } }) } }
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: "" } }) } }
4692
4872
  ]
4693
4873
  },
4694
4874
  {
4695
4875
  component: "chrome/InlineConfirm",
4696
4876
  when: { input: "view.confirmingClear" },
4697
4877
  props: { message: { value: "Clear all history?" }, label: { value: "Confirm clear history" }, confirmLabel: { value: "Clear" } },
4698
- on: { onConfirm: ui2("confirmClear"), onCancel: ui2("cancelClear") }
4878
+ on: { onConfirm: ui3("confirmClear"), onCancel: ui3("cancelClear") }
4699
4879
  },
4700
- { component: "chrome/Text", when: { input: "view.pruned" }, props: { role: { value: "caption" } }, children: [text2({ value: "Earlier history discarded (limit reached)" })] },
4880
+ { component: "chrome/Text", when: { input: "view.pruned" }, props: { role: { value: "caption" } }, children: [text3({ value: "Earlier history discarded (limit reached)" })] },
4701
4881
  {
4702
4882
  component: "history/Scroller",
4703
4883
  children: [
@@ -4729,19 +4909,19 @@ var HISTORY_DECLARATION = {
4729
4909
  component: "fragment",
4730
4910
  children: [
4731
4911
  { component: "history/PendingGlyph", when: { item: "pending" } },
4732
- { component: "chrome/Badge", when: { item: "failed" }, props: { size: { value: "sm" }, tone: { value: "error" } }, children: [text2({ value: "failed" })] },
4733
- { component: "chrome/Badge", when: { item: "showSource" }, props: { size: { value: "sm" }, title: { item: "sourceTitle" } }, children: [text2({ item: "sourceGlyph" })] },
4734
- { component: "chrome/Badge", when: { item: "showCoalesced" }, props: { size: { value: "sm" }, title: { item: "coalescedTitle" } }, children: [text2({ item: "coalescedText" })] },
4735
- { component: "chrome/Text", when: { item: "showTime" }, props: { role: { value: "meta" } }, children: [text2({ item: "time" })] },
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" })] },
4736
4916
  { component: "history/CancelButton", when: { item: "pending" }, props: { entryId: { item: "cancelId" } } }
4737
4917
  ]
4738
4918
  }
4739
4919
  }
4740
4920
  },
4741
4921
  on: {
4742
- onClick: ui2("jump", { entryId: { item: "entryId" } }),
4743
- onMouseEnter: ui2("hover", { entryId: { item: "entryId" } }),
4744
- onContextMenuAt: ui2("contextMenu", { entryId: { item: "entryId" }, x: { arg: "" }, y: { arg: "", index: 1 } })
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 } })
4745
4925
  }
4746
4926
  }
4747
4927
  ]
@@ -4755,8 +4935,8 @@ var HISTORY_DECLARATION = {
4755
4935
  component: "chrome/StatusBar",
4756
4936
  when: { input: "view.hasMemory" },
4757
4937
  props: {
4758
- left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text2({ input: "view.memoryText" })] } },
4759
- right: { node: { component: "chrome/Text", when: { input: "view.memoryDisk" }, props: { role: { value: "meta" } }, children: [text2({ input: "view.memoryDisk" })] } }
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" })] } }
4760
4940
  }
4761
4941
  }
4762
4942
  ]
@@ -4866,26 +5046,26 @@ function HistoryPanelView({ controller }) {
4866
5046
  const getSnapshot = useCallback4(() => controller.getRevision(), [controller]);
4867
5047
  useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
4868
5048
  const view = historyViewState(controller);
4869
- const ui3 = (intent) => applyHistoryUi(controller, intent);
5049
+ const ui5 = (intent) => applyHistoryUi(controller, intent);
4870
5050
  return /* @__PURE__ */ jsxs7(Column3, { className: "xeno-history-panel", children: [
4871
5051
  /* @__PURE__ */ jsx8(
4872
5052
  Toolbar2,
4873
5053
  {
4874
5054
  left: /* @__PURE__ */ jsxs7(Fragment4, { children: [
4875
- /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(UndoIcon, {}), label: "Undo", disabled: view.cannotUndo, onClick: () => ui3({ kind: "undo" }) }),
4876
- /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(RedoIcon, {}), label: "Redo", disabled: view.cannotRedo, onClick: () => ui3({ kind: "redo" }) })
5055
+ /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(UndoIcon, {}), label: "Undo", disabled: view.cannotUndo, onClick: () => ui5({ kind: "undo" }) }),
5056
+ /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(RedoIcon, {}), label: "Redo", disabled: view.cannotRedo, onClick: () => ui5({ kind: "redo" }) })
4877
5057
  ] }),
4878
5058
  right: /* @__PURE__ */ jsxs7(Fragment4, { children: [
4879
5059
  /* @__PURE__ */ jsx8(Text4, { role: "number", children: view.counter }),
4880
- view.clearAllowed ? /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(TrashIcon, {}), label: "Clear history", onClick: () => ui3({ kind: "requestClear" }) }) : null
5060
+ view.clearAllowed ? /* @__PURE__ */ jsx8(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx8(TrashIcon, {}), label: "Clear history", onClick: () => ui5({ kind: "requestClear" }) }) : null
4881
5061
  ] }),
4882
- children: view.searchShown ? /* @__PURE__ */ jsx8(SearchField3, { value: view.filter, onChange: (v) => ui3({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
5062
+ children: view.searchShown ? /* @__PURE__ */ jsx8(SearchField3, { value: view.filter, onChange: (v) => ui5({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
4883
5063
  }
4884
5064
  ),
4885
- view.confirmingClear ? /* @__PURE__ */ jsx8(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui3({ kind: "confirmClear" }), onCancel: () => ui3({ kind: "cancelClear" }) }) : null,
5065
+ view.confirmingClear ? /* @__PURE__ */ jsx8(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui5({ kind: "confirmClear" }), onCancel: () => ui5({ kind: "cancelClear" }) }) : null,
4886
5066
  view.pruned ? /* @__PURE__ */ jsx8(Text4, { role: "caption", children: "Earlier history discarded (limit reached)" }) : null,
4887
5067
  /* @__PURE__ */ jsxs7(HistoryScroller, { controller, children: [
4888
- /* @__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)) }) }),
5068
+ /* @__PURE__ */ jsx8(RowList2, { role: "listbox", "aria-label": "History", children: /* @__PURE__ */ jsx8(VirtualWindow2, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row2) => /* @__PURE__ */ jsx8(HistoryRowView, { row: row2, controller }, row2.key)) }) }),
4889
5069
  view.empty ? /* @__PURE__ */ jsx8(EmptyState3, { title: "No history" }) : null
4890
5070
  ] }),
4891
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
@@ -4939,33 +5119,33 @@ function HistoryCancelButton({ entryId, controller }) {
4939
5119
  }
4940
5120
  );
4941
5121
  }
4942
- function HistoryRowView({ row, controller }) {
4943
- const ui3 = (intent) => applyHistoryUi(controller, intent);
5122
+ function HistoryRowView({ row: row2, controller }) {
5123
+ const ui5 = (intent) => applyHistoryUi(controller, intent);
4944
5124
  return /* @__PURE__ */ jsx8(
4945
5125
  Row2,
4946
5126
  {
4947
- height: row.height,
4948
- selected: row.current,
4949
- focused: row.focused,
4950
- struck: row.future,
4951
- noIcon: row.hideIcon,
4952
- interactive: row.jumpable,
4953
- icon: /* @__PURE__ */ jsx8(EntryIcon, { name: row.iconName }),
4954
- label: row.label,
4955
- meta: row.meta,
4956
- "data-history-row": row.key,
4957
- title: row.label,
5127
+ height: row2.height,
5128
+ selected: row2.current,
5129
+ focused: row2.focused,
5130
+ struck: row2.future,
5131
+ noIcon: row2.hideIcon,
5132
+ interactive: row2.jumpable,
5133
+ icon: /* @__PURE__ */ jsx8(EntryIcon, { name: row2.iconName }),
5134
+ label: row2.label,
5135
+ meta: row2.meta,
5136
+ "data-history-row": row2.key,
5137
+ title: row2.label,
4958
5138
  trailing: /* @__PURE__ */ jsxs7(Fragment4, { children: [
4959
- row.pending ? /* @__PURE__ */ jsx8(PendingGlyph, {}) : null,
4960
- row.failed ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", tone: "error", children: "failed" }) : null,
4961
- row.showSource ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: row.sourceTitle, children: row.sourceGlyph }) : null,
4962
- row.showCoalesced ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: row.coalescedTitle, children: row.coalescedText }) : null,
4963
- row.showTime ? /* @__PURE__ */ jsx8(Text4, { role: "meta", children: row.time }) : null,
4964
- row.pending ? /* @__PURE__ */ jsx8(HistoryCancelButton, { entryId: row.cancelId, controller }) : null
5139
+ row2.pending ? /* @__PURE__ */ jsx8(PendingGlyph, {}) : null,
5140
+ row2.failed ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", tone: "error", children: "failed" }) : null,
5141
+ row2.showSource ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: row2.sourceTitle, children: row2.sourceGlyph }) : null,
5142
+ row2.showCoalesced ? /* @__PURE__ */ jsx8(Badge3, { size: "sm", title: row2.coalescedTitle, children: row2.coalescedText }) : null,
5143
+ row2.showTime ? /* @__PURE__ */ jsx8(Text4, { role: "meta", children: row2.time }) : null,
5144
+ row2.pending ? /* @__PURE__ */ jsx8(HistoryCancelButton, { entryId: row2.cancelId, controller }) : null
4965
5145
  ] }),
4966
- onClick: () => ui3({ kind: "jump", entryId: row.entryId }),
4967
- onMouseEnter: () => ui3({ kind: "hover", entryId: row.entryId }),
4968
- onContextMenuAt: (x, y) => ui3({ kind: "contextMenu", entryId: row.entryId, x, y })
5146
+ onClick: () => ui5({ kind: "jump", entryId: row2.entryId }),
5147
+ onMouseEnter: () => ui5({ kind: "hover", entryId: row2.entryId }),
5148
+ onContextMenuAt: (x, y) => ui5({ kind: "contextMenu", entryId: row2.entryId, x, y })
4969
5149
  }
4970
5150
  );
4971
5151
  }
@@ -6003,6 +6183,181 @@ var ColorPanelController = class {
6003
6183
  import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5 } from "react";
6004
6184
  import { useSyncExternalStore as useSyncExternalStore4 } from "react";
6005
6185
 
6186
+ // src/edit/color/declaration.ts
6187
+ var SPACE_LABEL = { hex: "HEX", rgb: "RGB", hsl: "HSL", hsv: "HSV", oklch: "OKLCH" };
6188
+ var COMPONENT_LABELS = { rgb: ["R", "G", "B"], hsl: ["H", "S", "L"], oklch: ["L", "C", "H"] };
6189
+ function roundForSpace(space, v) {
6190
+ if (space === "oklch") return Math.round(v * 10) / 10;
6191
+ return Math.round(v);
6192
+ }
6193
+ function colorViewState(c) {
6194
+ const value = c.getColorValue();
6195
+ const space = c.getSpace();
6196
+ const comps = value.components ?? [];
6197
+ const labels = COMPONENT_LABELS[space] ?? [];
6198
+ const active = c.getActiveChannel();
6199
+ return {
6200
+ hex: value.hex,
6201
+ bound: c.isBound(),
6202
+ fgBg: c.getChannelsMode() === "fg-bg",
6203
+ fgActive: active === "fg",
6204
+ bgActive: active === "bg",
6205
+ space,
6206
+ spaceOptions: EDIT_SPACES.map((sp) => ({ value: sp, label: SPACE_LABEL[sp] })),
6207
+ isHex: space === "hex",
6208
+ isComponents: space !== "hex",
6209
+ components: labels.map((label, index) => ({ key: label, label, index, value: roundForSpace(space, comps[index] ?? 0) })),
6210
+ alphaShown: c.isAlphaShown(),
6211
+ alphaPercent: Math.round(value.alpha * 100),
6212
+ hasTokens: c.hasTokens(),
6213
+ boundTokenId: c.getBoundTokenId() ?? "",
6214
+ tokenOptions: c.getTokens().map((t) => ({ value: t.id, label: t.name })),
6215
+ swatches: c.getSwatches().map((hex) => ({ key: hex, hex })),
6216
+ hasRecent: c.getRecent().length > 0,
6217
+ recent: c.getRecent().map((hex, i) => ({ key: `${hex}-${i}`, hex }))
6218
+ };
6219
+ }
6220
+ function applyColorUi(c, intent) {
6221
+ switch (intent.kind) {
6222
+ case "setChannel":
6223
+ c.setChannel(intent.channel);
6224
+ break;
6225
+ case "swapChannels":
6226
+ c.swapChannels();
6227
+ break;
6228
+ case "setSpace":
6229
+ c.setSpace(intent.space);
6230
+ break;
6231
+ case "setHex":
6232
+ c.setFromColor(intent.value, void 0, { commit: true });
6233
+ break;
6234
+ case "setComponent": {
6235
+ const comps = [...c.getColorValue().components ?? [0, 0, 0]];
6236
+ comps[intent.index] = intent.value;
6237
+ c.setFromComponents(c.getSpace(), comps, { commit: true });
6238
+ break;
6239
+ }
6240
+ case "setAlpha":
6241
+ c.setAlpha(intent.percent / 100, { commit: true });
6242
+ break;
6243
+ case "eyedropper":
6244
+ c.requestEyedropper();
6245
+ break;
6246
+ case "bindToken":
6247
+ if (intent.tokenId) c.bindToken(intent.tokenId);
6248
+ else c.unbindToken();
6249
+ break;
6250
+ case "unbindToken":
6251
+ c.unbindToken();
6252
+ break;
6253
+ case "addSwatch":
6254
+ void c.addSwatch();
6255
+ break;
6256
+ case "selectColor":
6257
+ c.selectColor(intent.hex);
6258
+ break;
6259
+ case "removeSwatch":
6260
+ void c.removeSwatch(intent.hex);
6261
+ break;
6262
+ }
6263
+ }
6264
+ var colorViewManifest = {
6265
+ id: colorManifest.id,
6266
+ version: "1.0.0",
6267
+ title: "Color",
6268
+ category: "edit",
6269
+ layout: { slot: "side" },
6270
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The ColorViewState the runtime derives from the controller on every draw." }],
6271
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
6272
+ commands: []
6273
+ };
6274
+ var EMPTY4 = { hex: "#000000", bound: false, fgBg: false, fgActive: true, bgActive: false, space: "hex", spaceOptions: EDIT_SPACES.map((sp) => ({ value: sp, label: SPACE_LABEL[sp] })), isHex: true, isComponents: false, components: [], alphaShown: false, alphaPercent: 100, hasTokens: false, boundTokenId: "", tokenOptions: [], swatches: [], hasRecent: false, recent: [] };
6275
+ var BEHAVIOURS4 = {
6276
+ "edit/colorPanelView": (inputs) => inputs.view ?? EMPTY4
6277
+ };
6278
+ var text4 = (b) => ({ component: "text", props: { text: b } });
6279
+ var ui4 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
6280
+ var row = (children, extra = {}, when) => ({
6281
+ component: "chrome/Stack",
6282
+ ...when ? { when } : {},
6283
+ props: { direction: { value: "row" }, pad: { value: "none" }, ...extra },
6284
+ children
6285
+ });
6286
+ var COLOR_DECLARATION = {
6287
+ format: "xeno.block@1",
6288
+ manifest: colorViewManifest,
6289
+ behaviour: "edit/colorPanelView",
6290
+ composition: {
6291
+ component: "chrome/Column",
6292
+ props: { className: { value: "xeno-color-panel" } },
6293
+ children: [
6294
+ {
6295
+ component: "chrome/Stack",
6296
+ props: { gap: { value: 8 } },
6297
+ children: [
6298
+ row(
6299
+ [
6300
+ { component: "chrome/ColorChip", props: { color: { input: "view.hex" }, label: { value: "Foreground" }, pressed: { input: "view.fgActive" } }, on: { onClick: ui4("setChannel", { channel: { value: "fg" } }) } },
6301
+ { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "color/SwapIcon" } }, label: { value: "Swap FG/BG (X)" } }, on: { onClick: ui4("swapChannels") } },
6302
+ { component: "chrome/ColorChip", props: { color: { value: null }, label: { value: "Background" }, pressed: { input: "view.bgActive" } }, on: { onClick: ui4("setChannel", { channel: { value: "bg" } }) } }
6303
+ ],
6304
+ {},
6305
+ { input: "view.fgBg" }
6306
+ ),
6307
+ { component: "color/Picker" },
6308
+ row(
6309
+ [
6310
+ { component: "chrome/Select", props: { size: { value: "sm" }, label: { value: "Color model" }, value: { input: "view.space" }, options: { input: "view.spaceOptions" } }, on: { onChange: ui4("setSpace", { space: { arg: "" } }) } },
6311
+ { component: "chrome/Input", when: { input: "view.isHex" }, props: { variant: { value: "mono" }, label: { value: "Hex" }, value: { input: "view.hex" }, disabled: { input: "view.bound" } }, on: { onChange: ui4("setHex", { value: { arg: "" } }) } },
6312
+ { component: "chrome/NumberInput", each: { input: "view.components", key: "key" }, props: { caption: { item: "label" }, value: { item: "value" }, disabled: { input: "view.bound" } }, on: { onChange: ui4("setComponent", { index: { item: "index" }, value: { arg: "" } }) } },
6313
+ { component: "chrome/NumberInput", when: { input: "view.alphaShown" }, props: { caption: { value: "A" }, label: { value: "Alpha" }, min: { value: 0 }, max: { value: 100 }, value: { input: "view.alphaPercent" }, disabled: { input: "view.bound" } }, on: { onChange: ui4("setAlpha", { percent: { arg: "" } }) } },
6314
+ { component: "chrome/IconButton", props: { size: { value: "sm" }, icon: { node: { component: "color/EyedropperIcon" } }, label: { value: "Pick from screen" } }, on: { onClick: ui4("eyedropper") } }
6315
+ ],
6316
+ { gap: { value: 4 } }
6317
+ ),
6318
+ {
6319
+ component: "chrome/Field",
6320
+ when: { input: "view.hasTokens" },
6321
+ props: { label: { value: "Token" }, htmlFor: { value: "xeno-color-token" } },
6322
+ children: [
6323
+ row(
6324
+ [
6325
+ { component: "chrome/Select", props: { size: { value: "sm" }, label: { value: "Bind token" }, value: { input: "view.boundTokenId" }, placeholder: { value: "\u2014 raw \u2014" }, options: { input: "view.tokenOptions" } }, on: { onChange: ui4("bindToken", { tokenId: { arg: "" } }) } },
6326
+ { component: "chrome/TextButton", when: { input: "view.bound" }, on: { onClick: ui4("unbindToken") }, children: [text4({ value: "Unbind" })] }
6327
+ ],
6328
+ { gap: { value: 4 } }
6329
+ )
6330
+ ]
6331
+ },
6332
+ {
6333
+ component: "chrome/Section",
6334
+ props: { title: { value: "Swatches" }, flush: { value: true }, actions: { node: { component: "chrome/TextButton", on: { onClick: ui4("addSwatch") }, children: [text4({ value: "+ Save" })] } } },
6335
+ children: [
6336
+ {
6337
+ component: "chrome/SwatchGrid",
6338
+ props: { label: { value: "Swatches" } },
6339
+ children: [{ component: "chrome/ColorChip", each: { input: "view.swatches", key: "key" }, props: { size: { value: "grid" }, color: { item: "hex" }, label: { item: "hex" } }, on: { onClick: ui4("selectColor", { hex: { item: "hex" } }), onContextMenu: ui4("removeSwatch", { hex: { item: "hex" } }) } }]
6340
+ }
6341
+ ]
6342
+ },
6343
+ {
6344
+ component: "chrome/Section",
6345
+ when: { input: "view.hasRecent" },
6346
+ props: { title: { value: "Recent" }, flush: { value: true } },
6347
+ children: [
6348
+ {
6349
+ component: "chrome/SwatchGrid",
6350
+ props: { label: { value: "Recent" } },
6351
+ children: [{ component: "chrome/ColorChip", each: { input: "view.recent", key: "key" }, props: { size: { value: "grid" }, color: { item: "hex" }, label: { item: "hex" } }, on: { onClick: ui4("selectColor", { hex: { item: "hex" } }) } }]
6352
+ }
6353
+ ]
6354
+ }
6355
+ ]
6356
+ }
6357
+ ]
6358
+ }
6359
+ };
6360
+
6006
6361
  // src/edit/color/react/viewHelpers.ts
6007
6362
  function hsvFromHexOrBlack(hex) {
6008
6363
  const rgba = parseHexColor(hex);
@@ -6096,24 +6451,39 @@ function SwapIcon() {
6096
6451
 
6097
6452
  // src/edit/color/react/ColorPanelView.tsx
6098
6453
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
6099
- var SPACE_LABEL = {
6100
- hex: "HEX",
6101
- rgb: "RGB",
6102
- hsl: "HSL",
6103
- hsv: "HSV",
6104
- oklch: "OKLCH"
6105
- };
6106
- var COMPONENT_LABELS = {
6107
- rgb: ["R", "G", "B"],
6108
- hsl: ["H", "S", "L"],
6109
- oklch: ["L", "C", "H"]
6110
- };
6111
6454
  function ColorPanelView({ controller }) {
6455
+ const subscribe = useCallback5((cb) => controller.subscribe(cb), [controller]);
6456
+ const getSnapshot = useCallback5(() => controller.getRevision(), [controller]);
6457
+ useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
6458
+ const view = colorViewState(controller);
6459
+ const ui5 = (intent) => applyColorUi(controller, intent);
6460
+ return /* @__PURE__ */ jsx10(Column4, { className: "xeno-color-panel", children: /* @__PURE__ */ jsxs9(Stack3, { gap: 8, children: [
6461
+ view.fgBg ? /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", children: [
6462
+ /* @__PURE__ */ jsx10(ColorChip, { color: view.hex, label: "Foreground", pressed: view.fgActive, onClick: () => ui5({ kind: "setChannel", channel: "fg" }) }),
6463
+ /* @__PURE__ */ jsx10(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx10(SwapIcon, {}), label: "Swap FG/BG (X)", onClick: () => ui5({ kind: "swapChannels" }) }),
6464
+ /* @__PURE__ */ jsx10(ColorChip, { color: null, label: "Background", pressed: view.bgActive, onClick: () => ui5({ kind: "setChannel", channel: "bg" }) })
6465
+ ] }) : null,
6466
+ /* @__PURE__ */ jsx10(ColorPicker, { controller }),
6467
+ /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6468
+ /* @__PURE__ */ jsx10(Select3, { size: "sm", label: "Color model", value: view.space, options: view.spaceOptions, onChange: (v) => ui5({ kind: "setSpace", space: v }) }),
6469
+ view.isHex ? /* @__PURE__ */ jsx10(Input3, { variant: "mono", label: "Hex", value: view.hex, onChange: (v) => ui5({ kind: "setHex", value: v }), disabled: view.bound }) : null,
6470
+ view.components.map((comp) => /* @__PURE__ */ jsx10(NumberInput, { caption: comp.label, value: comp.value, onChange: (n) => ui5({ kind: "setComponent", index: comp.index, value: n }), disabled: view.bound }, comp.key)),
6471
+ view.alphaShown ? /* @__PURE__ */ jsx10(NumberInput, { caption: "A", label: "Alpha", min: 0, max: 100, value: view.alphaPercent, onChange: (n) => ui5({ kind: "setAlpha", percent: n }), disabled: view.bound }) : null,
6472
+ /* @__PURE__ */ jsx10(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx10(EyedropperIcon, {}), label: "Pick from screen", onClick: () => ui5({ kind: "eyedropper" }) })
6473
+ ] }),
6474
+ view.hasTokens ? /* @__PURE__ */ jsx10(Field2, { label: "Token", htmlFor: "xeno-color-token", children: /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6475
+ /* @__PURE__ */ jsx10(Select3, { size: "sm", label: "Bind token", value: view.boundTokenId, placeholder: "\u2014 raw \u2014", options: view.tokenOptions, onChange: (id) => ui5({ kind: "bindToken", tokenId: id }) }),
6476
+ view.bound ? /* @__PURE__ */ jsx10(TextButton2, { onClick: () => ui5({ kind: "unbindToken" }), children: "Unbind" }) : null
6477
+ ] }) }) : null,
6478
+ /* @__PURE__ */ jsx10(Section2, { title: "Swatches", flush: true, actions: /* @__PURE__ */ jsx10(TextButton2, { onClick: () => ui5({ kind: "addSwatch" }), children: "+ Save" }), children: /* @__PURE__ */ jsx10(SwatchGrid, { label: "Swatches", children: view.swatches.map((sw) => /* @__PURE__ */ jsx10(ColorChip, { size: "grid", color: sw.hex, label: sw.hex, onClick: () => ui5({ kind: "selectColor", hex: sw.hex }), onContextMenu: () => ui5({ kind: "removeSwatch", hex: sw.hex }) }, sw.key)) }) }),
6479
+ view.hasRecent ? /* @__PURE__ */ jsx10(Section2, { title: "Recent", flush: true, children: /* @__PURE__ */ jsx10(SwatchGrid, { label: "Recent", children: view.recent.map((r) => /* @__PURE__ */ jsx10(ColorChip, { size: "grid", color: r.hex, label: r.hex, onClick: () => ui5({ kind: "selectColor", hex: r.hex }) }, r.key)) }) }) : null
6480
+ ] }) });
6481
+ }
6482
+ function ColorPicker({ controller }) {
6112
6483
  const subscribe = useCallback5((cb) => controller.subscribe(cb), [controller]);
6113
6484
  const getSnapshot = useCallback5(() => controller.getRevision(), [controller]);
6114
6485
  useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
6115
6486
  const value = controller.getColorValue();
6116
- const space = controller.getSpace();
6117
6487
  const bound = controller.isBound();
6118
6488
  const hsv = hsvFromHexOrBlack(value.hex);
6119
6489
  const spectrumRef = useRef5(null);
@@ -6184,116 +6554,82 @@ function ColorPanelView({ controller }) {
6184
6554
  controller.endDrag();
6185
6555
  }
6186
6556
  }, [controller]);
6187
- const onHexInput = useCallback5(
6188
- (raw) => {
6189
- controller.setFromColor(raw, void 0, { commit: true });
6190
- },
6191
- [controller]
6192
- );
6193
- const onComponentInput = useCallback5(
6194
- (index, n) => {
6195
- const comps2 = [...value.components ?? [0, 0, 0]];
6196
- comps2[index] = n;
6197
- controller.setFromComponents(space, comps2, { commit: true });
6198
- },
6199
- [controller, space, value.components]
6200
- );
6201
- const componentLabels = COMPONENT_LABELS[space];
6202
- const comps = value.components ?? [];
6203
- const activeChannel = controller.getActiveChannel();
6204
- return /* @__PURE__ */ jsx10(Column4, { className: "xeno-color-panel", children: /* @__PURE__ */ jsxs9(Stack3, { gap: 8, children: [
6205
- controller.getChannelsMode() === "fg-bg" ? /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", children: [
6206
- /* @__PURE__ */ jsx10(ColorChip, { color: value.hex, label: "Foreground", pressed: activeChannel === "fg", onClick: () => controller.setChannel("fg") }),
6207
- /* @__PURE__ */ jsx10(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx10(SwapIcon, {}), label: "Swap FG/BG (X)", onClick: () => controller.swapChannels() }),
6208
- /* @__PURE__ */ jsx10(ColorChip, { color: null, label: "Background", pressed: activeChannel === "bg", onClick: () => controller.setChannel("bg") })
6209
- ] }) : null,
6210
- /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", children: [
6211
- /* @__PURE__ */ jsx10(
6212
- CanvasSurface,
6213
- {
6214
- ref: spectrumRef,
6215
- width: 200,
6216
- height: 140,
6217
- grow: true,
6218
- cursor: "crosshair",
6219
- disabled: bound,
6220
- "aria-label": "Saturation and value",
6221
- onPointerDown: (e) => onSpectrumPointer(e, true),
6222
- onPointerMove: (e) => onSpectrumPointer(e, false),
6223
- onPointerUp: endDrag
6224
- }
6225
- ),
6226
- /* @__PURE__ */ jsx10(
6227
- CanvasSurface,
6228
- {
6229
- ref: hueRef,
6230
- width: 14,
6231
- height: 140,
6232
- cursor: "ns-resize",
6233
- disabled: bound,
6234
- "aria-label": "Hue",
6235
- onPointerDown: (e) => onHuePointer(e, true),
6236
- onPointerMove: (e) => onHuePointer(e, false),
6237
- onPointerUp: endDrag
6238
- }
6239
- ),
6240
- controller.isAlphaShown() ? /* @__PURE__ */ jsx10(
6241
- CanvasSurface,
6242
- {
6243
- ref: alphaRef,
6244
- width: 14,
6245
- height: 140,
6246
- cursor: "ns-resize",
6247
- disabled: bound,
6248
- "aria-label": "Alpha",
6249
- onPointerDown: (e) => onAlphaPointer(e, true),
6250
- onPointerMove: (e) => onAlphaPointer(e, false),
6251
- onPointerUp: endDrag
6252
- }
6253
- ) : null
6254
- ] }),
6255
- /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6256
- /* @__PURE__ */ jsx10(Select3, { size: "sm", label: "Color model", value: space, options: EDIT_SPACES.map((sp) => ({ value: sp, label: SPACE_LABEL[sp] })), onChange: (v) => controller.setSpace(v) }),
6257
- space === "hex" ? /* @__PURE__ */ jsx10(Input3, { variant: "mono", label: "Hex", value: value.hex, onChange: onHexInput, disabled: bound }) : (componentLabels ?? []).map((label, i) => /* @__PURE__ */ jsx10(NumberInput, { caption: label, value: roundForSpace(space, i, comps[i] ?? 0), onChange: (n) => onComponentInput(i, n), disabled: bound }, label)),
6258
- controller.isAlphaShown() ? /* @__PURE__ */ jsx10(NumberInput, { caption: "A", label: "Alpha", min: 0, max: 100, value: Math.round(value.alpha * 100), onChange: (n) => controller.setAlpha(n / 100, { commit: true }), disabled: bound }) : null,
6259
- /* @__PURE__ */ jsx10(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx10(EyedropperIcon, {}), label: "Pick from screen", onClick: () => controller.requestEyedropper() })
6260
- ] }),
6261
- controller.hasTokens() ? /* @__PURE__ */ jsx10(Field2, { label: "Token", htmlFor: "xeno-color-token", children: /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6262
- /* @__PURE__ */ jsx10(
6263
- Select3,
6264
- {
6265
- size: "sm",
6266
- label: "Bind token",
6267
- value: controller.getBoundTokenId() ?? "",
6268
- placeholder: "\u2014 raw \u2014",
6269
- options: controller.getTokens().map((t) => ({ value: t.id, label: t.name })),
6270
- onChange: (id) => id ? controller.bindToken(id) : controller.unbindToken()
6271
- }
6272
- ),
6273
- bound ? /* @__PURE__ */ jsx10(TextButton2, { onClick: () => controller.unbindToken(), children: "Unbind" }) : null
6274
- ] }) }) : null,
6275
- /* @__PURE__ */ jsx10(Section2, { title: "Swatches", flush: true, actions: /* @__PURE__ */ jsx10(TextButton2, { onClick: () => void controller.addSwatch(), children: "+ Save" }), children: /* @__PURE__ */ jsx10(SwatchGrid, { label: "Swatches", children: controller.getSwatches().map((hex) => /* @__PURE__ */ jsx10(ColorChip, { size: "grid", color: hex, label: hex, onClick: () => controller.selectColor(hex), onContextMenu: () => void controller.removeSwatch(hex) }, hex)) }) }),
6276
- controller.getRecent().length > 0 ? /* @__PURE__ */ jsx10(Section2, { title: "Recent", flush: true, children: /* @__PURE__ */ jsx10(SwatchGrid, { label: "Recent", children: controller.getRecent().map((hex, i) => /* @__PURE__ */ jsx10(ColorChip, { size: "grid", color: hex, label: hex, onClick: () => controller.selectColor(hex) }, `${hex}-${i}`)) }) }) : null
6277
- ] }) });
6557
+ return /* @__PURE__ */ jsxs9(Stack3, { direction: "row", pad: "none", children: [
6558
+ /* @__PURE__ */ jsx10(
6559
+ CanvasSurface,
6560
+ {
6561
+ ref: spectrumRef,
6562
+ width: 200,
6563
+ height: 140,
6564
+ grow: true,
6565
+ cursor: "crosshair",
6566
+ disabled: bound,
6567
+ "aria-label": "Saturation and value",
6568
+ onPointerDown: (e) => onSpectrumPointer(e, true),
6569
+ onPointerMove: (e) => onSpectrumPointer(e, false),
6570
+ onPointerUp: endDrag
6571
+ }
6572
+ ),
6573
+ /* @__PURE__ */ jsx10(
6574
+ CanvasSurface,
6575
+ {
6576
+ ref: hueRef,
6577
+ width: 14,
6578
+ height: 140,
6579
+ cursor: "ns-resize",
6580
+ disabled: bound,
6581
+ "aria-label": "Hue",
6582
+ onPointerDown: (e) => onHuePointer(e, true),
6583
+ onPointerMove: (e) => onHuePointer(e, false),
6584
+ onPointerUp: endDrag
6585
+ }
6586
+ ),
6587
+ controller.isAlphaShown() ? /* @__PURE__ */ jsx10(
6588
+ CanvasSurface,
6589
+ {
6590
+ ref: alphaRef,
6591
+ width: 14,
6592
+ height: 140,
6593
+ cursor: "ns-resize",
6594
+ disabled: bound,
6595
+ "aria-label": "Alpha",
6596
+ onPointerDown: (e) => onAlphaPointer(e, true),
6597
+ onPointerMove: (e) => onAlphaPointer(e, false),
6598
+ onPointerUp: endDrag
6599
+ }
6600
+ ) : null
6601
+ ] });
6602
+ }
6603
+ function colorRuntimeComponents(controller) {
6604
+ return {
6605
+ "color/Picker": () => /* @__PURE__ */ jsx10(ColorPicker, { controller }),
6606
+ "color/SwapIcon": SwapIcon,
6607
+ "color/EyedropperIcon": EyedropperIcon
6608
+ };
6278
6609
  }
6279
6610
  function clamp01(n) {
6280
6611
  return Math.max(0, Math.min(1, Number.isFinite(n) ? n : 0));
6281
6612
  }
6282
- function roundForSpace(space, index, v) {
6283
- if (space === "oklch") return Math.round(v * 10) / 10;
6284
- return Math.round(v);
6285
- }
6286
6613
 
6287
6614
  // src/edit/color/panel.ts
6288
6615
  function createColorPanel(options = {}) {
6289
6616
  return {
6290
6617
  manifest: colorManifest,
6291
6618
  activate(host) {
6292
- const controller = new ColorPanelController(host, options);
6619
+ const { render: customRender, ...controllerOptions } = options;
6620
+ const controller = new ColorPanelController(host, controllerOptions);
6293
6621
  void controller.load();
6294
6622
  let root = null;
6623
+ let unrender = null;
6295
6624
  return {
6296
6625
  render(el) {
6626
+ if (customRender) {
6627
+ unrender = customRender(el, controller);
6628
+ return () => {
6629
+ unrender?.();
6630
+ unrender = null;
6631
+ };
6632
+ }
6297
6633
  root = createRoot4(el);
6298
6634
  root.render(createElement4(ColorPanelView, { controller }));
6299
6635
  return () => {
@@ -6316,6 +6652,8 @@ function createColorPanel(options = {}) {
6316
6652
  dispose() {
6317
6653
  root?.unmount();
6318
6654
  root = null;
6655
+ unrender?.();
6656
+ unrender = null;
6319
6657
  controller.dispose();
6320
6658
  }
6321
6659
  };
@@ -8801,11 +9139,11 @@ var FindController = class {
8801
9139
  * @param text - The new query text.
8802
9140
  * @param immediate - Skip the debounce (a paste, or pressing Enter).
8803
9141
  */
8804
- setText(text3, immediate = false) {
8805
- if (text3 === this.text) return;
8806
- this.text = text3;
9142
+ setText(text5, immediate = false) {
9143
+ if (text5 === this.text) return;
9144
+ this.text = text5;
8807
9145
  this.index = -1;
8808
- if (text3 === "") {
9146
+ if (text5 === "") {
8809
9147
  this.clearResults("idle");
8810
9148
  this.emitQuery(true);
8811
9149
  return;
@@ -8833,8 +9171,8 @@ var FindController = class {
8833
9171
  this.notify();
8834
9172
  }
8835
9173
  /** The replacement text. Never emitted on its own — a replacement is not a search. */
8836
- setReplaceText(text3) {
8837
- this.replaceText = text3;
9174
+ setReplaceText(text5) {
9175
+ this.replaceText = text5;
8838
9176
  this.notify();
8839
9177
  }
8840
9178
  /** Show or hide the replace row. */
@@ -12124,14 +12462,14 @@ var TreeController = class {
12124
12462
  filter: this.filter,
12125
12463
  labelOf: (node) => node.label,
12126
12464
  isContainer: isTreeContainer
12127
- }).map((row) => ({
12128
- id: row.id,
12129
- node: row.node,
12130
- depth: row.depth,
12131
- container: row.container,
12132
- expanded: row.expanded,
12133
- matched: row.matched,
12134
- childState: childStateOf(row.node)
12465
+ }).map((row2) => ({
12466
+ id: row2.id,
12467
+ node: row2.node,
12468
+ depth: row2.depth,
12469
+ container: row2.container,
12470
+ expanded: row2.expanded,
12471
+ matched: row2.matched,
12472
+ childState: childStateOf(row2.node)
12135
12473
  }));
12136
12474
  return this.rowCache;
12137
12475
  }
@@ -12416,7 +12754,7 @@ var TreeController = class {
12416
12754
  if (rows.length === 0) return false;
12417
12755
  const index = rows.findIndex((r) => r.id === this.focusedId);
12418
12756
  const current = index >= 0 ? index : 0;
12419
- const row = rows[current];
12757
+ const row2 = rows[current];
12420
12758
  switch (event.key) {
12421
12759
  case "ArrowDown":
12422
12760
  this.moveFocus(rows, Math.min(rows.length - 1, current + 1), event);
@@ -12431,15 +12769,15 @@ var TreeController = class {
12431
12769
  this.moveFocus(rows, rows.length - 1, event);
12432
12770
  return true;
12433
12771
  case "ArrowRight":
12434
- if (row?.container && !row.expanded) this.setExpanded(row.id, true);
12435
- else if (row && current + 1 < rows.length) this.moveFocus(rows, current + 1, event);
12772
+ if (row2?.container && !row2.expanded) this.setExpanded(row2.id, true);
12773
+ else if (row2 && current + 1 < rows.length) this.moveFocus(rows, current + 1, event);
12436
12774
  return true;
12437
12775
  case "ArrowLeft": {
12438
- if (row?.container && row.expanded) {
12439
- this.setExpanded(row.id, false);
12776
+ if (row2?.container && row2.expanded) {
12777
+ this.setExpanded(row2.id, false);
12440
12778
  return true;
12441
12779
  }
12442
- const parentId = row?.node.parentId;
12780
+ const parentId = row2?.node.parentId;
12443
12781
  if (parentId) {
12444
12782
  this.focusedId = parentId;
12445
12783
  this.select([parentId]);
@@ -12447,10 +12785,10 @@ var TreeController = class {
12447
12785
  return true;
12448
12786
  }
12449
12787
  case "Enter":
12450
- if (row) this.open(row.id, { alt: event.altKey });
12788
+ if (row2) this.open(row2.id, { alt: event.altKey });
12451
12789
  return true;
12452
12790
  case "F2":
12453
- if (row) this.beginRename(row.id);
12791
+ if (row2) this.beginRename(row2.id);
12454
12792
  return true;
12455
12793
  case "Delete":
12456
12794
  case "Backspace":
@@ -12470,12 +12808,12 @@ var TreeController = class {
12470
12808
  }
12471
12809
  if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
12472
12810
  const fresh = now - this.typeAhead.at > this.typeAheadMs;
12473
- const text3 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12474
- this.typeAhead = { text: text3, at: now };
12811
+ const text5 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12812
+ this.typeAhead = { text: text5, at: now };
12475
12813
  const from = fresh ? current : current + 1;
12476
12814
  for (let i = 0; i < rows.length; i += 1) {
12477
12815
  const candidate = rows[(from + i) % rows.length];
12478
- if (candidate.node.label.toLowerCase().startsWith(text3)) {
12816
+ if (candidate.node.label.toLowerCase().startsWith(text5)) {
12479
12817
  this.focusedId = candidate.id;
12480
12818
  this.select([candidate.id]);
12481
12819
  return true;
@@ -12501,9 +12839,9 @@ var TreeController = class {
12501
12839
  return true;
12502
12840
  }
12503
12841
  /** Update the rename draft. */
12504
- updateRenameDraft(text3) {
12842
+ updateRenameDraft(text5) {
12505
12843
  if (!this.renaming) return;
12506
- this.renaming = { ...this.renaming, draft: text3 };
12844
+ this.renaming = { ...this.renaming, draft: text5 };
12507
12845
  this.notify();
12508
12846
  }
12509
12847
  /**
@@ -13232,10 +13570,10 @@ function TreePanelView({
13232
13570
  e.preventDefault();
13233
13571
  }
13234
13572
  },
13235
- children: /* @__PURE__ */ jsx17("div", { style: { height: window2.totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx17("div", { style: { transform: `translateY(${window2.offsetTop}px)` }, children: slice.map((row) => /* @__PURE__ */ jsx17(
13573
+ children: /* @__PURE__ */ jsx17("div", { style: { height: window2.totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx17("div", { style: { transform: `translateY(${window2.offsetTop}px)` }, children: slice.map((row2) => /* @__PURE__ */ jsx17(
13236
13574
  TreeRowView,
13237
13575
  {
13238
- row,
13576
+ row: row2,
13239
13577
  controller,
13240
13578
  state,
13241
13579
  rowHeight,
@@ -13243,7 +13581,7 @@ function TreePanelView({
13243
13581
  openOn,
13244
13582
  columns
13245
13583
  },
13246
- row.id
13584
+ row2.id
13247
13585
  )) }) })
13248
13586
  }
13249
13587
  ) });
@@ -13285,7 +13623,7 @@ function TreePanelView({
13285
13623
  ] });
13286
13624
  }
13287
13625
  function TreeRowView({
13288
- row,
13626
+ row: row2,
13289
13627
  controller,
13290
13628
  state,
13291
13629
  rowHeight,
@@ -13293,18 +13631,18 @@ function TreeRowView({
13293
13631
  openOn,
13294
13632
  columns
13295
13633
  }) {
13296
- const node = row.node;
13297
- const selected = state.selection.includes(row.id);
13298
- const renaming = state.renaming?.id === row.id ? state.renaming : null;
13299
- const drop = state.drag?.targetId === row.id ? state.drag : null;
13300
- const twisty = row.container ? /* @__PURE__ */ jsx17(
13634
+ const node = row2.node;
13635
+ const selected = state.selection.includes(row2.id);
13636
+ const renaming = state.renaming?.id === row2.id ? state.renaming : null;
13637
+ const drop = state.drag?.targetId === row2.id ? state.drag : null;
13638
+ const twisty = row2.container ? /* @__PURE__ */ jsx17(
13301
13639
  "span",
13302
13640
  {
13303
13641
  role: "button",
13304
- "aria-label": row.expanded ? "Collapse" : "Expand",
13642
+ "aria-label": row2.expanded ? "Collapse" : "Expand",
13305
13643
  onClick: (e) => {
13306
13644
  e.stopPropagation();
13307
- controller.setExpanded(row.id, !row.expanded);
13645
+ controller.setExpanded(row2.id, !row2.expanded);
13308
13646
  },
13309
13647
  style: {
13310
13648
  display: "flex",
@@ -13313,7 +13651,7 @@ function TreeRowView({
13313
13651
  width: 12,
13314
13652
  flex: "0 0 auto",
13315
13653
  cursor: "pointer",
13316
- transform: row.expanded ? "rotate(90deg)" : void 0,
13654
+ transform: row2.expanded ? "rotate(90deg)" : void 0,
13317
13655
  transition: "transform 80ms ease",
13318
13656
  opacity: 0.7
13319
13657
  },
@@ -13338,34 +13676,34 @@ function TreeRowView({
13338
13676
  Row4,
13339
13677
  {
13340
13678
  role: "treeitem",
13341
- "aria-expanded": row.container ? row.expanded : void 0,
13342
- "aria-level": row.depth + 1,
13679
+ "aria-expanded": row2.container ? row2.expanded : void 0,
13680
+ "aria-level": row2.depth + 1,
13343
13681
  selected,
13344
13682
  disabled: node.caps?.select === false,
13345
13683
  depth: 0,
13346
- style: { height: rowHeight, paddingLeft: 6 + row.depth * indentPx },
13684
+ style: { height: rowHeight, paddingLeft: 6 + row2.depth * indentPx },
13347
13685
  onClick: (e) => {
13348
- controller.select([row.id], {
13686
+ controller.select([row2.id], {
13349
13687
  toggle: e.ctrlKey || e.metaKey,
13350
13688
  range: e.shiftKey
13351
13689
  });
13352
- if (openOn === "single") controller.open(row.id, { alt: e.altKey });
13690
+ if (openOn === "single") controller.open(row2.id, { alt: e.altKey });
13353
13691
  },
13354
13692
  onDoubleClick: () => {
13355
- if (openOn === "double") controller.open(row.id);
13693
+ if (openOn === "double") controller.open(row2.id);
13356
13694
  },
13357
13695
  onContextMenu: (e) => {
13358
13696
  e.preventDefault();
13359
- controller.requestContextMenu(selected ? state.selection : [row.id], e.clientX, e.clientY);
13697
+ controller.requestContextMenu(selected ? state.selection : [row2.id], e.clientX, e.clientY);
13360
13698
  },
13361
13699
  onPointerDown: (e) => {
13362
13700
  if (e.button !== 0) return;
13363
- controller.beginDrag(selected ? state.selection : [row.id]);
13701
+ controller.beginDrag(selected ? state.selection : [row2.id]);
13364
13702
  },
13365
13703
  onPointerMove: (e) => {
13366
13704
  if (!state.drag) return;
13367
13705
  const rect = e.currentTarget.getBoundingClientRect();
13368
- controller.dragOver(row.id, e.clientY - rect.top);
13706
+ controller.dragOver(row2.id, e.clientY - rect.top);
13369
13707
  },
13370
13708
  onPointerUp: () => {
13371
13709
  if (state.drag) controller.endDrag();
@@ -13399,7 +13737,7 @@ function TreeRowView({
13399
13737
  }
13400
13738
  }
13401
13739
  ) : node.label,
13402
- meta: /* @__PURE__ */ jsx17(ChildStateChip, { row, controller }),
13740
+ meta: /* @__PURE__ */ jsx17(ChildStateChip, { row: row2, controller }),
13403
13741
  trailing: /* @__PURE__ */ jsxs16(Fragment9, { children: [
13404
13742
  columns?.map((columnId) => /* @__PURE__ */ jsx17("span", { style: { opacity: 0.7, fontVariantNumeric: "tabular-nums" }, children: node.columns?.[columnId] ?? "" }, columnId)),
13405
13743
  node.glyphs?.map((glyph) => /* @__PURE__ */ jsx17("span", { title: glyph.title, style: { display: "flex", opacity: 0.7 }, children: /* @__PURE__ */ jsx17(NodeIcon, { icon: glyph.icon }) }, glyph.id)),
@@ -13414,7 +13752,7 @@ function TreeRowView({
13414
13752
  icon: /* @__PURE__ */ jsx17(NodeIcon, { icon: action.icon }),
13415
13753
  label: action.label,
13416
13754
  disabled: action.disabled,
13417
- onClick: () => controller.runAction(row.id, action.id)
13755
+ onClick: () => controller.runAction(row2.id, action.id)
13418
13756
  },
13419
13757
  action.id
13420
13758
  ))
@@ -13425,35 +13763,35 @@ function TreeRowView({
13425
13763
  );
13426
13764
  }
13427
13765
  function ChildStateChip({
13428
- row,
13766
+ row: row2,
13429
13767
  controller
13430
13768
  }) {
13431
- if (!row.container) return null;
13432
- if (row.childState === "loading") {
13769
+ if (!row2.container) return null;
13770
+ if (row2.childState === "loading") {
13433
13771
  return /* @__PURE__ */ jsx17("span", { style: { opacity: 0.5, fontSize: 9 }, children: "loading\u2026" });
13434
13772
  }
13435
- if (row.childState === "error") {
13436
- const error = row.node.error;
13773
+ if (row2.childState === "error") {
13774
+ const error = row2.node.error;
13437
13775
  return /* @__PURE__ */ jsx17(Badge7, { tone: "error", children: error?.code === "denied" ? "no access" : error?.code ?? "failed" });
13438
13776
  }
13439
- if (row.node.hasMore && row.expanded) {
13777
+ if (row2.node.hasMore && row2.expanded) {
13440
13778
  return /* @__PURE__ */ jsx17(
13441
13779
  "span",
13442
13780
  {
13443
13781
  role: "button",
13444
13782
  onClick: (e) => {
13445
13783
  e.stopPropagation();
13446
- controller.loadMore(row.id);
13784
+ controller.loadMore(row2.id);
13447
13785
  },
13448
13786
  style: { cursor: "pointer", opacity: 0.7, fontSize: 9 },
13449
13787
  children: "load more\u2026"
13450
13788
  }
13451
13789
  );
13452
13790
  }
13453
- if (row.node.truncatedAt !== void 0) {
13454
- return /* @__PURE__ */ jsx17(Badge7, { tone: "warning", children: `first ${row.node.truncatedAt}` });
13791
+ if (row2.node.truncatedAt !== void 0) {
13792
+ return /* @__PURE__ */ jsx17(Badge7, { tone: "warning", children: `first ${row2.node.truncatedAt}` });
13455
13793
  }
13456
- if (row.expanded && isEmptyContainer(row.node)) {
13794
+ if (row2.expanded && isEmptyContainer(row2.node)) {
13457
13795
  return /* @__PURE__ */ jsx17("span", { style: { opacity: 0.4, fontSize: 9 }, children: "empty" });
13458
13796
  }
13459
13797
  return null;
@@ -13667,6 +14005,8 @@ function numberConfig(value, fallback) {
13667
14005
  }
13668
14006
  var treePanel = createTreePanel();
13669
14007
  export {
14008
+ BEHAVIOURS4 as COLOR_BEHAVIOURS,
14009
+ COLOR_DECLARATION,
13670
14010
  COLOR_SPACES,
13671
14011
  COMMENTS_PANEL_ID,
13672
14012
  CURVE_PANEL_ID,
@@ -13692,21 +14032,24 @@ export {
13692
14032
  FindPanelView,
13693
14033
  GENERIC_ICON,
13694
14034
  GraphController,
13695
- BEHAVIOURS2 as HISTORY_BEHAVIOURS,
14035
+ BEHAVIOURS3 as HISTORY_BEHAVIOURS,
13696
14036
  HISTORY_DECLARATION,
13697
14037
  HistoryPanelController,
13698
14038
  HistoryPanelView,
13699
14039
  IMPLEMENTED_TREE_MODES,
13700
- BEHAVIOURS as INSPECTOR_BEHAVIOURS,
14040
+ BEHAVIOURS2 as INSPECTOR_BEHAVIOURS,
13701
14041
  INSPECTOR_DECLARATION,
13702
14042
  InspectorPanelController,
13703
14043
  InspectorPanelView,
14044
+ BEHAVIOURS as LAYERS_BEHAVIOURS,
14045
+ LAYERS_DECLARATION,
13704
14046
  LAYER_COLUMNS,
13705
14047
  LayersPanelController,
13706
14048
  LayersPanelView,
13707
14049
  MIXED,
13708
14050
  NODEGRAPH_PANEL_ID,
13709
14051
  NodeGraphPanelView,
14052
+ SPACE_LABEL,
13710
14053
  TREE_PANEL_ID,
13711
14054
  Thumbnail,
13712
14055
  TreeController,
@@ -13724,8 +14067,10 @@ export {
13724
14067
  allIds,
13725
14068
  anchorStateOf,
13726
14069
  appliedCount,
14070
+ applyColorUi,
13727
14071
  applyHistoryUi,
13728
14072
  applyInspectorUi,
14073
+ applyLayersUi,
13729
14074
  applyPrecision,
13730
14075
  applyTangentDrag,
13731
14076
  ariaSliderProps,
@@ -13740,6 +14085,9 @@ export {
13740
14085
  collectDescendants,
13741
14086
  colorManifest,
13742
14087
  colorPanel,
14088
+ colorRuntimeComponents,
14089
+ colorViewManifest,
14090
+ colorViewState,
13743
14091
  commentsManifest,
13744
14092
  commentsPanel,
13745
14093
  commitPolicyOf,
@@ -13825,6 +14173,9 @@ export {
13825
14173
  keyRefId,
13826
14174
  layersManifest,
13827
14175
  layersPanel,
14176
+ layersRuntimeComponents,
14177
+ layersViewManifest,
14178
+ layersViewState,
13828
14179
  liveEdges,
13829
14180
  makeColorValue,
13830
14181
  matchesFilter,