@xenosystem/blocks 0.13.16 → 0.13.18

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.
@@ -1,5 +1,4 @@
1
1
  import {
2
- ChevronGlyph,
3
2
  CollapseGlyph,
4
3
  DiamondIcon,
5
4
  EntryIcon,
@@ -22,7 +21,7 @@ import {
22
21
  VisibilityIcon,
23
22
  XIcon,
24
23
  XIcon2
25
- } from "../chunk-PI5WOATA.js";
24
+ } from "../chunk-7S2RKUAM.js";
26
25
  import "../chunk-2KG3PWR4.js";
27
26
 
28
27
  // src/edit/layers/panel.ts
@@ -1488,9 +1487,9 @@ function LayersPanelView({ controller }) {
1488
1487
  const getSnapshot = useCallback(() => controller.getRevision(), [controller]);
1489
1488
  useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1490
1489
  const view = layersViewState(controller);
1491
- const ui6 = (intent) => applyLayersUi(controller, intent);
1490
+ const ui8 = (intent) => applyLayersUi(controller, intent);
1492
1491
  return /* @__PURE__ */ jsxs(Column, { className: "xeno-layers-panel", children: [
1493
- view.searchShown ? /* @__PURE__ */ jsx2(Toolbar, { children: /* @__PURE__ */ jsx2(SearchField, { value: view.filter, onChange: (v) => ui6({ kind: "filter", value: v }), placeholder: "Search layers", label: "Search layers" }) }) : null,
1492
+ view.searchShown ? /* @__PURE__ */ jsx2(Toolbar, { children: /* @__PURE__ */ jsx2(SearchField, { value: view.filter, onChange: (v) => ui8({ kind: "filter", value: v }), placeholder: "Search layers", label: "Search layers" }) }) : null,
1494
1493
  /* @__PURE__ */ jsxs(LayersScroller, { controller, children: [
1495
1494
  /* @__PURE__ */ jsx2(RowList, { role: "tree", "aria-label": "Layers", children: /* @__PURE__ */ jsx2(VirtualWindow, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row2) => /* @__PURE__ */ jsx2(LayerRow, { row: row2, thumbnailSize: view.thumbnailSize, controller }, row2.id)) }) }),
1496
1495
  view.empty ? /* @__PURE__ */ jsx2(EmptyState, { title: "No layers" }) : null
@@ -1603,7 +1602,7 @@ function LayersThumbnail({ thumbnailKey, size, controller }) {
1603
1602
  return /* @__PURE__ */ jsx2(Thumbnail, { size, data: controller.getThumbnail(thumbnailKey) });
1604
1603
  }
1605
1604
  function LayerRow({ row: row2, thumbnailSize, controller }) {
1606
- const ui6 = (intent) => applyLayersUi(controller, intent);
1605
+ const ui8 = (intent) => applyLayersUi(controller, intent);
1607
1606
  return /* @__PURE__ */ jsx2(
1608
1607
  Row,
1609
1608
  {
@@ -1618,11 +1617,11 @@ function LayerRow({ row: row2, thumbnailSize, controller }) {
1618
1617
  title: row2.title,
1619
1618
  icon: row2.hasThumbnail ? /* @__PURE__ */ jsx2(LayersThumbnail, { thumbnailKey: row2.thumbnailKey, size: thumbnailSize, controller }) : /* @__PURE__ */ jsx2(KindGlyph, {}),
1620
1619
  leading: /* @__PURE__ */ jsxs(Fragment, { children: [
1621
- row2.showVisibility ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(VisibilityIcon, { open: row2.visible }), label: row2.visibilityLabel, active: row2.visible, onClick: () => ui6({ kind: "toggleProp", id: row2.id, prop: "visible" }) }) : null,
1622
- row2.showLock ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(LockStateIcon, { locked: row2.locked }), label: row2.lockLabel, active: row2.locked, onClick: () => ui6({ kind: "toggleProp", id: row2.id, prop: "locked" }) }) : null,
1623
- row2.showSolo ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "S", label: "Solo", on: row2.solo, onChange: () => ui6({ kind: "toggleProp", id: row2.id, prop: "solo" }) }) : null,
1624
- row2.showMute ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "M", label: "Mute", on: row2.muted, onChange: () => ui6({ kind: "toggleProp", id: row2.id, prop: "muted" }) }) : null,
1625
- row2.showArm ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "R", label: "Arm", on: row2.armed, onChange: () => ui6({ kind: "toggleProp", id: row2.id, prop: "armed" }) }) : null,
1620
+ row2.showVisibility ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(VisibilityIcon, { open: row2.visible }), label: row2.visibilityLabel, active: row2.visible, onClick: () => ui8({ kind: "toggleProp", id: row2.id, prop: "visible" }) }) : null,
1621
+ row2.showLock ? /* @__PURE__ */ jsx2(IconButton, { size: "sm", icon: /* @__PURE__ */ jsx2(LockStateIcon, { locked: row2.locked }), label: row2.lockLabel, active: row2.locked, onClick: () => ui8({ kind: "toggleProp", id: row2.id, prop: "locked" }) }) : null,
1622
+ row2.showSolo ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "S", label: "Solo", on: row2.solo, onChange: () => ui8({ kind: "toggleProp", id: row2.id, prop: "solo" }) }) : null,
1623
+ row2.showMute ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "M", label: "Mute", on: row2.muted, onChange: () => ui8({ kind: "toggleProp", id: row2.id, prop: "muted" }) }) : null,
1624
+ row2.showArm ? /* @__PURE__ */ jsx2(ChipToggle, { glyph: "R", label: "Arm", on: row2.armed, onChange: () => ui8({ kind: "toggleProp", id: row2.id, prop: "armed" }) }) : null,
1626
1625
  row2.showColor ? /* @__PURE__ */ jsx2(ColorTag, { color: row2.colorLabel }) : null
1627
1626
  ] }),
1628
1627
  label: row2.renaming ? /* @__PURE__ */ jsx2(LayersRenameField, { id: row2.id, controller }) : row2.name,
@@ -1630,9 +1629,9 @@ function LayerRow({ row: row2, thumbnailSize, controller }) {
1630
1629
  row2.adornments.map((a) => /* @__PURE__ */ jsx2(Badge, { size: "sm", title: a.title, children: a.text }, a.key)),
1631
1630
  row2.showOpacity ? /* @__PURE__ */ jsx2(Text, { role: "number", children: row2.opacity }) : null
1632
1631
  ] }),
1633
- onExpandedChange: () => ui6({ kind: "toggleExpanded", id: row2.id }),
1634
- onDoubleClick: () => ui6({ kind: "beginRename", id: row2.id }),
1635
- onContextMenuAt: (x, y) => ui6({ kind: "contextMenu", id: row2.id, x, y })
1632
+ onExpandedChange: () => ui8({ kind: "toggleExpanded", id: row2.id }),
1633
+ onDoubleClick: () => ui8({ kind: "beginRename", id: row2.id }),
1634
+ onContextMenuAt: (x, y) => ui8({ kind: "contextMenu", id: row2.id, x, y })
1636
1635
  }
1637
1636
  );
1638
1637
  }
@@ -2024,8 +2023,8 @@ function constraintsOf(field) {
2024
2023
  scale: field.scale
2025
2024
  };
2026
2025
  }
2027
- function parseNumeric(text6) {
2028
- const trimmed = String(text6).trim();
2026
+ function parseNumeric(text8) {
2027
+ const trimmed = String(text8).trim();
2029
2028
  if (trimmed.length === 0) return { ok: false, reason: "empty" };
2030
2029
  const match = /^[+-]?(\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/.exec(trimmed);
2031
2030
  if (!match) return { ok: false, reason: "nan" };
@@ -2050,8 +2049,8 @@ function normalizeNumeric(value, c) {
2050
2049
  function formatNumeric(value, field) {
2051
2050
  if (!Number.isFinite(value)) return "";
2052
2051
  const precision = field.precision;
2053
- const text6 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
2054
- return field.unit ? `${text6}${field.unit}` : text6;
2052
+ const text8 = typeof precision === "number" && Number.isFinite(precision) ? value.toFixed(Math.max(0, Math.floor(precision))) : String(value);
2053
+ return field.unit ? `${text8}${field.unit}` : text8;
2055
2054
  }
2056
2055
  function usesLogScale(c) {
2057
2056
  return c.scale === "log" && typeof c.min === "number" && typeof c.max === "number" && c.min > 0 && c.max > c.min;
@@ -2385,44 +2384,44 @@ var InspectorPanelController = class {
2385
2384
  if (!field || !isFieldEditable(field)) return;
2386
2385
  const mixed = isFieldMixed(field);
2387
2386
  const value = this.getFieldValue(fieldId);
2388
- const text6 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2389
- this.drafts.set(fieldId, { text: text6, original: value });
2387
+ const text8 = mixed ? "" : field.type === "number" && typeof value === "number" ? formatNumeric(value, field) : value === void 0 || value === null ? "" : String(value);
2388
+ this.drafts.set(fieldId, { text: text8, original: value });
2390
2389
  this.focusedFieldId = fieldId;
2391
2390
  this.emitFieldFocus(fieldId);
2392
2391
  this.notify();
2393
2392
  }
2394
2393
  /** Update the draft text (typing). Emits nothing under the `blur` policy. */
2395
- setDraft(fieldId, text6) {
2394
+ setDraft(fieldId, text8) {
2396
2395
  const draft = this.drafts.get(fieldId);
2397
2396
  if (!draft) {
2398
- this.drafts.set(fieldId, { text: text6, original: this.getFieldValue(fieldId) });
2397
+ this.drafts.set(fieldId, { text: text8, original: this.getFieldValue(fieldId) });
2399
2398
  } else {
2400
- draft.text = text6;
2399
+ draft.text = text8;
2401
2400
  }
2402
2401
  const field = this.getField(fieldId);
2403
2402
  if (field && commitPolicyOf(field) === "live") {
2404
- const parsed = this.parseDraft(fieldId, text6);
2403
+ const parsed = this.parseDraft(fieldId, text8);
2405
2404
  if (parsed.ok) this.emitSet(fieldId, parsed.value);
2406
2405
  }
2407
2406
  this.notify();
2408
2407
  }
2409
2408
  /** Parse a draft string into a field-appropriate value. */
2410
- parseDraft(fieldId, text6) {
2409
+ parseDraft(fieldId, text8) {
2411
2410
  const field = this.getField(fieldId);
2412
2411
  if (!field) return { ok: false };
2413
2412
  if (field.type === "number") {
2414
- const parsed = parseNumeric(text6);
2413
+ const parsed = parseNumeric(text8);
2415
2414
  if (!parsed.ok) return { ok: false };
2416
2415
  return { ok: true, value: normalizeNumeric(parsed.value, constraintsOf(field)) };
2417
2416
  }
2418
2417
  if (field.type === "json") {
2419
2418
  try {
2420
- return { ok: true, value: JSON.parse(text6) };
2419
+ return { ok: true, value: JSON.parse(text8) };
2421
2420
  } catch {
2422
2421
  return { ok: false };
2423
2422
  }
2424
2423
  }
2425
- return { ok: true, value: text6 };
2424
+ return { ok: true, value: text8 };
2426
2425
  }
2427
2426
  /**
2428
2427
  * Commit the draft (Enter / blur). Invalid input reverts instead of committing garbage.
@@ -3348,7 +3347,7 @@ function NumberWidget({ field, controller, component }) {
3348
3347
  },
3349
3348
  [controller, editable, field.id, component]
3350
3349
  );
3351
- const text6 = component !== void 0 ? (() => {
3350
+ const text8 = component !== void 0 ? (() => {
3352
3351
  const raw = controller.getFieldValue(field.id);
3353
3352
  const v = Array.isArray(raw) ? raw[component] : void 0;
3354
3353
  return typeof v === "number" ? formatNumeric(v, field) : "";
@@ -3360,7 +3359,7 @@ function NumberWidget({ field, controller, component }) {
3360
3359
  Input2,
3361
3360
  {
3362
3361
  inputMode: "decimal",
3363
- value: text6,
3362
+ value: text8,
3364
3363
  placeholder: mixed ? controller.getMixedPlaceholder() : field.placeholder,
3365
3364
  mixed,
3366
3365
  readOnly: !editable,
@@ -3578,7 +3577,7 @@ function ActionGlyph({ glyph }) {
3578
3577
  return /* @__PURE__ */ jsx4("span", { "aria-hidden": true, children: glyph });
3579
3578
  }
3580
3579
  function SectionView({ section, controller }) {
3581
- const ui6 = (intent) => applyInspectorUi(controller, intent);
3580
+ const ui8 = (intent) => applyInspectorUi(controller, intent);
3582
3581
  const hasActions = section.actions.length > 0 || section.hasAddMenu;
3583
3582
  return /* @__PURE__ */ jsxs3(
3584
3583
  Section,
@@ -3587,13 +3586,13 @@ function SectionView({ section, controller }) {
3587
3586
  flush: true,
3588
3587
  collapsible: section.collapsible,
3589
3588
  collapsed: section.collapsed,
3590
- onCollapsedChange: () => ui6({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3589
+ onCollapsedChange: () => ui8({ kind: "toggleSectionCollapsed", sectionId: section.id }),
3591
3590
  enabled: section.enabled,
3592
- onEnabledChange: () => ui6({ kind: "toggleSectionEnabled", sectionId: section.id }),
3591
+ onEnabledChange: () => ui8({ kind: "toggleSectionEnabled", sectionId: section.id }),
3593
3592
  badge: section.badge ? /* @__PURE__ */ jsx4(Badge2, { children: section.badge }) : null,
3594
3593
  actions: hasActions ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
3595
- section.actions.map((action) => /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui6({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3596
- section.hasAddMenu ? /* @__PURE__ */ jsx4(Menu, { size: "sm", trigger: "\uFF0B", items: section.addItems, label: section.addLabel, align: "end", onSelect: (id) => ui6({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3594
+ section.actions.map((action) => /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui8({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3595
+ section.hasAddMenu ? /* @__PURE__ */ jsx4(Menu, { size: "sm", trigger: "\uFF0B", items: section.addItems, label: section.addLabel, align: "end", onSelect: (id) => ui8({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3597
3596
  ] }) : null,
3598
3597
  children: [
3599
3598
  section.showEmptyHint ? /* @__PURE__ */ jsx4(Stack2, { pad: "inset", children: /* @__PURE__ */ jsx4(Text3, { role: "caption", children: section.emptyHint }) }) : null,
@@ -3603,7 +3602,7 @@ function SectionView({ section, controller }) {
3603
3602
  );
3604
3603
  }
3605
3604
  function FieldRow({ row: row2, controller }) {
3606
- const ui6 = (intent) => applyInspectorUi(controller, intent);
3605
+ const ui8 = (intent) => applyInspectorUi(controller, intent);
3607
3606
  return /* @__PURE__ */ jsxs3(Fragment3, { children: [
3608
3607
  /* @__PURE__ */ jsxs3(
3609
3608
  Field,
@@ -3611,19 +3610,19 @@ function FieldRow({ row: row2, controller }) {
3611
3610
  label: row2.label,
3612
3611
  title: row2.title,
3613
3612
  focused: row2.focused,
3614
- onLabelClick: () => ui6({ kind: "focusField", fieldId: row2.id }),
3615
- onContextMenu: (x, y) => ui6({ kind: "contextMenu", fieldId: row2.id, x, y }),
3613
+ onLabelClick: () => ui8({ kind: "focusField", fieldId: row2.id }),
3614
+ onContextMenu: (x, y) => ui8({ kind: "contextMenu", fieldId: row2.id, x, y }),
3616
3615
  leading: row2.showKeyframes ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
3617
- /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(StopwatchIcon, {}), label: "Toggle animation", active: row2.animated, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: "toggleAnim" }) }),
3618
- row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PrevIcon, {}), label: "Previous keyframe", disabled: row2.prevDisabled, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: "prev" }) }) : null,
3619
- row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(DiamondIcon, { filled: row2.onKeyframe }), label: row2.keyframeLabel, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: row2.keyframeOp }) }) : null,
3620
- row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(NextIcon, {}), label: "Next keyframe", disabled: row2.nextDisabled, onClick: () => ui6({ kind: "keyframe", fieldId: row2.id, op: "next" }) }) : null
3616
+ /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(StopwatchIcon, {}), label: "Toggle animation", active: row2.animated, onClick: () => ui8({ kind: "keyframe", fieldId: row2.id, op: "toggleAnim" }) }),
3617
+ row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(PrevIcon, {}), label: "Previous keyframe", disabled: row2.prevDisabled, onClick: () => ui8({ kind: "keyframe", fieldId: row2.id, op: "prev" }) }) : null,
3618
+ row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(DiamondIcon, { filled: row2.onKeyframe }), label: row2.keyframeLabel, onClick: () => ui8({ kind: "keyframe", fieldId: row2.id, op: row2.keyframeOp }) }) : null,
3619
+ row2.animated ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(NextIcon, {}), label: "Next keyframe", disabled: row2.nextDisabled, onClick: () => ui8({ kind: "keyframe", fieldId: row2.id, op: "next" }) }) : null
3621
3620
  ] }) : null,
3622
3621
  trailing: /* @__PURE__ */ jsxs3(Fragment3, { children: [
3623
- row2.showEasing ? /* @__PURE__ */ jsx4(Select2, { size: "sm", options: row2.easingOptions, value: row2.easing, label: "Easing", onChange: (v) => ui6({ kind: "setEasing", fieldId: row2.id, easing: v }) }) : null,
3622
+ row2.showEasing ? /* @__PURE__ */ jsx4(Select2, { size: "sm", options: row2.easingOptions, value: row2.easing, label: "Easing", onChange: (v) => ui8({ kind: "setEasing", fieldId: row2.id, easing: v }) }) : null,
3624
3623
  row2.showKeyframes ? /* @__PURE__ */ jsx4(Badge2, { size: "sm", children: row2.keyframeChip }) : null,
3625
- row2.showToken ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(LinkIcon, {}), label: row2.tokenActionLabel, active: row2.bound, onClick: () => ui6({ kind: "bindToken", fieldId: row2.id }) }) : null,
3626
- row2.showReset ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ResetIcon, {}), label: "Reset to default", onClick: () => ui6({ kind: "resetField", fieldId: row2.id }) }) : null
3624
+ row2.showToken ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(LinkIcon, {}), label: row2.tokenActionLabel, active: row2.bound, onClick: () => ui8({ kind: "bindToken", fieldId: row2.id }) }) : null,
3625
+ row2.showReset ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ResetIcon, {}), label: "Reset to default", onClick: () => ui8({ kind: "resetField", fieldId: row2.id }) }) : null
3627
3626
  ] }),
