@xenosystem/blocks 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1199,9 +1199,10 @@ declare function fieldMatches(field: XenoInspectorField, term: string): boolean;
1199
1199
  /**
1200
1200
  * Project a schema into renderable sections.
1201
1201
  *
1202
- * Visibility-by-data: a section is dropped entirely when it has no matching fields AND no
1203
- * `emptyHint` — the panel never renders an empty box the user cannot act on. While a filter is
1204
- * active, `emptyHint`-only sections are also dropped (they are not search results).
1202
+ * Visibility-by-data: a section is dropped entirely when it has no matching fields AND nothing to
1203
+ * act on — no `emptyHint`, no `actions`, no `addMenu` — the panel never renders an empty box the
1204
+ * user cannot act on. (An action-only section — Canvas's align strip — IS something to act on.)
1205
+ * While a filter is active, fieldless sections are dropped regardless (they are not search results).
1205
1206
  */
1206
1207
  declare function projectSections(schema: XenoInspectorSchema, options?: ProjectOptions$1): ProjectedSection[];
1207
1208
  /** Whether a section is collapsed, honoring `defaultCollapsed` for sections the user never touched. */
@@ -6755,6 +6756,11 @@ interface XenoTreeGlyph {
6755
6756
  * and another names lucide icons. A single `icon: string` field forces every consumer to guess
6756
6757
  * which it is holding, and guessing renders `'📄'` as a missing-icon box.
6757
6758
  */
6759
+ /** What a node carries when dragged out of the panel (see `XenoTreeNode.dragOut`). */
6760
+ interface XenoTreeDragOut {
6761
+ kind: string;
6762
+ payload?: unknown;
6763
+ }
6758
6764
  type XenoTreeIcon = {
6759
6765
  kind: 'lucide';
6760
6766
  name: string;
@@ -6841,6 +6847,15 @@ interface XenoTreeNode {
6841
6847
  columns?: Record<string, string>;
6842
6848
  /** Hover actions. */
6843
6849
  actions?: XenoTreeAction[];
6850
+ /**
6851
+ * The node may be dragged OUT of the panel and dropped on the host's own surface — a library
6852
+ * component onto a canvas, a clip onto a timeline. `kind` is the host's vocabulary word
6853
+ * (`'component'`, `'clip'`); `payload` is carried back verbatim on the `dragOut` output. The drag
6854
+ * itself is the panel's pointer drag: when it is released outside the panel's rows, the panel
6855
+ * emits `dragOut` with the release point and the host decides what a drop there means. A node
6856
+ * without this is only reorderable inside the tree.
6857
+ */
6858
+ dragOut?: XenoTreeDragOut;
6844
6859
  /**
6845
6860
  * A stable identity for the *thing* this node points at — a resolved real path, an inode, a
6846
6861
  * document id.
@@ -7360,14 +7375,19 @@ declare class TreeController {
7360
7375
  /** Update the hovered drop target. */
7361
7376
  dragOver(targetId: string, offsetY: number): DropPosition;
7362
7377
  /**
7363
- * Complete the drag, emitting a `move` intent when the drop is legal.
7378
+ * Complete the drag, emitting a `move` intent when the drop is legal — or, when the release was
7379
+ * OUTSIDE the panel (`outside` given) and every dragged node declares `dragOut` of one kind, a
7380
+ * `dragOut` event carrying the release point for the host to hit-test.
7364
7381
  *
7365
7382
  * A refused drop emits **nothing** — and the refusal that matters most is the descendant one,
7366
7383
  * which lives in `@xenosystem/tree-core` because two shipping apps delete a whole subtree without it.
7367
7384
  *
7368
7385
  * @returns `true` if a move intent was emitted.
7369
7386
  */
7370
- endDrag(): boolean;
7387
+ endDrag(outside?: {
7388
+ clientX: number;
7389
+ clientY: number;
7390
+ }): boolean;
7371
7391
  /** Abandon the drag. */
7372
7392
  cancelDrag(): void;
7373
7393
  /** Descendants of a node, through the shared recursive walk. */
@@ -1970,7 +1970,8 @@ function projectSections(schema, options = {}) {
1970
1970
  for (const section of schema.sections) {
1971
1971
  const fields = filtering ? section.fields.filter((f) => fieldMatches(f, term)) : section.fields;
1972
1972
  const empty = fields.length === 0;
1973
- if (empty && (filtering || !section.emptyHint)) continue;
1973
+ const actionable = !!section.emptyHint || (section.actions?.length ?? 0) > 0 || (section.addMenu?.length ?? 0) > 0;
1974
+ if (empty && (filtering || !actionable)) continue;
1974
1975
  const collapsed = !filtering && !!section.collapsible && isCollapsed(section, collapsedSet);
1975
1976
  out.push({
1976
1977
  section,
@@ -13113,16 +13114,29 @@ var TreeController = class {
13113
13114
  return result.position;
13114
13115
  }
13115
13116
  /**
13116
- * Complete the drag, emitting a `move` intent when the drop is legal.
13117
+ * Complete the drag, emitting a `move` intent when the drop is legal — or, when the release was
13118
+ * OUTSIDE the panel (`outside` given) and every dragged node declares `dragOut` of one kind, a
13119
+ * `dragOut` event carrying the release point for the host to hit-test.
13117
13120
  *
13118
13121
  * A refused drop emits **nothing** — and the refusal that matters most is the descendant one,
13119
13122
  * which lives in `@xenosystem/tree-core` because two shipping apps delete a whole subtree without it.
13120
13123
  *
13121
13124
  * @returns `true` if a move intent was emitted.
13122
13125
  */
13123
- endDrag() {
13126
+ endDrag(outside) {
13124
13127
  const drag = this.drag;
13125
13128
  this.drag = null;
13129
+ if (drag && outside) {
13130
+ const nodes = drag.ids.map((id) => this.source?.nodes[id]).filter((n) => !!n);
13131
+ const outs = nodes.map((n) => n.dragOut).filter((d) => !!d);
13132
+ if (nodes.length > 0 && outs.length === nodes.length && outs.every((d) => d.kind === outs[0].kind)) {
13133
+ this.host.emit("dragOut", { ids: nodes.map((n) => n.id), kind: outs[0].kind, payloads: outs.map((d) => d.payload), clientX: outside.clientX, clientY: outside.clientY });
13134
+ this.notify();
13135
+ return true;
13136
+ }
13137
+ this.notify();
13138
+ return false;
13139
+ }
13126
13140
  if (!drag || !drag.targetId || !drag.position) {
13127
13141
  this.notify();
13128
13142
  return false;
@@ -13247,6 +13261,12 @@ var treeManifest = {
13247
13261
  schema: WELL_KNOWN_PORT_SCHEMAS8.TREE_OP,
13248
13262
  description: "Everything the panel asks for \u2014 select, open, setExpanded, loadChildren, move, rename, create, delete, setProp, action. INTENTS ONLY: the host performs, pins ids, and pushes one undo entry."
13249
13263
  },
13264
+ {
13265
+ id: "dragOut",
13266
+ name: "Dragged Out",
13267
+ type: "object",
13268
+ description: "{ ids, kind, payloads, clientX, clientY } \u2014 a pointer drag of nodes that declare `dragOut` was released OUTSIDE the panel. The host hit-tests its own surface at the point and performs the drop (place an instance, insert a clip); the panel places nothing. Nodes without `dragOut` never emit this."
13269
+ },
13250
13270
  {
13251
13271
  id: "selection",
13252
13272
  name: "Selection",
@@ -13989,7 +14009,10 @@ function TreeScroller({ controller, children }) {
13989
14009
  e.currentTarget.releasePointerCapture(e.pointerId);
13990
14010
  } catch {
13991
14011
  }
13992
- if (press?.dragging) controller.endDrag();
14012
+ if (!press?.dragging) return;
14013
+ const r = scrollRef.current?.getBoundingClientRect();
14014
+ const inside = !!r && e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom;
14015
+ controller.endDrag(inside ? void 0 : { clientX: e.clientX, clientY: e.clientY });
13993
14016
  },
13994
14017
  [controller]
13995
14018
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "XENO Blocks — rung 3 of the front ladder: components composed into a contract-bearing unit (manifest: ports, intents, commands, agent surface), rendered by @xenosystem/workbench. ONE package, one subpath per family (/auth, /edit, /data, /time, /ops, /trust, /agent), one named export per block — XENO PACKAGE NAMING - STANDARD.md.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",