@xenosystem/blocks 0.16.1 → 0.18.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.
@@ -6756,12 +6756,28 @@ interface XenoTreeGlyph {
6756
6756
  * and another names lucide icons. A single `icon: string` field forces every consumer to guess
6757
6757
  * which it is holding, and guessing renders `'📄'` as a missing-icon box.
6758
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
+ }
6759
6764
  type XenoTreeIcon = {
6760
6765
  kind: 'lucide';
6761
6766
  name: string;
6762
6767
  } | {
6763
6768
  kind: 'text';
6764
6769
  value: string;
6770
+ }
6771
+ /**
6772
+ * A thumbnail the HOST rendered — a data or blob URL — drawn in the row's icon slot by
6773
+ * `chrome/Thumb`. The panel decodes and fetches nothing (the no-pixel-capability rule stands; the
6774
+ * pixels are the host's). Default frame 44×32.
6775
+ */
6776
+ | {
6777
+ kind: 'image';
6778
+ src: string;
6779
+ width?: number;
6780
+ height?: number;
6765
6781
  };
6766
6782
  /** What a node permits. Absent ⇒ permitted. */
6767
6783
  interface XenoTreeCaps {
@@ -6842,6 +6858,15 @@ interface XenoTreeNode {
6842
6858
  columns?: Record<string, string>;
6843
6859
  /** Hover actions. */
6844
6860
  actions?: XenoTreeAction[];
6861
+ /**
6862
+ * The node may be dragged OUT of the panel and dropped on the host's own surface — a library
6863
+ * component onto a canvas, a clip onto a timeline. `kind` is the host's vocabulary word
6864
+ * (`'component'`, `'clip'`); `payload` is carried back verbatim on the `dragOut` output. The drag
6865
+ * itself is the panel's pointer drag: when it is released outside the panel's rows, the panel
6866
+ * emits `dragOut` with the release point and the host decides what a drop there means. A node
6867
+ * without this is only reorderable inside the tree.
6868
+ */
6869
+ dragOut?: XenoTreeDragOut;
6845
6870
  /**
6846
6871
  * A stable identity for the *thing* this node points at — a resolved real path, an inode, a
6847
6872
  * document id.
@@ -6865,7 +6890,9 @@ interface XenoTreeNode {
6865
6890
  title?: string;
6866
6891
  }
6867
6892
  /** Which adapter produced this source. Presentation hint + telemetry; never behaviour. */
6868
- type XenoTreeSourceKind = 'scene-graph' | 'entity' | 'project-entities' | 'document-pages' | 'filesystem' | 'derived-outline' | 'org' | 'custom';
6893
+ type XenoTreeSourceKind = 'scene-graph' | 'entity' | 'project-entities' | 'document-pages'
6894
+ /** A library of reusable things — components, assets, presets — usually with `image` icons and `dragOut`. */
6895
+ | 'library' | 'filesystem' | 'derived-outline' | 'org' | 'custom';
6869
6896
  /**
6870
6897
  * The source-level lifecycle — one incumbent's six-state machine, hoisted.
6871
6898
  *
@@ -7361,14 +7388,19 @@ declare class TreeController {
7361
7388
  /** Update the hovered drop target. */
7362
7389
  dragOver(targetId: string, offsetY: number): DropPosition;
7363
7390
  /**
7364
- * Complete the drag, emitting a `move` intent when the drop is legal.
7391
+ * Complete the drag, emitting a `move` intent when the drop is legal — or, when the release was
7392
+ * OUTSIDE the panel (`outside` given) and every dragged node declares `dragOut` of one kind, a
7393
+ * `dragOut` event carrying the release point for the host to hit-test.
7365
7394
  *
7366
7395
  * A refused drop emits **nothing** — and the refusal that matters most is the descendant one,
7367
7396
  * which lives in `@xenosystem/tree-core` because two shipping apps delete a whole subtree without it.
7368
7397
  *
7369
7398
  * @returns `true` if a move intent was emitted.
7370
7399
  */
7371
- endDrag(): boolean;
7400
+ endDrag(outside?: {
7401
+ clientX: number;
7402
+ clientY: number;
7403
+ }): boolean;
7372
7404
  /** Abandon the drag. */
7373
7405
  cancelDrag(): void;
7374
7406
  /** Descendants of a node, through the shared recursive walk. */
@@ -7496,6 +7528,10 @@ interface IconHalf {
7496
7528
  iconName: string | null;
7497
7529
  /** Literal text (emoji) instead of a glyph. */
7498
7530
  iconText: string | null;
7531
+ /** A host-rendered thumbnail (`chrome/Thumb`). */
7532
+ iconSrc: string | null;
7533
+ iconWidth: number;
7534
+ iconHeight: number;
7499
7535
  }
7500
7536
  interface TreeViewAction extends IconHalf {
7501
7537
  key: string;
@@ -13114,16 +13114,29 @@ var TreeController = class {
13114
13114
  return result.position;
13115
13115
  }
13116
13116
  /**
13117
- * 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.
13118
13120
  *
13119
13121
  * A refused drop emits **nothing** — and the refusal that matters most is the descendant one,
13120
13122
  * which lives in `@xenosystem/tree-core` because two shipping apps delete a whole subtree without it.
13121
13123
  *
13122
13124
  * @returns `true` if a move intent was emitted.
13123
13125
  */
13124
- endDrag() {
13126
+ endDrag(outside) {
13125
13127
  const drag = this.drag;
13126
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
+ }
13127
13140
  if (!drag || !drag.targetId || !drag.position) {
13128
13141
  this.notify();
13129
13142
  return false;
@@ -13248,6 +13261,12 @@ var treeManifest = {
13248
13261
  schema: WELL_KNOWN_PORT_SCHEMAS8.TREE_OP,
13249
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."
13250
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
+ },
13251
13270
  {
13252
13271
  id: "selection",
13253
13272
  name: "Selection",
@@ -13563,12 +13582,13 @@ import {
13563
13582
  TextButton as TextButton7,
13564
13583
  Toolbar as Toolbar7,
13565
13584
  ToolbarGroup as ToolbarGroup5,
13566
- VirtualWindow as VirtualWindow3
13585
+ VirtualWindow as VirtualWindow3,
13586
+ Thumb
13567
13587
  } from "@xenosystem/components/chrome";
13568
13588
 
13569
13589
  // src/edit/tree/declaration.ts
13570
13590
  import { computeWindow as computeWindow3 } from "@xenosystem/tree-core";
13571
- var iconHalf = (icon) => icon.kind === "text" ? { iconName: null, iconText: icon.value } : { iconName: icon.name, iconText: null };
13591
+ var iconHalf = (icon) => icon.kind === "text" ? { iconName: null, iconText: icon.value, iconSrc: null, iconWidth: 0, iconHeight: 0 } : icon.kind === "image" ? { iconName: null, iconText: null, iconSrc: icon.src, iconWidth: icon.width ?? 44, iconHeight: icon.height ?? 32 } : { iconName: icon.name, iconText: null, iconSrc: null, iconWidth: 0, iconHeight: 0 };
13572
13592
  function treeViewState(c, options = {}) {
13573
13593
  const { overscan = 6, showSearch = true, columns, emptyHint } = options;
13574
13594
  const state = c.getState();
@@ -13750,6 +13770,7 @@ var nodeIcon = () => ({
13750
13770
  component: "fragment",
13751
13771
  children: [
13752
13772
  { component: "icons/TreeKind", when: { item: "iconName" }, props: { name: { item: "iconName" } } },
13773
+ { component: "chrome/Thumb", when: { item: "iconSrc" }, props: { src: { item: "iconSrc" }, width: { item: "iconWidth" }, height: { item: "iconHeight" } } },
13753
13774
  { component: "chrome/Text", when: { item: "iconText" }, props: { role: { value: "caption" } }, children: [text10({ item: "iconText" })] }
13754
13775
  ]
13755
13776
  });
@@ -13872,8 +13893,9 @@ function TreePanelView({ controller, ...options }) {
13872
13893
  useSyncExternalStore10(controller.subscribe, controller.getState, controller.getState);
13873
13894
  const view = treeViewState(controller, options);
13874
13895
  const ui10 = (intent) => applyTreeUi(controller, intent, options);
13875
- const icon = (name, textValue) => /* @__PURE__ */ jsxs11(Fragment9, { children: [
13896
+ const icon = (name, textValue, src = null, w = 0, h = 0) => /* @__PURE__ */ jsxs11(Fragment9, { children: [
13876
13897
  name ? /* @__PURE__ */ jsx12(TreeIcon, { name }) : null,
13898
+ src ? /* @__PURE__ */ jsx12(Thumb, { src, width: w, height: h }) : null,
13877
13899
  textValue ? /* @__PURE__ */ jsx12(Text9, { role: "caption", children: textValue }) : null
13878
13900
  ] });
13879
13901
  const row2 = (r) => /* @__PURE__ */ jsx12(
@@ -13888,7 +13910,7 @@ function TreePanelView({ controller, ...options }) {
13888
13910
  expanded: r.expanded,
13889
13911
  dropHint: r.dropHint,
13890
13912
  title: r.dropTitle,
13891
- icon: icon(r.iconName, r.iconText),
13913
+ icon: icon(r.iconName, r.iconText, r.iconSrc, r.iconWidth, r.iconHeight),
13892
13914
  label: /* @__PURE__ */ jsxs11(Fragment9, { children: [
13893
13915
  r.renaming ? /* @__PURE__ */ jsx12(Input5, { autoFocus: true, value: r.draft, label: "Name", onChange: (value) => ui10({ kind: "renameDraft", value }), onBlur: () => ui10({ kind: "commitRename" }), onKeyDown: (e) => ui10({ kind: "renameKey", key: e.key }) }) : null,
13894
13916
  !r.renaming ? r.label : null
@@ -13990,7 +14012,10 @@ function TreeScroller({ controller, children }) {
13990
14012
  e.currentTarget.releasePointerCapture(e.pointerId);
13991
14013
  } catch {
13992
14014
  }
13993
- if (press?.dragging) controller.endDrag();
14015
+ if (!press?.dragging) return;
14016
+ const r = scrollRef.current?.getBoundingClientRect();
14017
+ const inside = !!r && e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom;
14018
+ controller.endDrag(inside ? void 0 : { clientX: e.clientX, clientY: e.clientY });
13994
14019
  },
13995
14020
  [controller]
13996
14021
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.16.1",
3
+ "version": "0.18.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",
@@ -64,7 +64,7 @@
64
64
  "peerDependencies": {
65
65
  "@duckdb/duckdb-wasm": ">=1.28.0",
66
66
  "@xenosystem/block-sdk": ">=0.3.4 <1",
67
- "@xenosystem/components": ">=0.33.0 <1",
67
+ "@xenosystem/components": ">=0.34.0 <1",
68
68
  "@xenosystem/data-core": "^0.1.0",
69
69
  "@xenosystem/elements": ">=0.0.1",
70
70
  "@xenosystem/elements-react": ">=0.0.1",
@@ -81,7 +81,7 @@
81
81
  "@types/react": "^19.0.0",
82
82
  "@types/react-dom": "^19.0.0",
83
83
  "@xenosystem/block-sdk": "^0.3.4",
84
- "@xenosystem/components": "^0.33.0",
84
+ "@xenosystem/components": "^0.34.0",
85
85
  "@xenosystem/data-core": "^0.1.0",
86
86
  "@xenosystem/elements": "0.0.1",
87
87
  "@xenosystem/elements-react": "0.0.1",