3628
3627
  children: [
3629
3628
  row2.bound ? /* @__PURE__ */ jsx4(Badge2, { title: row2.tokenTitle, children: row2.tokenLabel }) : null,
@@ -4876,23 +4875,23 @@ function HistoryPanelView({ controller }) {
4876
4875
  const getSnapshot = useCallback4(() => controller.getRevision(), [controller]);
4877
4876
  useSyncExternalStore3(subscribe, getSnapshot, getSnapshot);
4878
4877
  const view = historyViewState(controller);
4879
- const ui6 = (intent) => applyHistoryUi(controller, intent);
4878
+ const ui8 = (intent) => applyHistoryUi(controller, intent);
4880
4879
  return /* @__PURE__ */ jsxs4(Column3, { className: "xeno-history-panel", children: [
4881
4880
  /* @__PURE__ */ jsx5(
4882
4881
  Toolbar2,
4883
4882
  {
4884
4883
  left: /* @__PURE__ */ jsxs4(Fragment4, { children: [
4885
- /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(UndoIcon, {}), label: "Undo", disabled: !view.canUndo, onClick: () => ui6({ kind: "undo" }) }),
4886
- /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(RedoIcon, {}), label: "Redo", disabled: !view.canRedo, onClick: () => ui6({ kind: "redo" }) })
4884
+ /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(UndoIcon, {}), label: "Undo", disabled: !view.canUndo, onClick: () => ui8({ kind: "undo" }) }),
4885
+ /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(RedoIcon, {}), label: "Redo", disabled: !view.canRedo, onClick: () => ui8({ kind: "redo" }) })
4887
4886
  ] }),
4888
4887
  right: /* @__PURE__ */ jsxs4(Fragment4, { children: [
4889
4888
  /* @__PURE__ */ jsx5(Text4, { role: "number", children: view.counter }),
4890
- view.clearAllowed ? /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(TrashIcon, {}), label: "Clear history", onClick: () => ui6({ kind: "requestClear" }) }) : null
4889
+ view.clearAllowed ? /* @__PURE__ */ jsx5(IconButton4, { size: "sm", icon: /* @__PURE__ */ jsx5(TrashIcon, {}), label: "Clear history", onClick: () => ui8({ kind: "requestClear" }) }) : null
4891
4890
  ] }),
4892
- children: view.searchShown ? /* @__PURE__ */ jsx5(SearchField3, { value: view.filter, onChange: (v) => ui6({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
4891
+ children: view.searchShown ? /* @__PURE__ */ jsx5(SearchField3, { value: view.filter, onChange: (v) => ui8({ kind: "filter", value: v }), placeholder: "Search history", label: "Search history" }) : null
4893
4892
  }
4894
4893
  ),
4895
- view.confirmingClear ? /* @__PURE__ */ jsx5(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui6({ kind: "confirmClear" }), onCancel: () => ui6({ kind: "cancelClear" }) }) : null,
4894
+ view.confirmingClear ? /* @__PURE__ */ jsx5(InlineConfirm, { message: "Clear all history?", label: "Confirm clear history", confirmLabel: "Clear", onConfirm: () => ui8({ kind: "confirmClear" }), onCancel: () => ui8({ kind: "cancelClear" }) }) : null,
4896
4895
  view.pruned ? /* @__PURE__ */ jsx5(Text4, { role: "caption", children: "Earlier history discarded (limit reached)" }) : null,
4897
4896
  /* @__PURE__ */ jsxs4(HistoryScroller, { controller, children: [
4898
4897
  /* @__PURE__ */ jsx5(RowList2, { role: "listbox", "aria-label": "History", children: /* @__PURE__ */ jsx5(VirtualWindow2, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map((row2) => /* @__PURE__ */ jsx5(HistoryRowView, { row: row2, controller }, row2.key)) }) }),
@@ -4951,7 +4950,7 @@ function HistoryCancelButton({ entryId, controller }) {
4951
4950
  );
4952
4951
  }
4953
4952
  function HistoryRowView({ row: row2, controller }) {
4954
- const ui6 = (intent) => applyHistoryUi(controller, intent);
4953
+ const ui8 = (intent) => applyHistoryUi(controller, intent);
4955
4954
  return /* @__PURE__ */ jsx5(
4956
4955
  Row2,
4957
4956
  {
@@ -4974,9 +4973,9 @@ function HistoryRowView({ row: row2, controller }) {
4974
4973
  row2.showTime ? /* @__PURE__ */ jsx5(Text4, { role: "meta", children: row2.time }) : null,
4975
4974
  row2.pending ? /* @__PURE__ */ jsx5(HistoryCancelButton, { entryId: row2.cancelId, controller }) : null
4976
4975
  ] }),
4977
- onClick: () => ui6({ kind: "jump", entryId: row2.entryId }),
4978
- onMouseEnter: () => ui6({ kind: "hover", entryId: row2.entryId }),
4979
- onContextMenuAt: (x, y) => ui6({ kind: "contextMenu", entryId: row2.entryId, x, y })
4976
+ onClick: () => ui8({ kind: "jump", entryId: row2.entryId }),
4977
+ onMouseEnter: () => ui8({ kind: "hover", entryId: row2.entryId }),
4978
+ onContextMenuAt: (x, y) => ui8({ kind: "contextMenu", entryId: row2.entryId, x, y })
4980
4979
  }
4981
4980
  );
4982
4981
  }
@@ -6262,27 +6261,27 @@ function ColorPanelView({ controller }) {
6262
6261
  const getSnapshot = useCallback5(() => controller.getRevision(), [controller]);
6263
6262
  useSyncExternalStore4(subscribe, getSnapshot, getSnapshot);
6264
6263
  const view = colorViewState(controller);
6265
- const ui6 = (intent) => applyColorUi(controller, intent);
6264
+ const ui8 = (intent) => applyColorUi(controller, intent);
6266
6265
  return /* @__PURE__ */ jsx6(Column4, { className: "xeno-color-panel", children: /* @__PURE__ */ jsxs5(Stack3, { gap: 8, children: [
6267
6266
  view.fgBg ? /* @__PURE__ */ jsxs5(Stack3, { direction: "row", pad: "none", children: [
6268
- /* @__PURE__ */ jsx6(ColorChip, { color: view.hex, label: "Foreground", pressed: view.fgActive, onClick: () => ui6({ kind: "setChannel", channel: "fg" }) }),
6269
- /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(SwapIcon, {}), label: "Swap FG/BG (X)", onClick: () => ui6({ kind: "swapChannels" }) }),
6270
- /* @__PURE__ */ jsx6(ColorChip, { color: null, label: "Background", pressed: view.bgActive, onClick: () => ui6({ kind: "setChannel", channel: "bg" }) })
6267
+ /* @__PURE__ */ jsx6(ColorChip, { color: view.hex, label: "Foreground", pressed: view.fgActive, onClick: () => ui8({ kind: "setChannel", channel: "fg" }) }),
6268
+ /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(SwapIcon, {}), label: "Swap FG/BG (X)", onClick: () => ui8({ kind: "swapChannels" }) }),
6269
+ /* @__PURE__ */ jsx6(ColorChip, { color: null, label: "Background", pressed: view.bgActive, onClick: () => ui8({ kind: "setChannel", channel: "bg" }) })
6271
6270
  ] }) : null,
6272
6271
  /* @__PURE__ */ jsx6(ColorPicker, { controller }),
6273
6272
  /* @__PURE__ */ jsxs5(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6274
- /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Color model", value: view.space, options: view.spaceOptions, onChange: (v) => ui6({ kind: "setSpace", space: v }) }),
6275
- view.isHex ? /* @__PURE__ */ jsx6(Input3, { variant: "mono", label: "Hex", value: view.hex, onChange: (v) => ui6({ kind: "setHex", value: v }), disabled: view.bound }) : null,
6276
- view.components.map((comp) => /* @__PURE__ */ jsx6(NumberInput, { caption: comp.label, value: comp.value, onChange: (n) => ui6({ kind: "setComponent", index: comp.index, value: n }), disabled: view.bound }, comp.key)),
6277
- view.alphaShown ? /* @__PURE__ */ jsx6(NumberInput, { caption: "A", label: "Alpha", min: 0, max: 100, value: view.alphaPercent, onChange: (n) => ui6({ kind: "setAlpha", percent: n }), disabled: view.bound }) : null,
6278
- /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(EyedropperIcon, {}), label: "Pick from screen", onClick: () => ui6({ kind: "eyedropper" }) })
6273
+ /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Color model", value: view.space, options: view.spaceOptions, onChange: (v) => ui8({ kind: "setSpace", space: v }) }),
6274
+ view.isHex ? /* @__PURE__ */ jsx6(Input3, { variant: "mono", label: "Hex", value: view.hex, onChange: (v) => ui8({ kind: "setHex", value: v }), disabled: view.bound }) : null,
6275
+ view.components.map((comp) => /* @__PURE__ */ jsx6(NumberInput, { caption: comp.label, value: comp.value, onChange: (n) => ui8({ kind: "setComponent", index: comp.index, value: n }), disabled: view.bound }, comp.key)),
6276
+ view.alphaShown ? /* @__PURE__ */ jsx6(NumberInput, { caption: "A", label: "Alpha", min: 0, max: 100, value: view.alphaPercent, onChange: (n) => ui8({ kind: "setAlpha", percent: n }), disabled: view.bound }) : null,
6277
+ /* @__PURE__ */ jsx6(IconButton5, { size: "sm", icon: /* @__PURE__ */ jsx6(EyedropperIcon, {}), label: "Pick from screen", onClick: () => ui8({ kind: "eyedropper" }) })
6279
6278
  ] }),
6280
6279
  view.hasTokens ? /* @__PURE__ */ jsx6(Field2, { label: "Token", htmlFor: "xeno-color-token", children: /* @__PURE__ */ jsxs5(Stack3, { direction: "row", pad: "none", gap: 4, children: [
6281
- /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Bind token", value: view.boundTokenId, placeholder: "\u2014 raw \u2014", options: view.tokenOptions, onChange: (id) => ui6({ kind: "bindToken", tokenId: id }) }),
6282
- view.bound ? /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui6({ kind: "unbindToken" }), children: "Unbind" }) : null
6280
+ /* @__PURE__ */ jsx6(Select3, { size: "sm", label: "Bind token", value: view.boundTokenId, placeholder: "\u2014 raw \u2014", options: view.tokenOptions, onChange: (id) => ui8({ kind: "bindToken", tokenId: id }) }),
6281
+ view.bound ? /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui8({ kind: "unbindToken" }), children: "Unbind" }) : null
6283
6282
  ] }) }) : null,
6284
- /* @__PURE__ */ jsx6(Section2, { title: "Swatches", flush: true, actions: /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui6({ kind: "addSwatch" }), children: "+ Save" }), children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Swatches", children: view.swatches.map((sw) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: sw.hex, label: sw.hex, onClick: () => ui6({ kind: "selectColor", hex: sw.hex }), onContextMenu: () => ui6({ kind: "removeSwatch", hex: sw.hex }) }, sw.key)) }) }),
6285
- view.hasRecent ? /* @__PURE__ */ jsx6(Section2, { title: "Recent", flush: true, children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Recent", children: view.recent.map((r) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: r.hex, label: r.hex, onClick: () => ui6({ kind: "selectColor", hex: r.hex }) }, r.key)) }) }) : null
6283
+ /* @__PURE__ */ jsx6(Section2, { title: "Swatches", flush: true, actions: /* @__PURE__ */ jsx6(TextButton2, { onClick: () => ui8({ kind: "addSwatch" }), children: "+ Save" }), children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Swatches", children: view.swatches.map((sw) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: sw.hex, label: sw.hex, onClick: () => ui8({ kind: "selectColor", hex: sw.hex }), onContextMenu: () => ui8({ kind: "removeSwatch", hex: sw.hex }) }, sw.key)) }) }),
6284
+ view.hasRecent ? /* @__PURE__ */ jsx6(Section2, { title: "Recent", flush: true, children: /* @__PURE__ */ jsx6(SwatchGrid, { label: "Recent", children: view.recent.map((r) => /* @__PURE__ */ jsx6(ColorChip, { size: "grid", color: r.hex, label: r.hex, onClick: () => ui8({ kind: "selectColor", hex: r.hex }) }, r.key)) }) }) : null
6286
6285
  ] }) });
6287
6286
  }
6288
6287
  function ColorPicker({ controller }) {
@@ -8944,11 +8943,11 @@ var FindController = class {
8944
8943
  * @param text - The new query text.
8945
8944
  * @param immediate - Skip the debounce (a paste, or pressing Enter).
8946
8945
  */
8947
- setText(text6, immediate = false) {
8948
- if (text6 === this.text) return;
8949
- this.text = text6;
8946
+ setText(text8, immediate = false) {
8947
+ if (text8 === this.text) return;
8948
+ this.text = text8;
8950
8949
  this.index = -1;
8951
- if (text6 === "") {
8950
+ if (text8 === "") {
8952
8951
  this.clearResults("idle");
8953
8952
  this.emitQuery(true);
8954
8953
  return;
@@ -8976,8 +8975,8 @@ var FindController = class {
8976
8975
  this.notify();
8977
8976
  }
8978
8977
  /** The replacement text. Never emitted on its own — a replacement is not a search. */
8979
- setReplaceText(text6) {
8980
- this.replaceText = text6;
8978
+ setReplaceText(text8) {
8979
+ this.replaceText = text8;
8981
8980
  this.notify();
8982
8981
  }
8983
8982
  /** Show or hide the replace row. */
@@ -9626,7 +9625,7 @@ function findRuntimeComponents(controller) {
9626
9625
  function FindPanelView({ controller, showResults, placeholder }) {
9627
9626
  useSyncExternalStore7(controller.subscribe, controller.getState, controller.getState);
9628
9627
  const view = findViewState(controller, { showResults, placeholder });
9629
- const ui6 = (intent) => applyFindUi(controller, intent);
9628
+ const ui8 = (intent) => applyFindUi(controller, intent);
9630
9629
  const renderRow = (row2) => /* @__PURE__ */ jsx9(
9631
9630
  Row3,
9632
9631
  {
@@ -9634,8 +9633,8 @@ function FindPanelView({ controller, showResults, placeholder }) {
9634
9633
  selected: row2.current,
9635
9634
  meta: row2.location,
9636
9635
  label: /* @__PURE__ */ jsx9(Highlight, { segments: row2.segments }),
9637
- onClick: () => ui6({ kind: "select", matchId: row2.id }),
9638
- onContextMenuAt: (x, y) => ui6({ kind: "contextMenu", matchId: row2.id, x, y })
9636
+ onClick: () => ui8({ kind: "select", matchId: row2.id }),
9637
+ onContextMenuAt: (x, y) => ui8({ kind: "contextMenu", matchId: row2.id, x, y })
9639
9638
  },
9640
9639
  row2.id
9641
9640
  );
@@ -9643,11 +9642,11 @@ function FindPanelView({ controller, showResults, placeholder }) {
9643
9642
  /* @__PURE__ */ jsx9(
9644
9643
  Toolbar4,
9645
9644
  {
9646
- left: /* @__PURE__ */ jsx9(SearchField4, { value: view.text, placeholder: view.placeholder, onChange: (value) => ui6({ kind: "text", value }) }),
9645
+ left: /* @__PURE__ */ jsx9(SearchField4, { value: view.text, placeholder: view.placeholder, onChange: (value) => ui8({ kind: "text", value }) }),
9647
9646
  right: /* @__PURE__ */ jsxs8(ToolbarGroup2, { end: true, children: [
9648
- /* @__PURE__ */ jsx9(IconButton6, { label: "Match case", active: view.caseSensitive, icon: /* @__PURE__ */ jsx9(CaseIcon, { size: 12 }), onClick: () => ui6({ kind: "option", option: "caseSensitive" }) }),
9649
- /* @__PURE__ */ jsx9(IconButton6, { label: "Whole word", active: view.wholeWord, icon: /* @__PURE__ */ jsx9(WordIcon, { size: 12 }), onClick: () => ui6({ kind: "option", option: "wholeWord" }) }),
9650
- /* @__PURE__ */ jsx9(IconButton6, { label: "Regular expression", active: view.regex, icon: /* @__PURE__ */ jsx9(RegexIcon, { size: 12 }), onClick: () => ui6({ kind: "option", option: "regex" }) })
9647
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Match case", active: view.caseSensitive, icon: /* @__PURE__ */ jsx9(CaseIcon, { size: 12 }), onClick: () => ui8({ kind: "option", option: "caseSensitive" }) }),
9648
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Whole word", active: view.wholeWord, icon: /* @__PURE__ */ jsx9(WordIcon, { size: 12 }), onClick: () => ui8({ kind: "option", option: "wholeWord" }) }),
9649
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Regular expression", active: view.regex, icon: /* @__PURE__ */ jsx9(RegexIcon, { size: 12 }), onClick: () => ui8({ kind: "option", option: "regex" }) })
9651
9650
  ] })
9652
9651
  }
9653
9652
  ),
@@ -9662,9 +9661,9 @@ function FindPanelView({ controller, showResults, placeholder }) {
9662
9661
  view.countShown ? /* @__PURE__ */ jsx9(Text5, { role: "number", children: view.counter }) : null
9663
9662
  ] }),
9664
9663
  right: /* @__PURE__ */ jsxs8(ToolbarGroup2, { end: true, children: [
9665
- /* @__PURE__ */ jsx9(IconButton6, { label: "Previous match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "up", size: 12 }), onClick: () => ui6({ kind: "previous" }) }),
9666
- /* @__PURE__ */ jsx9(IconButton6, { label: "Next match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "down", size: 12 }), onClick: () => ui6({ kind: "next" }) }),
9667
- /* @__PURE__ */ jsx9(TextButton3, { onClick: () => ui6({ kind: "toggleReplace" }), children: view.replaceLabel })
9664
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Previous match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "up", size: 12 }), onClick: () => ui8({ kind: "previous" }) }),
9665
+ /* @__PURE__ */ jsx9(IconButton6, { label: "Next match", disabled: !view.hasMatches, icon: /* @__PURE__ */ jsx9(Chevron, { direction: "down", size: 12 }), onClick: () => ui8({ kind: "next" }) }),
9666
+ /* @__PURE__ */ jsx9(TextButton3, { onClick: () => ui8({ kind: "toggleReplace" }), children: view.replaceLabel })
9668
9667
  ] })
9669
9668
  }
9670
9669
  ),
@@ -9672,10 +9671,10 @@ function FindPanelView({ controller, showResults, placeholder }) {
9672
9671
  view.replaceVisible ? /* @__PURE__ */ jsx9(
9673
9672
  Toolbar4,
9674
9673
  {
9675
- left: /* @__PURE__ */ jsx9(SearchField4, { value: view.replaceText, placeholder: "Replace with", onChange: (value) => ui6({ kind: "replaceText", value }) }),
9674
+ left: /* @__PURE__ */ jsx9(SearchField4, { value: view.replaceText, placeholder: "Replace with", onChange: (value) => ui8({ kind: "replaceText", value }) }),
9676
9675
  right: /* @__PURE__ */ jsxs8(ToolbarGroup2, { end: true, children: [
9677
- /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui6({ kind: "replaceCurrent" }), children: "Replace" }),
9678
- /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui6({ kind: "replaceAll" }), children: view.allLabel })
9676
+ /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui8({ kind: "replaceCurrent" }), children: "Replace" }),
9677
+ /* @__PURE__ */ jsx9(TextButton3, { disabled: !view.canReplace, onClick: () => ui8({ kind: "replaceAll" }), children: view.allLabel })
9679
9678
  ] })
9680
9679
  }
9681
9680
  ) : null,
@@ -10333,279 +10332,290 @@ var commentsManifest = {
10333
10332
  import { useSyncExternalStore as useSyncExternalStore8 } from "react";
10334
10333
  import {
10335
10334
  Badge as Badge6,
10335
+ Column as Column6,
10336
10336
  EmptyState as EmptyState6,
10337
+ Input as Input4,
10338
+ Message,
10339
+ Note,
10340
+ Quote,
10337
10341
  ScrollArea as ScrollArea5,
10338
10342
  Section as Section4,
10339
10343
  SegmentedControl as SegmentedControl2,
10344
+ Stack as Stack4,
10340
10345
  StatusBar as StatusBar6,
10346
+ Text as Text6,
10347
+ TextArea as TextArea2,
10341
10348
  TextButton as TextButton4,
10349
+ ThreadCard,
10342
10350
  Toolbar as Toolbar5,
10343
10351
  ToolbarGroup as ToolbarGroup3
10344
10352
  } from "@xenosystem/components/chrome";
10345
- import { Fragment as Fragment7, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
10353
+
10354
+ // src/edit/comments/declaration.ts
10346
10355
  var ANCHOR_BADGE = {
10347
10356
  live: null,
10348
- stale: {
10349
- label: "edited",
10350
- tone: "warning",
10351
- title: "The content this comment is about has changed since it was written."
10352
- },
10353
- orphaned: {
10354
- label: "content deleted",
10355
- tone: "error",
10356
- title: "The content this comment was about no longer exists. The comment is kept."
10357
+ stale: { label: "edited", tone: "warning", title: "The content this comment is about has changed since it was written." },
10358
+ orphaned: { label: "content deleted", tone: "error", title: "The content this comment was about no longer exists. The comment is kept." }
10359
+ };
10360
+ function commentsViewState(c, options = {}) {
10361
+ const s = c.getState();
10362
+ const now = c.clock();
10363
+ const showResolved = options.showResolved ?? true;
10364
+ const allowReactions = options.allowReactions ?? false;
10365
+ const me = s.currentUser?.id ?? "";
10366
+ const thread = (v) => {
10367
+ const t = v.thread;
10368
+ const badge = ANCHOR_BADGE[v.anchorState];
10369
+ const replying = s.replyingTo === t.id;
10370
+ return {
10371
+ id: t.id,
10372
+ active: v.active,
10373
+ resolved: t.resolved === true,
10374
+ hasAnchorBadge: badge !== null,
10375
+ anchorLabel: badge?.label,
10376
+ anchorTone: badge?.tone ?? "neutral",
10377
+ anchorTitle: badge?.title,
10378
+ age: formatAge(t.createdAt, now),
10379
+ hasQuote: !!t.anchorLabel,
10380
+ quote: t.anchorLabel,
10381
+ orphaned: v.anchorState === "orphaned",
10382
+ messages: v.messages.map((m) => ({ id: m.id, author: m.authorName ?? m.authorId, age: formatAge(m.createdAt, now), edited: !!m.editedAt, body: m.body })),
10383
+ hasReactions: allowReactions && !!t.reactions?.length,
10384
+ reactions: allowReactions ? (t.reactions ?? []).map((r) => ({ threadId: t.id, emoji: r.emoji, label: `${r.emoji} ${r.userIds.length}`, mine: r.userIds.includes(me), reactTo: !r.userIds.includes(me) })) : [],
10385
+ replying,
10386
+ replyDraft: replying ? s.replyDraft : "",
10387
+ idle: !replying,
10388
+ resolveLabel: t.resolved ? "Reopen" : "Resolve",
10389
+ resolveTo: !t.resolved
10390
+ };
10391
+ };
10392
+ const open = s.threads.filter((v) => !v.thread.resolved).map(thread);
10393
+ const resolved = s.threads.filter((v) => v.thread.resolved).map(thread);
10394
+ const o = s.totals.orphaned;
10395
+ return {
10396
+ show: s.filter.show ?? "open",
10397
+ shows: [{ value: "open", label: "Open" }, { value: "resolved", label: "Resolved" }, { value: "all", label: "All" }],
10398
+ sort: s.sort,
10399
+ sorts: [{ value: "document", label: "Doc" }, { value: "newest", label: "New" }, { value: "oldest", label: "Old" }],
10400
+ orphanedNotice: o > 0 ? `${o} comment${o === 1 ? "" : "s"} lost the content${o === 1 ? " it was" : " they were"} about.` : void 0,
10401
+ orphanedHidden: o > 0 && s.filter.includeOrphaned === false,
10402
+ filteredEmpty: s.threads.length === 0 && s.filteredEmpty,
10403
+ filteredHint: `${s.totals.all} comment${s.totals.all === 1 ? "" : "s"} on this document.`,
10404
+ empty: s.threads.length === 0 && !s.filteredEmpty,
10405
+ emptyHint: options.emptyHint ?? "Select something and add a comment.",
10406
+ listed: s.threads.length > 0,
10407
+ open,
10408
+ hasResolved: showResolved && resolved.length > 0,
10409
+ resolvedTitle: `Resolved (${resolved.length})`,
10410
+ resolved: showResolved ? resolved : [],
10411
+ draft: s.draft,
10412
+ placeholder: s.activeAnchor === null ? "Select something in the document to comment on it" : "Add a comment\u2026",
10413
+ composerDisabled: s.activeAnchor === null,
10414
+ commentDisabled: s.activeAnchor === null || s.draft.trim() === "",
10415
+ countLabel: `${s.totals.open} open`,
10416
+ staleLabel: s.totals.stale > 0 ? `${s.totals.stale} edited` : void 0
10417
+ };
10418
+ }
10419
+ function applyCommentsUi(c, intent) {
10420
+ switch (intent.kind) {
10421
+ case "show":
10422
+ c.setFilter({ show: intent.show });
10423
+ break;
10424
+ case "sort":
10425
+ c.setSort(intent.sort);
10426
+ break;
10427
+ case "showOrphaned":
10428
+ c.setFilter({ includeOrphaned: true });
10429
+ break;
10430
+ case "activate":
10431
+ c.activate(intent.id);
10432
+ break;
10433
+ case "contextMenu":
10434
+ c.requestContextMenu(intent.id, intent.x, intent.y);
10435
+ break;
10436
+ case "react":
10437
+ c.react(intent.id, intent.emoji, intent.on);
10438
+ break;
10439
+ case "reply":
10440
+ c.reply(intent.id);
10441
+ break;
10442
+ case "replyDraft":
10443
+ c.setReplyDraft(intent.value);
10444
+ break;
10445
+ case "replyKey":
10446
+ if (intent.key === "Enter" && !intent.shift) c.reply(intent.id);
10447
+ else if (intent.key === "Escape") c.setReplyingTo(null);
10448
+ break;
10449
+ case "startReply":
10450
+ c.setReplyingTo(intent.id);
10451
+ break;
10452
+ case "resolve":
10453
+ c.resolve(intent.id, intent.resolved);
10454
+ break;
10455
+ case "remove":
10456
+ c.remove(intent.id);
10457
+ break;
10458
+ case "draft":
10459
+ c.setDraft(intent.value);
10460
+ break;
10461
+ case "draftKey":
10462
+ if (intent.key === "Enter" && (intent.ctrl || intent.meta)) c.create();
10463
+ break;
10464
+ case "create":
10465
+ c.create();
10466
+ break;
10357
10467
  }
10468
+ }
10469
+ var commentsViewManifest = {
10470
+ id: commentsManifest.id,
10471
+ version: "1.0.0",
10472
+ title: "Comments",
10473
+ category: "edit",
10474
+ layout: { slot: "side" },
10475
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The CommentsViewModel the runtime derives from the controller on every draw." }],
10476
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, \u2026 } \u2014 applied by the runtime to the controller." }],
10477
+ commands: []
10358
10478
  };
10359
- function ThreadCard({
10360
- view,
10361
- controller,
10362
- allowReactions,
10363
- now
10364
- }) {
10365
- const { thread, anchorState } = view;
10366
- const badge = ANCHOR_BADGE[anchorState];
10367
- const replying = controller.getState().replyingTo === thread.id;
10368
- const orphaned = anchorState === "orphaned";
10369
- return /* @__PURE__ */ jsx10(
10370
- "div",
10479
+ var EMPTY6 = { show: "open", shows: [], sort: "document", sorts: [], orphanedHidden: false, filteredEmpty: false, filteredHint: "", empty: true, emptyHint: "Select something and add a comment.", listed: false, open: [], hasResolved: false, resolvedTitle: "", resolved: [], draft: "", placeholder: "", composerDisabled: true, commentDisabled: true, countLabel: "0 open" };
10480
+ var BEHAVIOURS6 = {
10481
+ "edit/commentsPanelView": (inputs) => inputs.view ?? EMPTY6
10482
+ };
10483
+ var COMMENTS_RUNTIME_UNITS = [];
10484
+ var text6 = (b) => ({ component: "text", props: { text: b } });
10485
+ var ui6 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
10486
+ var threadCard = (input) => ({
10487
+ component: "chrome/ThreadCard",
10488
+ each: { input, key: "id" },
10489
+ props: { active: { item: "active" }, resolved: { item: "resolved" } },
10490
+ on: { onContextMenuAt: ui6("contextMenu", { id: { item: "id" }, x: { arg: "", index: 0 }, y: { arg: "", index: 1 } }) },
10491
+ children: [
10492
+ {
10493
+ component: "chrome/Stack",
10494
+ props: { direction: { value: "row" }, gap: { value: 4 }, pad: { value: "none" } },
10495
+ children: [
10496
+ { component: "chrome/Badge", when: { item: "hasAnchorBadge" }, props: { tone: { item: "anchorTone" }, title: { item: "anchorTitle" } }, children: [text6({ item: "anchorLabel" })] },
10497
+ { component: "chrome/Badge", when: { item: "resolved" }, children: [text6({ value: "resolved" })] },
10498
+ { component: "chrome/Text", props: { role: { value: "caption" } }, children: [text6({ item: "age" })] }
10499
+ ]
10500
+ },
10501
+ { component: "chrome/Quote", when: { item: "hasQuote" }, props: { orphaned: { item: "orphaned" } }, on: { onActivate: ui6("activate", { id: { item: "id" } }) }, children: [text6({ item: "quote" })] },
10502
+ { component: "chrome/Message", each: { item: "messages", key: "id" }, props: { author: { item: "author" }, age: { item: "age" }, edited: { item: "edited" } }, children: [text6({ item: "body" })] },
10371
10503
  {
10372
- style: {
10373
- borderBottom: "1px solid var(--xeno-border-subtle)",
10374
- // Resolved dims. Orphaned does NOT — a comment whose content was deleted is the one most
10375
- // likely to matter, and dimming it would finish the job the deletion started.
10376
- opacity: thread.resolved ? 0.55 : 1,
10377
- background: view.active ? "var(--xeno-border-subtle)" : void 0
10504
+ component: "chrome/Stack",
10505
+ when: { item: "hasReactions" },
10506
+ props: { direction: { value: "row" }, gap: { value: 4 }, pad: { value: "none" } },
10507
+ children: [{ component: "chrome/TextButton", each: { item: "reactions", key: "emoji" }, on: { onClick: ui6("react", { id: { item: "threadId" }, emoji: { item: "emoji" }, on: { item: "reactTo" } }) }, children: [text6({ item: "label" })] }]
10508
+ },
10509
+ {
10510
+ component: "chrome/Stack",
10511
+ when: { item: "replying" },
10512
+ props: { direction: { value: "row" }, gap: { value: 4 }, pad: { value: "none" } },
10513
+ children: [
10514
+ { component: "chrome/Input", props: { value: { item: "replyDraft" }, placeholder: { value: "Reply" }, autoFocus: { value: true }, label: { value: "Reply" } }, on: { onChange: ui6("replyDraft", { value: { arg: "" } }), onKeyDown: ui6("replyKey", { id: { item: "id" }, key: { arg: "key" }, shift: { arg: "shiftKey" } }) } },
10515
+ { component: "chrome/TextButton", on: { onClick: ui6("reply", { id: { item: "id" } }) }, children: [text6({ value: "Send" })] }
10516
+ ]
10517
+ },
10518
+ {
10519
+ component: "chrome/Stack",
10520
+ when: { item: "idle" },
10521
+ props: { direction: { value: "row" }, gap: { value: 6 }, pad: { value: "none" } },
10522
+ children: [
10523
+ { component: "chrome/TextButton", on: { onClick: ui6("startReply", { id: { item: "id" } }) }, children: [text6({ value: "Reply" })] },
10524
+ { component: "chrome/TextButton", on: { onClick: ui6("resolve", { id: { item: "id" }, resolved: { item: "resolveTo" } }) }, children: [text6({ item: "resolveLabel" })] },
10525
+ { component: "chrome/TextButton", on: { onClick: ui6("remove", { id: { item: "id" } }) }, children: [text6({ value: "Delete" })] }
10526
+ ]
10527
+ }
10528
+ ]
10529
+ });
10530
+ var COMMENTS_DECLARATION = {
10531
+ format: "xeno.block@1",
10532
+ manifest: commentsViewManifest,
10533
+ behaviour: "edit/commentsPanelView",
10534
+ composition: {
10535
+ component: "chrome/Column",
10536
+ children: [
10537
+ {
10538
+ component: "chrome/Toolbar",
10539
+ props: {
10540
+ left: { node: { component: "chrome/SegmentedControl", props: { label: { value: "Show" }, options: { input: "view.shows" }, value: { input: "view.show" } }, on: { onChange: ui6("show", { show: { arg: "" } }) } } },
10541
+ right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/SegmentedControl", props: { label: { value: "Sort" }, options: { input: "view.sorts" }, value: { input: "view.sort" } }, on: { onChange: ui6("sort", { sort: { arg: "" } }) } }] } }
10542
+ }
10378
10543
  },
10379
- onContextMenu: (event) => {
10380
- event.preventDefault();
10381
- controller.requestContextMenu(thread.id, event.clientX, event.clientY);
10544
+ {
10545
+ component: "chrome/Note",
10546
+ when: { input: "view.orphanedNotice" },
10547
+ children: [text6({ input: "view.orphanedNotice" }), { component: "chrome/TextButton", when: { input: "view.orphanedHidden" }, on: { onClick: ui6("showOrphaned") }, children: [text6({ value: "Show" })] }]
10382
10548
  },
10383
- children: /* @__PURE__ */ jsxs9("div", { style: { padding: "6px 8px", display: "flex", flexDirection: "column", gap: 4 }, children: [
10384
- /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
10385
- badge ? /* @__PURE__ */ jsx10(Badge6, { tone: badge.tone, title: badge.title, children: badge.label }) : null,
10386
- thread.resolved ? /* @__PURE__ */ jsx10(Badge6, { children: "resolved" }) : null,
10387
- /* @__PURE__ */ jsx10("span", { style: { marginLeft: "auto", fontSize: 9, opacity: 0.4 }, children: formatAge(thread.createdAt, now) })
10388
- ] }),
10389
- thread.anchorLabel ? /* @__PURE__ */ jsx10(
10390
- "button",
10391
- {
10392
- type: "button",
10393
- onClick: () => controller.activate(thread.id),
10394
- style: {
10395
- textAlign: "left",
10396
- fontSize: 10,
10397
- opacity: orphaned ? 0.4 : 0.6,
10398
- borderLeft: "2px solid var(--xeno-control)",
10399
- paddingLeft: 6,
10400
- textDecoration: orphaned ? "line-through" : void 0,
10401
- background: "none",
10402
- border: "none",
10403
- borderLeftWidth: 2,
10404
- borderLeftStyle: "solid",
10405
- borderLeftColor: "var(--xeno-control)",
10406
- cursor: "pointer",
10407
- font: "inherit"
10408
- },
10409
- children: thread.anchorLabel
10410
- }
10411
- ) : null,
10412
- view.messages.map((message) => /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", gap: 1 }, children: [
10413
- /* @__PURE__ */ jsxs9("div", { style: { display: "flex", alignItems: "baseline", gap: 4 }, children: [
10414
- /* @__PURE__ */ jsx10("span", { style: { fontSize: 10, fontWeight: 600 }, children: message.authorName ?? message.authorId }),
10415
- /* @__PURE__ */ jsx10("span", { style: { fontSize: 9, opacity: 0.35 }, children: formatAge(message.createdAt, now) }),
10416
- message.editedAt ? /* @__PURE__ */ jsx10("span", { style: { fontSize: 9, opacity: 0.3 }, children: "edited" }) : null
10417
- ] }),
10418
- /* @__PURE__ */ jsx10("p", { style: { fontSize: 11, opacity: 0.75, margin: 0, whiteSpace: "pre-wrap" }, children: message.body })
10419
- ] }, message.id)),
10420
- allowReactions && thread.reactions?.length ? /* @__PURE__ */ jsx10("div", { style: { display: "flex", gap: 4 }, children: thread.reactions.map((reaction) => /* @__PURE__ */ jsxs9(
10421
- TextButton4,
10422
- {
10423
- onClick: () => controller.react(
10424
- thread.id,
10425
- reaction.emoji,
10426
- !reaction.userIds.includes(controller.getState().currentUser?.id ?? "")
10427
- ),
10428
- children: [
10429
- reaction.emoji,
10430
- " ",
10431
- reaction.userIds.length
10432
- ]
10433
- },
10434
- reaction.emoji
10435
- )) }) : null,
10436
- replying ? /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 4 }, children: [
10437
- /* @__PURE__ */ jsx10(
10438
- "input",
10439
- {
10440
- value: controller.getState().replyDraft,
10441
- onChange: (event) => controller.setReplyDraft(event.target.value),
10442
- onKeyDown: (event) => {
10443
- if (event.key === "Enter" && !event.shiftKey) {
10444
- event.preventDefault();
10445
- controller.reply(thread.id);
10446
- } else if (event.key === "Escape") {
10447
- controller.setReplyingTo(null);
10448
- }
10449
- },
10450
- placeholder: "Reply",
10451
- autoFocus: true,
10452
- style: {
10453
- flex: 1,
10454
- fontSize: 10,
10455
- background: "var(--xeno-canvas)",
10456
- border: "1px solid var(--xeno-border)",
10457
- borderRadius: 2,
10458
- padding: "2px 4px",
10459
- color: "inherit",
10460
- outline: "none"
10461
- }
10462
- }
10463
- ),
10464
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.reply(thread.id), children: "Send" })
10465
- ] }) : /* @__PURE__ */ jsxs9("div", { style: { display: "flex", gap: 6 }, children: [
10466
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.setReplyingTo(thread.id), children: "Reply" }),
10467
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.resolve(thread.id, !thread.resolved), children: thread.resolved ? "Reopen" : "Resolve" }),
10468
- /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.remove(thread.id), children: "Delete" })
10469
- ] })
10470
- ] })
10471
- }
10472
- );
10473
- }
10474
- function CommentsPanelView({
10475
- controller,
10476
- showResolved = true,
10477
- allowReactions = false,
10478
- emptyHint
10479
- }) {
10480
- const state = useSyncExternalStore8(controller.subscribe, controller.getState, controller.getState);
10481
- const now = controller.clock();
10482
- const open = state.threads.filter((t) => !t.thread.resolved);
10483
- const resolved = state.threads.filter((t) => t.thread.resolved);
10484
- return /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
10549
+ {
10550
+ component: "chrome/ScrollArea",
10551
+ children: [
10552
+ { component: "chrome/EmptyState", when: { input: "view.filteredEmpty" }, props: { title: { value: "Nothing matches this filter" }, hint: { input: "view.filteredHint" } } },
10553
+ { component: "chrome/EmptyState", when: { input: "view.empty" }, props: { title: { value: "No comments" }, hint: { input: "view.emptyHint" } } },
10554
+ threadCard("view.open"),
10555
+ { component: "chrome/Section", when: { input: "view.hasResolved" }, props: { title: { input: "view.resolvedTitle" }, flush: { value: true } }, children: [threadCard("view.resolved")] }
10556
+ ]
10557
+ },
10558
+ {
10559
+ component: "chrome/Stack",
10560
+ props: { gap: { value: 2 }, pad: { value: "gutter" }, className: { value: "xeno-comments-composer" } },
10561
+ children: [
10562
+ { component: "chrome/TextArea", props: { value: { input: "view.draft" }, placeholder: { input: "view.placeholder" }, disabled: { input: "view.composerDisabled" }, rows: { value: 2 }, label: { value: "New comment" } }, on: { onChange: ui6("draft", { value: { arg: "" } }), onKeyDown: ui6("draftKey", { key: { arg: "key" }, ctrl: { arg: "ctrlKey" }, meta: { arg: "metaKey" } }) } },
10563
+ { component: "chrome/Toolbar", props: { divided: { value: false }, right: { node: { component: "chrome/ToolbarGroup", props: { end: { value: true } }, children: [{ component: "chrome/TextButton", props: { strong: { value: true }, disabled: { input: "view.commentDisabled" } }, on: { onClick: ui6("create") }, children: [text6({ value: "Comment" })] }] } } } }
10564
+ ]
10565
+ },
10566
+ { component: "chrome/StatusBar", props: { left: { input: "view.countLabel" }, right: { input: "view.staleLabel" } } }
10567
+ ]
10568
+ }
10569
+ };
10570
+
10571
+ // src/edit/comments/react/CommentsPanelView.tsx
10572
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
10573
+ function CommentsPanelView({ controller, ...options }) {
10574
+ useSyncExternalStore8(controller.subscribe, controller.getState, controller.getState);
10575
+ const view = commentsViewState(controller, options);
10576
+ const ui8 = (intent) => applyCommentsUi(controller, intent);
10577
+ const card = (t) => /* @__PURE__ */ jsxs9(ThreadCard, { active: t.active, resolved: t.resolved, onContextMenuAt: (x, y) => ui8({ kind: "contextMenu", id: t.id, x, y }), children: [
10578
+ /* @__PURE__ */ jsxs9(Stack4, { direction: "row", gap: 4, pad: "none", children: [
10579
+ t.hasAnchorBadge ? /* @__PURE__ */ jsx10(Badge6, { tone: t.anchorTone, title: t.anchorTitle, children: t.anchorLabel }) : null,
10580
+ t.resolved ? /* @__PURE__ */ jsx10(Badge6, { children: "resolved" }) : null,
10581
+ /* @__PURE__ */ jsx10(Text6, { role: "caption", children: t.age })
10582
+ ] }),
10583
+ t.hasQuote ? /* @__PURE__ */ jsx10(Quote, { orphaned: t.orphaned, onActivate: () => ui8({ kind: "activate", id: t.id }), children: t.quote }) : null,
10584
+ t.messages.map((m) => /* @__PURE__ */ jsx10(Message, { author: m.author, age: m.age, edited: m.edited, children: m.body }, m.id)),
10585
+ t.hasReactions ? /* @__PURE__ */ jsx10(Stack4, { direction: "row", gap: 4, pad: "none", children: t.reactions.map((r) => /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui8({ kind: "react", id: r.threadId, emoji: r.emoji, on: r.reactTo }), children: r.label }, r.emoji)) }) : null,
10586
+ t.replying ? /* @__PURE__ */ jsxs9(Stack4, { direction: "row", gap: 4, pad: "none", children: [
10587
+ /* @__PURE__ */ jsx10(Input4, { value: t.replyDraft, placeholder: "Reply", autoFocus: true, label: "Reply", onChange: (value) => ui8({ kind: "replyDraft", value }), onKeyDown: (e) => ui8({ kind: "replyKey", id: t.id, key: e.key, shift: e.shiftKey }) }),
10588
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui8({ kind: "reply", id: t.id }), children: "Send" })
10589
+ ] }) : null,
10590
+ t.idle ? /* @__PURE__ */ jsxs9(Stack4, { direction: "row", gap: 6, pad: "none", children: [
10591
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui8({ kind: "startReply", id: t.id }), children: "Reply" }),
10592
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui8({ kind: "resolve", id: t.id, resolved: t.resolveTo }), children: t.resolveLabel }),
10593
+ /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui8({ kind: "remove", id: t.id }), children: "Delete" })
10594
+ ] }) : null
10595
+ ] }, t.id);
10596
+ return /* @__PURE__ */ jsxs9(Column6, { children: [
10485
10597
  /* @__PURE__ */ jsx10(
10486
10598
  Toolbar5,
10487
10599
  {
10488
- left: /* @__PURE__ */ jsx10(
10489
- SegmentedControl2,
10490
- {
10491
- label: "Show",
10492
- value: state.filter.show ?? "open",
10493
- onChange: (value) => controller.setFilter({ show: value }),
10494
- options: [
10495
- { label: "Open", value: "open" },
10496
- { label: "Resolved", value: "resolved" },
10497
- { label: "All", value: "all" }
10498
- ]
10499
- }
10500
- ),
10501
- right: /* @__PURE__ */ jsx10(ToolbarGroup3, { end: true, children: /* @__PURE__ */ jsx10(
10502
- SegmentedControl2,
10503
- {
10504
- label: "Sort",
10505
- value: state.sort,
10506
- onChange: (value) => controller.setSort(value),
10507
- options: [
10508
- { label: "Doc", value: "document" },
10509
- { label: "New", value: "newest" },
10510
- { label: "Old", value: "oldest" }
10511
- ]
10512
- }
10513
- ) })
10600
+ left: /* @__PURE__ */ jsx10(SegmentedControl2, { label: "Show", options: view.shows, value: view.show, onChange: (show) => ui8({ kind: "show", show }) }),
10601
+ right: /* @__PURE__ */ jsx10(ToolbarGroup3, { end: true, children: /* @__PURE__ */ jsx10(SegmentedControl2, { label: "Sort", options: view.sorts, value: view.sort, onChange: (sort) => ui8({ kind: "sort", sort }) }) })
10514
10602
  }
10515
10603
  ),
10516
- state.totals.orphaned > 0 ? /* @__PURE__ */ jsxs9("div", { style: { padding: "2px 8px", fontSize: 9, opacity: 0.6 }, children: [
10517
- state.totals.orphaned,
10518
- " comment",
10519
- state.totals.orphaned === 1 ? "" : "s",
10520
- " lost the content",
10521
- state.totals.orphaned === 1 ? " it was" : " they were",
10522
- " about.",
10523
- state.filter.includeOrphaned === false ? /* @__PURE__ */ jsx10(TextButton4, { onClick: () => controller.setFilter({ includeOrphaned: true }), children: "Show" }) : null
10604
+ view.orphanedNotice ? /* @__PURE__ */ jsxs9(Note, { children: [
10605
+ view.orphanedNotice,
10606
+ view.orphanedHidden ? /* @__PURE__ */ jsx10(TextButton4, { onClick: () => ui8({ kind: "showOrphaned" }), children: "Show" }) : null
10524
10607
  ] }) : null,
10525
- /* @__PURE__ */ jsx10(ScrollArea5, { children: state.threads.length === 0 ? state.filteredEmpty ? (
10526
- // NOT "no comments". There are comments; this filter hides them, and telling a
10527
- // reviewer their feedback is gone is worse than telling them nothing.
10528
- /* @__PURE__ */ jsx10(
10529
- EmptyState6,
10530
- {
10531
- title: "Nothing matches this filter",
10532
- hint: `${state.totals.all} comment${state.totals.all === 1 ? "" : "s"} on this document.`
10533
- }
10534
- )
10535
- ) : /* @__PURE__ */ jsx10(
10536
- EmptyState6,
10537
- {
10538
- title: "No comments",
10539
- hint: emptyHint ?? "Select something and add a comment."
10540
- }
10541
- ) : /* @__PURE__ */ jsxs9(Fragment7, { children: [
10542
- open.map((view) => /* @__PURE__ */ jsx10(
10543
- ThreadCard,
10544
- {
10545
- view,
10546
- controller,
10547
- allowReactions,
10548
- now
10549
- },
10550
- view.thread.id
10551
- )),
10552
- showResolved && resolved.length > 0 ? /* @__PURE__ */ jsx10(Section4, { title: `Resolved (${resolved.length})`, flush: true, children: resolved.map((view) => /* @__PURE__ */ jsx10(
10553
- ThreadCard,
10554
- {
10555
- view,
10556
- controller,
10557
- allowReactions,
10558
- now
10559
- },
10560
- view.thread.id
10561
- )) }) : null
10562
- ] }) }),
10563
- /* @__PURE__ */ jsxs9("div", { style: { borderTop: "1px solid var(--xeno-border-subtle)", padding: 6 }, children: [
10564
- /* @__PURE__ */ jsx10(
10565
- "textarea",
10566
- {
10567
- value: state.draft,
10568
- onChange: (event) => controller.setDraft(event.target.value),
10569
- onKeyDown: (event) => {
10570
- if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
10571
- event.preventDefault();
10572
- controller.create();
10573
- }
10574
- },
10575
- placeholder: state.activeAnchor === null ? "Select something in the document to comment on it" : "Add a comment\u2026",
10576
- disabled: state.activeAnchor === null,
10577
- rows: 2,
10578
- style: {
10579
- width: "100%",
10580
- fontSize: 11,
10581
- background: "var(--xeno-canvas)",
10582
- border: "1px solid var(--xeno-border)",
10583
- borderRadius: 2,
10584
- padding: 4,
10585
- color: "inherit",
10586
- outline: "none",
10587
- resize: "none",
10588
- opacity: state.activeAnchor === null ? 0.5 : 1
10589
- }
10590
- }
10591
- ),
10592
- /* @__PURE__ */ jsx10("div", { style: { display: "flex", justifyContent: "flex-end", marginTop: 2 }, children: /* @__PURE__ */ jsx10(
10593
- TextButton4,
10594
- {
10595
- strong: true,
10596
- disabled: state.activeAnchor === null || state.draft.trim() === "",
10597
- onClick: () => controller.create(),
10598
- children: "Comment"
10599
- }
10600
- ) })
10608
+ /* @__PURE__ */ jsxs9(ScrollArea5, { children: [
10609
+ view.filteredEmpty ? /* @__PURE__ */ jsx10(EmptyState6, { title: "Nothing matches this filter", hint: view.filteredHint }) : null,
10610
+ view.empty ? /* @__PURE__ */ jsx10(EmptyState6, { title: "No comments", hint: view.emptyHint }) : null,
10611
+ view.open.map(card),
10612
+ view.hasResolved ? /* @__PURE__ */ jsx10(Section4, { title: view.resolvedTitle, flush: true, children: view.resolved.map(card) }) : null
10601
10613
  ] }),
10602
- /* @__PURE__ */ jsx10(
10603
- StatusBar6,
10604
- {
10605
- left: `${state.totals.open} open`,
10606
- right: state.totals.stale > 0 ? `${state.totals.stale} edited` : void 0
10607
- }
10608
- )
10614
+ /* @__PURE__ */ jsxs9(Stack4, { gap: 2, pad: "gutter", className: "xeno-comments-composer", children: [
10615
+ /* @__PURE__ */ jsx10(TextArea2, { value: view.draft, placeholder: view.placeholder, disabled: view.composerDisabled, rows: 2, label: "New comment", onChange: (value) => ui8({ kind: "draft", value }), onKeyDown: (e) => ui8({ kind: "draftKey", key: e.key, ctrl: e.ctrlKey, meta: e.metaKey }) }),
10616
+ /* @__PURE__ */ jsx10(Toolbar5, { divided: false, right: /* @__PURE__ */ jsx10(ToolbarGroup3, { end: true, children: /* @__PURE__ */ jsx10(TextButton4, { strong: true, disabled: view.commentDisabled, onClick: () => ui8({ kind: "create" }), children: "Comment" }) }) })
10617
+ ] }),
10618
+ /* @__PURE__ */ jsx10(StatusBar6, { left: view.countLabel, right: view.staleLabel })
10609
10619
  ] });
10610
10620
  }
10611
10621
 
@@ -11773,7 +11783,7 @@ var nodegraphManifest = {
11773
11783
  // src/edit/nodegraph/react/NodeGraphPanelView.tsx
11774
11784
  import { useCallback as useCallback7, useEffect as useEffect7, useRef as useRef8, useSyncExternalStore as useSyncExternalStore9 } from "react";
11775
11785
  import { EmptyState as EmptyState7, StatusBar as StatusBar7, Toolbar as Toolbar6, ToolbarGroup as ToolbarGroup4 } from "@xenosystem/components/chrome";
11776
- import { Fragment as Fragment8, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
11786
+ import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
11777
11787
  var HEADER_H2 = 24;
11778
11788
  var PORT_ROW_H2 = 18;
11779
11789
  function NodeGraphPanelView({ controller }) {
@@ -11996,7 +12006,7 @@ function NodeRect({ node, selected, zoom }) {
11996
12006
  children: node.title
11997
12007
  }
11998
12008
  ),
11999
- node.collapsed ? null : /* @__PURE__ */ jsxs10(Fragment8, { children: [
12009
+ node.collapsed ? null : /* @__PURE__ */ jsxs10(Fragment7, { children: [
12000
12010
  node.inputs.map((port, i) => /* @__PURE__ */ jsx11(
12001
12011
  Port,
12002
12012
  {
@@ -12307,6 +12317,8 @@ var TreeController = class {
12307
12317
  mode;
12308
12318
  renaming = null;
12309
12319
  drag = null;
12320
+ /** The scroller's measured viewport — the view state windows the rows from it (`tree/Scroller` reports it). */
12321
+ viewport = { height: 0, scrollTop: 0 };
12310
12322
  /** Newest outstanding load request PER NODE. Anything else that arrives is stale. */
12311
12323
  outstanding = /* @__PURE__ */ new Map();
12312
12324
  /** Type-ahead buffer + the time it was last touched. */
@@ -12356,6 +12368,20 @@ var TreeController = class {
12356
12368
  this.rowCache = null;
12357
12369
  for (const listener of this.listeners) listener();
12358
12370
  }
12371
+ /* ── Viewport ──────────────────────────────────────────────────────────── */
12372
+ /** The scroller reports its viewport; the view state windows the rows from it. */
12373
+ setViewport(height, scrollTop) {
12374
+ if (this.viewport.height === height && this.viewport.scrollTop === scrollTop) return;
12375
+ this.viewport = { height, scrollTop };
12376
+ this.notify();
12377
+ }
12378
+ getViewport() {
12379
+ return this.viewport;
12380
+ }
12381
+ /** The fixed row height the virtualizer and the drop resolver share. */
12382
+ getRowHeight() {
12383
+ return this.rowHeight;
12384
+ }
12359
12385
  /* ── Rows ──────────────────────────────────────────────────────────────── */
12360
12386
  /** The visible rows, flattened through the shared engine. */
12361
12387
  rows() {
@@ -12729,12 +12755,12 @@ var TreeController = class {
12729
12755
  }
12730
12756
  if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
12731
12757
  const fresh = now - this.typeAhead.at > this.typeAheadMs;
12732
- const text6 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12733
- this.typeAhead = { text: text6, at: now };
12758
+ const text8 = (fresh ? "" : this.typeAhead.text) + event.key.toLowerCase();
12759
+ this.typeAhead = { text: text8, at: now };
12734
12760
  const from = fresh ? current : current + 1;
12735
12761
  for (let i = 0; i < rows.length; i += 1) {
12736
12762
  const candidate = rows[(from + i) % rows.length];
12737
- if (candidate.node.label.toLowerCase().startsWith(text6)) {
12763
+ if (candidate.node.label.toLowerCase().startsWith(text8)) {
12738
12764
  this.focusedId = candidate.id;
12739
12765
  this.select([candidate.id]);
12740
12766
  return true;
@@ -12760,9 +12786,9 @@ var TreeController = class {
12760
12786
  return true;
12761
12787
  }
12762
12788
  /** Update the rename draft. */
12763
- updateRenameDraft(text6) {
12789
+ updateRenameDraft(text8) {
12764
12790
  if (!this.renaming) return;
12765
- this.renaming = { ...this.renaming, draft: text6 };
12791
+ this.renaming = { ...this.renaming, draft: text8 };
12766
12792
  this.notify();
12767
12793
  }
12768
12794
  /**
@@ -13299,331 +13325,474 @@ var treeManifest = {
13299
13325
  };
13300
13326
 
13301
13327
  // src/edit/tree/react/TreePanelView.tsx
13302
- import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef9, useState as useState2, useSyncExternalStore as useSyncExternalStore10 } from "react";
13328
+ import { useCallback as useCallback8, useEffect as useEffect8, useRef as useRef9, useSyncExternalStore as useSyncExternalStore10 } from "react";
13303
13329
  import {
13304
13330
  Badge as Badge7,
13331
+ Column as Column7,
13305
13332
  EmptyState as EmptyState8,
13306
13333
  ErrorState,
13307
13334
  IconButton as IconButton7,
13335
+ Input as Input5,
13308
13336
  LoadingState,
13309
13337
  Row as Row4,
13338
+ RowList as RowList4,
13310
13339
  ScrollArea as ScrollArea6,
13311
13340
  SearchField as SearchField5,
13312
13341
  StatusBar as StatusBar8,
13342
+ Text as Text7,
13343
+ TextButton as TextButton5,
13313
13344
  Toolbar as Toolbar7,
13314
- ToolbarGroup as ToolbarGroup5
13345
+ ToolbarGroup as ToolbarGroup5,
13346
+ VirtualWindow as VirtualWindow3
13315
13347
  } from "@xenosystem/components/chrome";
13348
+
13349
+ // src/edit/tree/declaration.ts
13316
13350
  import { computeWindow as computeWindow3 } from "@xenosystem/tree-core";
13317
- import { Fragment as Fragment9, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
13318
- function TreePanelView({
13319
- controller,
13320
- rowHeight = 24,
13321
- indentPx = 14,
13322
- overscan = 6,
13323
- showSearch = true,
13324
- openOn = "double",
13325
- columns,
13326
- emptyHint
13327
- }) {
13328
- const state = useSyncExternalStore10(controller.subscribe, controller.getState, controller.getState);
13329
- const scrollRef = useRef9(null);
13330
- const [viewport, setViewport] = useState2({ height: 0, scrollTop: 0 });
13331
- useEffect8(() => {
13332
- const element = scrollRef.current;
13333
- if (!element || typeof ResizeObserver === "undefined") return;
13334
- const observer = new ResizeObserver(() => {
13335
- setViewport((v) => ({ ...v, height: element.clientHeight }));
13336
- });
13337
- observer.observe(element);
13338
- setViewport((v) => ({ ...v, height: element.clientHeight }));
13339
- return () => observer.disconnect();
13340
- }, []);
13341
- const onScroll = useCallback8((event) => {
13342
- const el = event.currentTarget;
13343
- setViewport((v) => ({ ...v, scrollTop: el.scrollTop }));
13344
- }, []);
13351
+ var iconHalf = (icon) => icon.kind === "text" ? { iconName: null, iconText: icon.value } : { iconName: icon.name, iconText: null };
13352
+ function treeViewState(c, options = {}) {
13353
+ const { overscan = 6, showSearch = true, columns, emptyHint } = options;
13354
+ const state = c.getState();
13345
13355
  const source = state.source;
13346
13356
  const sourceState = source?.sourceState ?? "idle";
13347
- const body = (() => {
13348
- if (!source || sourceState === "idle") {
13349
- return /* @__PURE__ */ jsx12(EmptyState8, { title: "No source", hint: emptyHint ?? "Connect a source to browse it." });
13357
+ const rowHeight = c.getRowHeight();
13358
+ const viewport = c.getViewport();
13359
+ const noSource = !source || sourceState === "idle";
13360
+ const loading = !noSource && sourceState === "loading" && state.rows.length === 0;
13361
+ const failed = !noSource && (sourceState === "error" || sourceState === "denied");
13362
+ const denied = sourceState === "denied";
13363
+ const bare = !noSource && !loading && !failed && state.rows.length === 0;
13364
+ const noMatches = bare && state.filter !== "";
13365
+ const emptyTree = bare && state.filter === "";
13366
+ const listShown = !noSource && !loading && !failed && state.rows.length > 0;
13367
+ const window_ = computeWindow3({
13368
+ scrollTop: viewport.scrollTop,
13369
+ viewportHeight: viewport.height || rowHeight * 24,
13370
+ rowHeight,
13371
+ rowCount: state.rows.length,
13372
+ overscan
13373
+ });
13374
+ const drag = state.drag;
13375
+ const renaming = state.renaming;
13376
+ const rows = listShown ? state.rows.slice(window_.startIndex, window_.endIndex).map((row2) => {
13377
+ const node = row2.node;
13378
+ const drop = drag?.targetId === row2.id ? drag : null;
13379
+ const error = node.error;
13380
+ const chipLoading = row2.container && row2.childState === "loading";
13381
+ const chipError = row2.container && !chipLoading && row2.childState === "error";
13382
+ const chipLoadMore = row2.container && !chipLoading && !chipError && !!node.hasMore && row2.expanded;
13383
+ const chipTruncated = row2.container && !chipLoading && !chipError && !chipLoadMore && node.truncatedAt !== void 0;
13384
+ const chipEmpty = row2.container && !chipLoading && !chipError && !chipLoadMore && !chipTruncated && row2.expanded && isEmptyContainer(node);
13385
+ return {
13386
+ id: row2.id,
13387
+ label: node.label,
13388
+ depth: row2.depth,
13389
+ level: row2.depth + 1,
13390
+ height: rowHeight,
13391
+ selected: state.selection.includes(row2.id),
13392
+ disabled: node.caps?.select === false,
13393
+ expanded: row2.container ? row2.expanded : void 0,
13394
+ dropHint: drop?.position ?? null,
13395
+ dropTitle: drop?.reason,
13396
+ renaming: renaming?.id === row2.id,
13397
+ draft: renaming?.id === row2.id ? renaming.draft : "",
13398
+ ...iconHalf(node.icon ?? defaultIconFor(node.kind)),
13399
+ chipLoading,
13400
+ chipError,
13401
+ // The host's own sentence — never a regex over a message to guess the cause.
13402
+ chipErrorText: error?.code === "denied" ? "no access" : error?.code ?? "failed",
13403
+ chipLoadMore,
13404
+ chipTruncated,
13405
+ chipTruncatedText: `first ${node.truncatedAt ?? 0}`,
13406
+ chipEmpty,
13407
+ columns: (columns ?? []).map((key) => ({ key, text: node.columns?.[key] ?? "" })),
13408
+ glyphs: (node.glyphs ?? []).map((g) => ({ key: g.id, title: g.title, ...iconHalf(g.icon) })),
13409
+ badges: (node.badges ?? []).map((b) => ({ key: b.id, tone: b.tone, text: b.text })),
13410
+ actions: (node.actions ?? []).map((a) => ({ key: a.id, rowId: row2.id, actionId: a.id, label: a.label, disabled: !!a.disabled, ...iconHalf(a.icon) }))
13411
+ };
13412
+ }) : [];
13413
+ const count = state.rows.length;
13414
+ return {
13415
+ searchShown: showSearch,
13416
+ filter: state.filter,
13417
+ noSource,
13418
+ noSourceHint: emptyHint ?? "Connect a source to browse it.",
13419
+ loading,
13420
+ loadingLabel: source?.sourceMessage ?? "Loading\u2026",
13421
+ failed,
13422
+ failedTitle: denied ? "Access not granted" : "Could not read this source",
13423
+ // The HOST's sentence, verbatim. The panel never composes error prose.
13424
+ failedMessage: source?.sourceMessage,
13425
+ retryLabel: denied ? "Grant access" : "Retry",
13426
+ denied,
13427
+ noMatches,
13428
+ noMatchesHint: `Nothing matches \u201C${state.filter}\u201D.`,
13429
+ emptyTree,
13430
+ emptyHint: emptyHint ?? source?.sourceMessage ?? "This source has no items yet.",
13431
+ listShown,
13432
+ totalHeight: window_.totalHeight,
13433
+ offsetTop: window_.offsetTop,
13434
+ rows,
13435
+ countLabel: `${count} item${count === 1 ? "" : "s"}`,
13436
+ selectedLabel: state.selection.length > 0 ? `${state.selection.length} selected` : void 0
13437
+ };
13438
+ }
13439
+ function applyTreeUi(c, intent, options = {}) {
13440
+ switch (intent.kind) {
13441
+ case "filter":
13442
+ c.setFilter(intent.value);
13443
+ break;
13444
+ case "expandAll":
13445
+ c.expandAll();
13446
+ break;
13447
+ case "collapseAll":
13448
+ c.collapseAll();
13449
+ break;
13450
+ case "refresh":
13451
+ c.refresh();
13452
+ break;
13453
+ case "retry": {
13454
+ if (c.getState().source?.sourceState === "denied") c.requestConsent(void 0, "source denied");
13455
+ else c.refresh();
13456
+ break;
13350
13457
  }
13351
- if (sourceState === "loading" && state.rows.length === 0) {
13352
- return /* @__PURE__ */ jsx12(LoadingState, { label: source.sourceMessage ?? "Loading\u2026" });
13458
+ case "toggleExpanded": {
13459
+ const row2 = c.getState().rows.find((r) => r.id === intent.id);
13460
+ if (row2) c.setExpanded(intent.id, !row2.expanded);
13461
+ break;
13353
13462
  }
13354
- if (sourceState === "error" || sourceState === "denied") {
13355
- return /* @__PURE__ */ jsx12(
13356
- ErrorState,
13357
- {
13358
- title: sourceState === "denied" ? "Access not granted" : "Could not read this source",
13359
- message: source.sourceMessage,
13360
- onRetry: sourceState === "denied" ? () => controller.requestConsent(void 0, "source denied") : () => controller.refresh(),
13361
- retryLabel: sourceState === "denied" ? "Grant access" : "Retry"
13362
- }
13363
- );
13463
+ case "click": {
13464
+ c.select([intent.id], { toggle: !!intent.ctrlKey || !!intent.metaKey, range: !!intent.shiftKey });
13465
+ if ((options.openOn ?? "double") === "single") c.open(intent.id, { alt: !!intent.altKey });
13466
+ break;
13364
13467
  }
13365
- if (state.rows.length === 0) {
13366
- return state.filter ? /* @__PURE__ */ jsx12(EmptyState8, { title: "No matches", hint: `Nothing matches \u201C${state.filter}\u201D.` }) : /* @__PURE__ */ jsx12(
13367
- EmptyState8,
13368
- {
13369
- title: "Nothing here",
13370
- hint: emptyHint ?? source.sourceMessage ?? "This source has no items yet."
13371
- }
13372
- );
13468
+ case "doubleClick":
13469
+ if ((options.openOn ?? "double") === "double") c.open(intent.id);
13470
+ break;
13471
+ case "contextMenu": {
13472
+ const state = c.getState();
13473
+ c.requestContextMenu(state.selection.includes(intent.id) ? state.selection : [intent.id], intent.x, intent.y);
13474
+ break;
13373
13475
  }
13374
- const window2 = computeWindow3({
13375
- scrollTop: viewport.scrollTop,
13376
- viewportHeight: viewport.height || rowHeight * 24,
13377
- rowHeight,
13378
- rowCount: state.rows.length,
13379
- overscan
13380
- });
13381
- const slice = state.rows.slice(window2.startIndex, window2.endIndex);
13382
- return /* @__PURE__ */ jsx12(ScrollArea6, { children: /* @__PURE__ */ jsx12(
13383
- "div",
13476
+ case "loadMore":
13477
+ c.loadMore(intent.id);
13478
+ break;
13479
+ case "action":
13480
+ c.runAction(intent.id, intent.actionId);
13481
+ break;
13482
+ case "renameDraft":
13483
+ c.updateRenameDraft(intent.value);
13484
+ break;
13485
+ case "renameKey": {
13486
+ if (intent.key === "Enter") c.commitRename();
13487
+ else if (intent.key === "Escape") c.cancelRename();
13488
+ break;
13489
+ }
13490
+ case "commitRename":
13491
+ c.commitRename();
13492
+ break;
13493
+ }
13494
+ }
13495
+ var treeViewManifest = {
13496
+ id: treeManifest.id,
13497
+ version: "1.0.0",
13498
+ title: "Tree",
13499
+ category: "edit",
13500
+ layout: { slot: "side" },
13501
+ inputs: [{ id: "view", type: "object", label: "View state", description: "The TreeViewModel the runtime derives from the controller on every draw." }],
13502
+ outputs: [{ id: "ui", type: "object", label: "UI intent", description: "{ kind, id?, value?, key?, actionId?, x?, y?, ctrlKey?, metaKey?, shiftKey?, altKey? } \u2014 applied by the runtime to the controller." }],
13503
+ commands: []
13504
+ };
13505
+ var EMPTY7 = {
13506
+ searchShown: true,
13507
+ filter: "",
13508
+ noSource: true,
13509
+ noSourceHint: "Connect a source to browse it.",
13510
+ loading: false,
13511
+ loadingLabel: "Loading\u2026",
13512
+ failed: false,
13513
+ failedTitle: "",
13514
+ retryLabel: "Retry",
13515
+ denied: false,
13516
+ noMatches: false,
13517
+ noMatchesHint: "",
13518
+ emptyTree: false,
13519
+ emptyHint: "",
13520
+ listShown: false,
13521
+ totalHeight: 0,
13522
+ offsetTop: 0,
13523
+ rows: [],
13524
+ countLabel: "0 items"
13525
+ };
13526
+ var BEHAVIOURS7 = {
13527
+ "edit/treePanelView": (inputs) => inputs.view ?? EMPTY7
13528
+ };
13529
+ var text7 = (b) => ({ component: "text", props: { text: b } });
13530
+ var ui7 = (kind, fields = {}) => ({ emit: "ui", value: { object: { kind: { value: kind }, ...fields } } });
13531
+ var nodeIcon = () => ({
13532
+ component: "fragment",
13533
+ children: [
13534
+ { component: "icons/TreeKind", when: { item: "iconName" }, props: { name: { item: "iconName" } } },
13535
+ { component: "chrome/Text", when: { item: "iconText" }, props: { role: { value: "caption" } }, children: [text7({ item: "iconText" })] }
13536
+ ]
13537
+ });
13538
+ var TREE_DECLARATION = {
13539
+ format: "xeno.block@1",
13540
+ manifest: treeViewManifest,
13541
+ behaviour: "edit/treePanelView",
13542
+ composition: {
13543
+ component: "chrome/Column",
13544
+ children: [
13384
13545
  {
13385
- ref: scrollRef,
13386
- onScroll,
13387
- style: { height: "100%", overflow: "auto" },
13388
- role: "tree",
13389
- "aria-multiselectable": true,
13390
- tabIndex: 0,
13391
- onKeyDown: (e) => {
13392
- if (controller.handleKey({ key: e.key, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey })) {
13393
- e.preventDefault();
13546
+ component: "chrome/Toolbar",
13547
+ props: {
13548
+ left: { node: { component: "chrome/SearchField", when: { input: "view.searchShown" }, props: { value: { input: "view.filter" }, placeholder: { value: "Filter\u2026" } }, on: { onChange: ui7("filter", { value: { arg: "" } }) } } },
13549
+ right: {
13550
+ node: {
13551
+ component: "chrome/ToolbarGroup",
13552
+ props: { end: { value: true } },
13553
+ children: [
13554
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Expand" } }, label: { value: "Expand all" } }, on: { onClick: ui7("expandAll") } },
13555
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Collapse" } }, label: { value: "Collapse all" } }, on: { onClick: ui7("collapseAll") } },
13556
+ { component: "chrome/IconButton", props: { icon: { node: { component: "icons/Refresh" } }, label: { value: "Refresh" } }, on: { onClick: ui7("refresh") } }
13557
+ ]
13558
+ }
13394
13559
  }
13395
- },
13396
- children: /* @__PURE__ */ jsx12("div", { style: { height: window2.totalHeight, position: "relative" }, children: /* @__PURE__ */ jsx12("div", { style: { transform: `translateY(${window2.offsetTop}px)` }, children: slice.map((row2) => /* @__PURE__ */ jsx12(
13397
- TreeRowView,
13560
+ }
13561
+ },
13562
+ { component: "chrome/EmptyState", when: { input: "view.noSource" }, props: { title: { value: "No source" }, hint: { input: "view.noSourceHint" } } },
13563
+ { component: "chrome/LoadingState", when: { input: "view.loading" }, props: { label: { input: "view.loadingLabel" } } },
13564
+ { component: "chrome/ErrorState", when: { input: "view.failed" }, props: { title: { input: "view.failedTitle" }, message: { input: "view.failedMessage" }, retryLabel: { input: "view.retryLabel" } }, on: { onRetry: ui7("retry") } },
13565
+ { component: "chrome/EmptyState", when: { input: "view.noMatches" }, props: { title: { value: "No matches" }, hint: { input: "view.noMatchesHint" } } },
13566
+ { component: "chrome/EmptyState", when: { input: "view.emptyTree" }, props: { title: { value: "Nothing here" }, hint: { input: "view.emptyHint" } } },
13567
+ {
13568
+ component: "tree/Scroller",
13569
+ when: { input: "view.listShown" },
13570
+ children: [
13398
13571
  {
13399
- row: row2,
13400
- controller,
13401
- state,
13402
- rowHeight,
13403
- indentPx,
13404
- openOn,
13405
- columns
13406
- },
13407
- row2.id
13408
- )) }) })
13572
+ component: "chrome/RowList",
13573
+ props: { role: { value: "tree" }, "aria-label": { value: "Tree" }, "aria-multiselectable": { value: true } },
13574
+ children: [
13575
+ {
13576
+ component: "chrome/VirtualWindow",
13577
+ props: { totalHeight: { input: "view.totalHeight" }, offsetTop: { input: "view.offsetTop" } },
13578
+ children: [
13579
+ {
13580
+ component: "chrome/Row",
13581
+ each: { input: "view.rows", key: "id" },
13582
+ props: {
13583
+ "data-tree-row": { item: "id" },
13584
+ "aria-level": { item: "level" },
13585
+ height: { item: "height" },
13586
+ depth: { item: "depth" },
13587
+ selected: { item: "selected" },
13588
+ disabled: { item: "disabled" },
13589
+ expanded: { item: "expanded" },
13590
+ dropHint: { item: "dropHint" },
13591
+ title: { item: "dropTitle" },
13592
+ icon: { node: nodeIcon() },
13593
+ label: {
13594
+ node: {
13595
+ component: "fragment",
13596
+ children: [
13597
+ { component: "chrome/Input", when: { item: "renaming" }, props: { autoFocus: { value: true }, value: { item: "draft" }, label: { value: "Name" } }, on: { onChange: ui7("renameDraft", { value: { arg: "" } }), onBlur: ui7("commitRename"), onKeyDown: ui7("renameKey", { key: { arg: "key" } }) } },
13598
+ { component: "text", when: { not: { item: "renaming" } }, props: { text: { item: "label" } } }
13599
+ ]
13600
+ }
13601
+ },
13602
+ meta: {
13603
+ node: {
13604
+ component: "fragment",
13605
+ children: [
13606
+ { component: "chrome/Text", when: { item: "chipLoading" }, props: { role: { value: "caption" } }, children: [text7({ value: "loading\u2026" })] },
13607
+ { component: "chrome/Badge", when: { item: "chipError" }, props: { tone: { value: "error" } }, children: [text7({ item: "chipErrorText" })] },
13608
+ { component: "chrome/TextButton", when: { item: "chipLoadMore" }, children: [text7({ value: "load more\u2026" })], on: { onClick: ui7("loadMore", { id: { item: "id" } }) } },
13609
+ { component: "chrome/Badge", when: { item: "chipTruncated" }, props: { tone: { value: "warning" } }, children: [text7({ item: "chipTruncatedText" })] },
13610
+ { component: "chrome/Text", when: { item: "chipEmpty" }, props: { role: { value: "caption" } }, children: [text7({ value: "empty" })] }
13611
+ ]
13612
+ }
13613
+ },
13614
+ trailing: {
13615
+ node: {
13616
+ component: "fragment",
13617
+ children: [
13618
+ { component: "chrome/Text", each: { item: "columns", key: "key" }, props: { role: { value: "number" } }, children: [text7({ item: "text" })] },
13619
+ { component: "chrome/Text", each: { item: "glyphs", key: "key" }, props: { role: { value: "meta" }, title: { item: "title" } }, children: [nodeIcon()] },
13620
+ { component: "chrome/Badge", each: { item: "badges", key: "key" }, props: { tone: { item: "tone" } }, children: [text7({ item: "text" })] },
13621
+ { component: "chrome/IconButton", each: { item: "actions", key: "key" }, props: { size: { value: "sm" }, icon: { node: nodeIcon() }, label: { item: "label" }, disabled: { item: "disabled" } }, on: { onClick: ui7("action", { id: { item: "rowId" }, actionId: { item: "actionId" } }) } }
13622
+ ]
13623
+ }
13624
+ }
13625
+ },
13626
+ on: {
13627
+ onExpandedChange: ui7("toggleExpanded", { id: { item: "id" } }),
13628
+ onClick: ui7("click", { id: { item: "id" }, ctrlKey: { arg: "ctrlKey" }, metaKey: { arg: "metaKey" }, shiftKey: { arg: "shiftKey" }, altKey: { arg: "altKey" } }),
13629
+ onDoubleClick: ui7("doubleClick", { id: { item: "id" } }),
13630
+ onContextMenuAt: ui7("contextMenu", { id: { item: "id" }, x: { arg: "" }, y: { arg: "", index: 1 } })
13631
+ }
13632
+ }
13633
+ ]
13634
+ }
13635
+ ]
13636
+ }
13637
+ ]
13638
+ },
13639
+ {
13640
+ component: "chrome/StatusBar",
13641
+ props: {
13642
+ left: { node: { component: "chrome/Text", props: { role: { value: "number" } }, children: [text7({ input: "view.countLabel" })] } },
13643
+ right: { node: { component: "chrome/Text", when: { input: "view.selectedLabel" }, props: { role: { value: "meta" } }, children: [text7({ input: "view.selectedLabel" })] } }
13644
+ }
13409
13645
  }
13410
- ) });
13411
- })();
13412
- return /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
13646
+ ]
13647
+ }
13648
+ };
13649
+
13650
+ // src/edit/tree/react/TreePanelView.tsx
13651
+ import { Fragment as Fragment8, jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
13652
+ var DRAG_THRESHOLD2 = 4;
13653
+ function TreePanelView({ controller, ...options }) {
13654
+ useSyncExternalStore10(controller.subscribe, controller.getState, controller.getState);
13655
+ const view = treeViewState(controller, options);
13656
+ const ui8 = (intent) => applyTreeUi(controller, intent, options);
13657
+ const icon = (name, textValue) => /* @__PURE__ */ jsxs11(Fragment8, { children: [
13658
+ name ? /* @__PURE__ */ jsx12(TreeIcon, { name }) : null,
13659
+ textValue ? /* @__PURE__ */ jsx12(Text7, { role: "caption", children: textValue }) : null
13660
+ ] });
13661
+ const row2 = (r) => /* @__PURE__ */ jsx12(
13662
+ Row4,
13663
+ {
13664
+ "data-tree-row": r.id,
13665
+ "aria-level": r.level,
13666
+ height: r.height,
13667
+ depth: r.depth,
13668
+ selected: r.selected,
13669
+ disabled: r.disabled,
13670
+ expanded: r.expanded,
13671
+ dropHint: r.dropHint,
13672
+ title: r.dropTitle,
13673
+ icon: icon(r.iconName, r.iconText),
13674
+ label: /* @__PURE__ */ jsxs11(Fragment8, { children: [
13675
+ r.renaming ? /* @__PURE__ */ jsx12(Input5, { autoFocus: true, value: r.draft, label: "Name", onChange: (value) => ui8({ kind: "renameDraft", value }), onBlur: () => ui8({ kind: "commitRename" }), onKeyDown: (e) => ui8({ kind: "renameKey", key: e.key }) }) : null,
13676
+ !r.renaming ? r.label : null
13677
+ ] }),
13678
+ meta: /* @__PURE__ */ jsxs11(Fragment8, { children: [
13679
+ r.chipLoading ? /* @__PURE__ */ jsx12(Text7, { role: "caption", children: "loading\u2026" }) : null,
13680
+ r.chipError ? /* @__PURE__ */ jsx12(Badge7, { tone: "error", children: r.chipErrorText }) : null,
13681
+ r.chipLoadMore ? /* @__PURE__ */ jsx12(TextButton5, { onClick: () => ui8({ kind: "loadMore", id: r.id }), children: "load more\u2026" }) : null,
13682
+ r.chipTruncated ? /* @__PURE__ */ jsx12(Badge7, { tone: "warning", children: r.chipTruncatedText }) : null,
13683
+ r.chipEmpty ? /* @__PURE__ */ jsx12(Text7, { role: "caption", children: "empty" }) : null
13684
+ ] }),
13685
+ trailing: /* @__PURE__ */ jsxs11(Fragment8, { children: [
13686
+ r.columns.map((c) => /* @__PURE__ */ jsx12(Text7, { role: "number", children: c.text }, c.key)),
13687
+ r.glyphs.map((g) => /* @__PURE__ */ jsx12(Text7, { role: "meta", title: g.title, children: icon(g.iconName, g.iconText) }, g.key)),
13688
+ r.badges.map((b) => /* @__PURE__ */ jsx12(Badge7, { tone: b.tone, children: b.text }, b.key)),
13689
+ r.actions.map((a) => /* @__PURE__ */ jsx12(IconButton7, { size: "sm", icon: icon(a.iconName, a.iconText), label: a.label, disabled: a.disabled, onClick: () => ui8({ kind: "action", id: a.rowId, actionId: a.actionId }) }, a.key))
13690
+ ] }),
13691
+ onExpandedChange: () => ui8({ kind: "toggleExpanded", id: r.id }),
13692
+ onClick: (e) => ui8({ kind: "click", id: r.id, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey }),
13693
+ onDoubleClick: () => ui8({ kind: "doubleClick", id: r.id }),
13694
+ onContextMenuAt: (x, y) => ui8({ kind: "contextMenu", id: r.id, x, y })
13695
+ },
13696
+ r.id
13697
+ );
13698
+ return /* @__PURE__ */ jsxs11(Column7, { children: [
13413
13699
  /* @__PURE__ */ jsx12(
13414
13700
  Toolbar7,
13415
13701
  {
13416
- left: showSearch ? /* @__PURE__ */ jsx12(
13417
- SearchField5,
13418
- {
13419
- value: state.filter,
13420
- onChange: (term) => controller.setFilter(term),
13421
- placeholder: "Filter\u2026"
13422
- }
13423
- ) : void 0,
13702
+ left: view.searchShown ? /* @__PURE__ */ jsx12(SearchField5, { value: view.filter, placeholder: "Filter\u2026", onChange: (value) => ui8({ kind: "filter", value }) }) : null,
13424
13703
  right: /* @__PURE__ */ jsxs11(ToolbarGroup5, { end: true, children: [
13425
- /* @__PURE__ */ jsx12(IconButton7, { icon: /* @__PURE__ */ jsx12(ExpandGlyph, {}), label: "Expand all", onClick: () => controller.expandAll() }),
13426
- /* @__PURE__ */ jsx12(
13427
- IconButton7,
13428
- {
13429
- icon: /* @__PURE__ */ jsx12(CollapseGlyph, {}),
13430
- label: "Collapse all",
13431
- onClick: () => controller.collapseAll()
13432
- }
13433
- ),
13434
- /* @__PURE__ */ jsx12(IconButton7, { icon: /* @__PURE__ */ jsx12(RefreshGlyph, {}), label: "Refresh", onClick: () => controller.refresh() })
13704
+ /* @__PURE__ */ jsx12(IconButton7, { icon: /* @__PURE__ */ jsx12(ExpandGlyph, {}), label: "Expand all", onClick: () => ui8({ kind: "expandAll" }) }),
13705
+ /* @__PURE__ */ jsx12(IconButton7, { icon: /* @__PURE__ */ jsx12(CollapseGlyph, {}), label: "Collapse all", onClick: () => ui8({ kind: "collapseAll" }) }),
13706
+ /* @__PURE__ */ jsx12(IconButton7, { icon: /* @__PURE__ */ jsx12(RefreshGlyph, {}), label: "Refresh", onClick: () => ui8({ kind: "refresh" }) })
13435
13707
  ] })
13436
13708
  }
13437
13709
  ),
13438
- body,
13439
- /* @__PURE__ */ jsx12(
13440
- StatusBar8,
13441
- {
13442
- left: `${state.rows.length} item${state.rows.length === 1 ? "" : "s"}`,
13443
- right: state.selection.length > 0 ? `${state.selection.length} selected` : void 0
13444
- }
13445
- )
13710
+ view.noSource ? /* @__PURE__ */ jsx12(EmptyState8, { title: "No source", hint: view.noSourceHint }) : null,
13711
+ view.loading ? /* @__PURE__ */ jsx12(LoadingState, { label: view.loadingLabel }) : null,
13712
+ view.failed ? /* @__PURE__ */ jsx12(ErrorState, { title: view.failedTitle, message: view.failedMessage, retryLabel: view.retryLabel, onRetry: () => ui8({ kind: "retry" }) }) : null,
13713
+ view.noMatches ? /* @__PURE__ */ jsx12(EmptyState8, { title: "No matches", hint: view.noMatchesHint }) : null,
13714
+ view.emptyTree ? /* @__PURE__ */ jsx12(EmptyState8, { title: "Nothing here", hint: view.emptyHint }) : null,
13715
+ view.listShown ? /* @__PURE__ */ jsx12(TreeScroller, { controller, children: /* @__PURE__ */ jsx12(RowList4, { role: "tree", "aria-label": "Tree", "aria-multiselectable": true, children: /* @__PURE__ */ jsx12(VirtualWindow3, { totalHeight: view.totalHeight, offsetTop: view.offsetTop, children: view.rows.map(row2) }) }) }) : null,
13716
+ /* @__PURE__ */ jsx12(StatusBar8, { left: /* @__PURE__ */ jsx12(Text7, { role: "number", children: view.countLabel }), right: view.selectedLabel ? /* @__PURE__ */ jsx12(Text7, { role: "meta", children: view.selectedLabel }) : null })
13446
13717
  ] });
13447
13718
  }
13448
- function TreeRowView({
13449
- row: row2,
13450
- controller,
13451
- state,
13452
- rowHeight,
13453
- indentPx,
13454
- openOn,
13455
- columns
13456
- }) {
13457
- const node = row2.node;
13458
- const selected = state.selection.includes(row2.id);
13459
- const renaming = state.renaming?.id === row2.id ? state.renaming : null;
13460
- const drop = state.drag?.targetId === row2.id ? state.drag : null;
13461
- const twisty = row2.container ? /* @__PURE__ */ jsx12(
13462
- "span",
13463
- {
13464
- role: "button",
13465
- "aria-label": row2.expanded ? "Collapse" : "Expand",
13466
- onClick: (e) => {
13467
- e.stopPropagation();
13468
- controller.setExpanded(row2.id, !row2.expanded);
13469
- },
13470
- style: {
13471
- display: "flex",
13472
- alignItems: "center",
13473
- justifyContent: "center",
13474
- width: 12,
13475
- flex: "0 0 auto",
13476
- cursor: "pointer",
13477
- transform: row2.expanded ? "rotate(90deg)" : void 0,
13478
- transition: "transform 80ms ease",
13479
- opacity: 0.7
13480
- },
13481
- children: /* @__PURE__ */ jsx12(ChevronGlyph, { size: 9 })
13482
- }
13483
- ) : (
13484
- // A leaf still reserves the twisty column, so labels stay in ONE column across mixed rows.
13485
- /* @__PURE__ */ jsx12("span", { style: { width: 12, flex: "0 0 auto" } })
13719
+ function TreeScroller({ controller, children }) {
13720
+ const scrollRef = useRef9(null);
13721
+ const pressRef = useRef9(null);
13722
+ useEffect8(() => {
13723
+ const el = scrollRef.current;
13724
+ if (!el) return;
13725
+ controller.setViewport(el.clientHeight, el.scrollTop);
13726
+ const RO = globalThis.ResizeObserver;
13727
+ if (!RO) return;
13728
+ const ro = new RO(() => controller.setViewport(el.clientHeight, el.scrollTop));
13729
+ ro.observe(el);
13730
+ return () => ro.disconnect();
13731
+ }, [controller]);
13732
+ const onScroll = useCallback8((e) => controller.setViewport(e.currentTarget.clientHeight, e.currentTarget.scrollTop), [controller]);
13733
+ const rowFromPoint = useCallback8((clientX, clientY) => {
13734
+ const el = document.elementFromPoint(clientX, clientY);
13735
+ const rowEl = el?.closest?.("[data-tree-row]");
13736
+ const id = rowEl?.getAttribute("data-tree-row");
13737
+ if (!rowEl || !id) return null;
13738
+ return { id, offsetY: clientY - rowEl.getBoundingClientRect().top };
13739
+ }, []);
13740
+ const onPointerDown = useCallback8(
13741
+ (e) => {
13742
+ if (e.button !== 0) return;
13743
+ const target = e.target;
13744
+ if (target?.closest?.("button,input")) return;
13745
+ const id = target?.closest?.("[data-tree-row]")?.getAttribute("data-tree-row");
13746
+ if (!id) return;
13747
+ const selection = controller.getState().selection;
13748
+ pressRef.current = { ids: selection.includes(id) ? selection : [id], x: e.clientX, y: e.clientY, dragging: false };
13749
+ e.currentTarget.setPointerCapture(e.pointerId);
13750
+ },
13751
+ [controller]
13486
13752
  );
13487
- return /* @__PURE__ */ jsx12(
13488
- "div",
13489
- {
13490
- style: {
13491
- position: "relative",
13492
- // The drop indicator is drawn on the row, not inserted into the list: inserting a
13493
- // placeholder row would change the flatten mid-drag and move the target out from under
13494
- // the pointer.
13495
- boxShadow: drop?.position === "into" ? "inset 0 0 0 1px var(--xeno-active)" : drop?.position === "before" ? "inset 0 1px 0 0 var(--xeno-active)" : drop?.position === "after" ? "inset 0 -1px 0 0 var(--xeno-active)" : void 0
13496
- },
13497
- title: drop?.reason,
13498
- children: /* @__PURE__ */ jsx12(
13499
- Row4,
13500
- {
13501
- role: "treeitem",
13502
- "aria-expanded": row2.container ? row2.expanded : void 0,
13503
- "aria-level": row2.depth + 1,
13504
- selected,
13505
- disabled: node.caps?.select === false,
13506
- depth: 0,
13507
- style: { height: rowHeight, paddingLeft: 6 + row2.depth * indentPx },
13508
- onClick: (e) => {
13509
- controller.select([row2.id], {
13510
- toggle: e.ctrlKey || e.metaKey,
13511
- range: e.shiftKey
13512
- });
13513
- if (openOn === "single") controller.open(row2.id, { alt: e.altKey });
13514
- },
13515
- onDoubleClick: () => {
13516
- if (openOn === "double") controller.open(row2.id);
13517
- },
13518
- onContextMenu: (e) => {
13519
- e.preventDefault();
13520
- controller.requestContextMenu(selected ? state.selection : [row2.id], e.clientX, e.clientY);
13521
- },
13522
- onPointerDown: (e) => {
13523
- if (e.button !== 0) return;
13524
- controller.beginDrag(selected ? state.selection : [row2.id]);
13525
- },
13526
- onPointerMove: (e) => {
13527
- if (!state.drag) return;
13528
- const rect = e.currentTarget.getBoundingClientRect();
13529
- controller.dragOver(row2.id, e.clientY - rect.top);
13530
- },
13531
- onPointerUp: () => {
13532
- if (state.drag) controller.endDrag();
13533
- },
13534
- icon: /* @__PURE__ */ jsxs11(Fragment9, { children: [
13535
- twisty,
13536
- /* @__PURE__ */ jsx12(NodeIcon, { icon: node.icon ?? defaultIconFor(node.kind) })
13537
- ] }),
13538
- label: renaming ? /* @__PURE__ */ jsx12(
13539
- "input",
13540
- {
13541
- autoFocus: true,
13542
- value: renaming.draft,
13543
- onChange: (e) => controller.updateRenameDraft(e.target.value),
13544
- onBlur: () => controller.commitRename(),
13545
- onFocus: (e) => e.currentTarget.select(),
13546
- onClick: (e) => e.stopPropagation(),
13547
- onKeyDown: (e) => {
13548
- e.stopPropagation();
13549
- if (e.key === "Enter") controller.commitRename();
13550
- else if (e.key === "Escape") controller.cancelRename();
13551
- },
13552
- style: {
13553
- width: "100%",
13554
- border: "none",
13555
- outline: "none",
13556
- background: "var(--xeno-canvas)",
13557
- color: "inherit",
13558
- font: "inherit",
13559
- padding: 0
13560
- }
13561
- }
13562
- ) : node.label,
13563
- meta: /* @__PURE__ */ jsx12(ChildStateChip, { row: row2, controller }),
13564
- trailing: /* @__PURE__ */ jsxs11(Fragment9, { children: [
13565
- columns?.map((columnId) => /* @__PURE__ */ jsx12("span", { style: { opacity: 0.7, fontVariantNumeric: "tabular-nums" }, children: node.columns?.[columnId] ?? "" }, columnId)),
13566
- node.glyphs?.map((glyph) => /* @__PURE__ */ jsx12("span", { title: glyph.title, style: { display: "flex", opacity: 0.7 }, children: /* @__PURE__ */ jsx12(NodeIcon, { icon: glyph.icon }) }, glyph.id)),
13567
- node.badges?.map((badge) => (
13568
- // Opaque: the host formatted the text, the panel only places it.
13569
- /* @__PURE__ */ jsx12(Badge7, { tone: badge.tone, className: badge.title ? void 0 : void 0, children: badge.text }, badge.id)
13570
- )),
13571
- node.actions?.map((action) => /* @__PURE__ */ jsx12(
13572
- IconButton7,
13573
- {
13574
- size: "sm",
13575
- icon: /* @__PURE__ */ jsx12(NodeIcon, { icon: action.icon }),
13576
- label: action.label,
13577
- disabled: action.disabled,
13578
- onClick: () => controller.runAction(row2.id, action.id)
13579
- },
13580
- action.id
13581
- ))
13582
- ] })
13583
- }
13584
- )
13585
- }
13753
+ const onPointerMove = useCallback8(
13754
+ (e) => {
13755
+ const press = pressRef.current;
13756
+ if (!press) return;
13757
+ if (!press.dragging) {
13758
+ if (Math.abs(e.clientX - press.x) + Math.abs(e.clientY - press.y) < DRAG_THRESHOLD2) return;
13759
+ press.dragging = true;
13760
+ controller.beginDrag(press.ids);
13761
+ }
13762
+ const hit = rowFromPoint(e.clientX, e.clientY);
13763
+ if (hit) controller.dragOver(hit.id, hit.offsetY);
13764
+ },
13765
+ [controller, rowFromPoint]
13586
13766
  );
13587
- }
13588
- function ChildStateChip({
13589
- row: row2,
13590
- controller
13591
- }) {
13592
- if (!row2.container) return null;
13593
- if (row2.childState === "loading") {
13594
- return /* @__PURE__ */ jsx12("span", { style: { opacity: 0.5, fontSize: 9 }, children: "loading\u2026" });
13595
- }
13596
- if (row2.childState === "error") {
13597
- const error = row2.node.error;
13598
- return /* @__PURE__ */ jsx12(Badge7, { tone: "error", children: error?.code === "denied" ? "no access" : error?.code ?? "failed" });
13599
- }
13600
- if (row2.node.hasMore && row2.expanded) {
13601
- return /* @__PURE__ */ jsx12(
13602
- "span",
13603
- {
13604
- role: "button",
13605
- onClick: (e) => {
13606
- e.stopPropagation();
13607
- controller.loadMore(row2.id);
13608
- },
13609
- style: { cursor: "pointer", opacity: 0.7, fontSize: 9 },
13610
- children: "load more\u2026"
13767
+ const onPointerUp = useCallback8(
13768
+ (e) => {
13769
+ const press = pressRef.current;
13770
+ pressRef.current = null;
13771
+ try {
13772
+ e.currentTarget.releasePointerCapture(e.pointerId);
13773
+ } catch {
13611
13774
  }
13612
- );
13613
- }
13614
- if (row2.node.truncatedAt !== void 0) {
13615
- return /* @__PURE__ */ jsx12(Badge7, { tone: "warning", children: `first ${row2.node.truncatedAt}` });
13616
- }
13617
- if (row2.expanded && isEmptyContainer(row2.node)) {
13618
- return /* @__PURE__ */ jsx12("span", { style: { opacity: 0.4, fontSize: 9 }, children: "empty" });
13619
- }
13620
- return null;
13775
+ if (press?.dragging) controller.endDrag();
13776
+ },
13777
+ [controller]
13778
+ );
13779
+ const onKeyDown = useCallback8(
13780
+ (e) => {
13781
+ if (controller.getState().renaming) return;
13782
+ if (controller.handleKey({ key: e.key, shiftKey: e.shiftKey, ctrlKey: e.ctrlKey, metaKey: e.metaKey, altKey: e.altKey })) {
13783
+ e.preventDefault();
13784
+ e.stopPropagation();
13785
+ }
13786
+ },
13787
+ [controller]
13788
+ );
13789
+ return /* @__PURE__ */ jsx12(ScrollArea6, { ref: scrollRef, onScroll, onKeyDown, onPointerDown, onPointerMove, onPointerUp, tabIndex: 0, children });
13621
13790
  }
13622
- function NodeIcon({ icon }) {
13623
- if (icon.kind === "text") {
13624
- return /* @__PURE__ */ jsx12("span", { style: { fontSize: 11, lineHeight: 1, display: "flex", alignItems: "center" }, children: icon.value });
13625
- }
13626
- return /* @__PURE__ */ jsx12(TreeIcon, { name: icon.name });
13791
+ var TREE_RUNTIME_UNITS = ["tree/Scroller"];
13792
+ function treeRuntimeComponents(controller) {
13793
+ return {
13794
+ "tree/Scroller": ({ children }) => /* @__PURE__ */ jsx12(TreeScroller, { controller, children })
13795
+ };
13627
13796
  }
13628
13797
 
13629
13798
  // src/edit/tree/panel.ts
@@ -13832,7 +14001,10 @@ export {
13832
14001
  COLOR_DECLARATION,
13833
14002
  COLOR_RUNTIME_UNITS,
13834
14003
  COLOR_SPACES,
14004
+ BEHAVIOURS6 as COMMENTS_BEHAVIOURS,
14005
+ COMMENTS_DECLARATION,
13835
14006
  COMMENTS_PANEL_ID,
14007
+ COMMENTS_RUNTIME_UNITS,
13836
14008
  CURVE_PANEL_ID,
13837
14009
  ColorPanelController,
13838
14010
  ColorPanelView,
@@ -13881,11 +14053,15 @@ export {
13881
14053
  NODEGRAPH_PANEL_ID,
13882
14054
  NodeGraphPanelView,
13883
14055
  SPACE_LABEL,
14056
+ BEHAVIOURS7 as TREE_BEHAVIOURS,
14057
+ TREE_DECLARATION,
13884
14058
  TREE_PANEL_ID,
14059
+ TREE_RUNTIME_UNITS,
13885
14060
  Thumbnail,
13886
14061
  TreeController,
13887
14062
  TreeIcon,
13888
14063
  TreePanelView,
14064
+ TreeScroller,
13889
14065
  VIEWPORT_PANEL_ID,
13890
14066
  VIEWPORT_SLOTS,
13891
14067
  VIEW_STATE_OPS,
@@ -13899,12 +14075,14 @@ export {
13899
14075
  anchorStateOf,
13900
14076
  appliedCount,
13901
14077
  applyColorUi,
14078
+ applyCommentsUi,
13902
14079
  applyFindUi,
13903
14080
  applyHistoryUi,
13904
14081
  applyInspectorUi,
13905
14082
  applyLayersUi,
13906
14083
  applyPrecision,
13907
14084
  applyTangentDrag,
14085
+ applyTreeUi,
13908
14086
  ariaSliderProps,
13909
14087
  boxesIntersect,
13910
14088
  canConnect,
@@ -13922,6 +14100,8 @@ export {
13922
14100
  colorViewState,
13923
14101
  commentsManifest,
13924
14102
  commentsPanel,
14103
+ commentsViewManifest,
14104
+ commentsViewState,
13925
14105
  commitPolicyOf,
13926
14106
  componentsForSpace,
13927
14107
  computeSurfaceSize,
@@ -14064,6 +14244,9 @@ export {
14064
14244
  totalCount,
14065
14245
  treeManifest,
14066
14246
  treePanel,
14247
+ treeRuntimeComponents,
14248
+ treeViewManifest,
14249
+ treeViewState,
14067
14250
  usesLogScale,
14068
14251
  valueExtent,
14069
14252
  valueToPosition,