@xenosystem/blocks 0.15.0 → 0.15.2

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.
@@ -242,6 +242,7 @@ function RedoIcon({ size = 11 }) {
242
242
 
243
243
  // src/edit/inspector/react/icons.tsx
244
244
  import Link from "@xenosystem/elements/elements/link";
245
+ import Copy2 from "@xenosystem/elements/elements/copy";
245
246
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
246
247
  var base = (size) => ({
247
248
  width: size,
@@ -271,6 +272,7 @@ function NextIcon({ size = 9 }) {
271
272
  return /* @__PURE__ */ jsx6("svg", { ...base(size), children: /* @__PURE__ */ jsx6("path", { d: "m9 18 6-6-6-6" }) });
272
273
  }
273
274
  var LinkIcon = fromElement(Link, 10);
275
+ var CopyIcon = fromElement(Copy2, 10);
274
276
  function ResetIcon({ size = 10 }) {
275
277
  return /* @__PURE__ */ jsxs5("svg", { ...base(size), children: [
276
278
  /* @__PURE__ */ jsx6("path", { d: "M3 12a9 9 0 1 0 3-6.7L3 8" }),
@@ -489,6 +491,7 @@ export {
489
491
  PrevIcon,
490
492
  NextIcon,
491
493
  LinkIcon,
494
+ CopyIcon,
492
495
  ResetIcon,
493
496
  XIcon2,
494
497
  KindGlyph,
@@ -12,7 +12,7 @@ import {
12
12
  RefreshGlyph,
13
13
  SortAscGlyph,
14
14
  SortDescGlyph
15
- } from "../chunk-4IDY5FHM.js";
15
+ } from "../chunk-LKYPCOTE.js";
16
16
  import "../chunk-2KG3PWR4.js";
17
17
 
18
18
  // src/data/table/panel.ts
@@ -981,8 +981,21 @@ interface XenoInspectorField {
981
981
  field: string;
982
982
  in: XenoFieldValue[];
983
983
  };
984
+ /**
985
+ * Inside `itemFields`: a sub-field with `token.bindable` carries its binding IN THE ITEM as
986
+ * `${subId}Token` (a token id or empty). The row's token menu reads it, and a pick rewrites the item
987
+ * and emits ONE `set` for the list — the same contract as a value edit, so a host binds a stroke's
988
+ * colour or an effect's shadow from the item it already receives.
989
+ */
984
990
  /** For a structured `list`: the label drawn on each item row (e.g. `Fill`, `Shadow`). */
985
991
  itemLabel?: string;
992
+ /**
993
+ * Draw a Copy button beside the field. The block writes the field's text to the clipboard itself
994
+ * when it has one; where it does not (a sandboxed frame), it emits `action { actionId: 'copy' }`
995
+ * with the value so the host can. Raised by Canvas's Dev handoff (2026-09-18): a read-only code
996
+ * block whose whole point is being copied.
997
+ */
998
+ copyable?: boolean;
986
999
  /** Axis labels for `vector` (e.g. `['X','Y']`). */
987
1000
  components?: string[];
988
1001
  /** Scalar ⇔ per-element expander (Canvas's corner radius). */
@@ -1424,6 +1437,11 @@ declare class InspectorPanelController {
1424
1437
  * normal field `set`; otherwise it is an `action` with `actionId: 'setEnabled'`.
1425
1438
  */
1426
1439
  toggleSectionEnabled(sectionId: string): boolean;
1440
+ /**
1441
+ * Copy a field's text. The clipboard is used when this runtime has one; otherwise the host gets
1442
+ * `action { sectionId, actionId: 'copy', value }` and copies on the panel's behalf.
1443
+ */
1444
+ copyField(fieldId: string): boolean;
1427
1445
  /** Run a section action button. */
1428
1446
  runAction(sectionId: string, actionId: string): void;
1429
1447
  /** Pick an entry from a section's Add menu. */
@@ -1685,6 +1703,7 @@ interface InspectorViewField {
1685
1703
  showToken: boolean;
1686
1704
  tokenActionLabel: string;
1687
1705
  showReset: boolean;
1706
+ showCopy: boolean;
1688
1707
  showInfoNote: boolean;
1689
1708
  infoNote?: string;
1690
1709
  /** The field record itself — what `inspector/FieldWidget` renders. */
@@ -1774,6 +1793,9 @@ type InspectorUiIntent = {
1774
1793
  } | {
1775
1794
  kind: 'resetField';
1776
1795
  fieldId: string;
1796
+ } | {
1797
+ kind: 'copyField';
1798
+ fieldId: string;
1777
1799
  };
1778
1800
  /** The link menu's items: the field's group of tokens (all, when it names none), then Unbind and Create-from-value. */
1779
1801
  declare function tokenItemsFor(field: XenoInspectorField, tokens: readonly XenoInspectorToken[], bound: boolean): InspectorViewField['tokenItems'];
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  CollapseGlyph,
3
+ CopyIcon,
3
4
  DiamondIcon,
4
5
  EntryIcon,
5
6
  ExpandGlyph,
@@ -21,7 +22,7 @@ import {
21
22
  VisibilityIcon,
22
23
  XIcon,
23
24
  XIcon2
24
- } from "../chunk-4IDY5FHM.js";
25
+ } from "../chunk-LKYPCOTE.js";
25
26
  import "../chunk-2KG3PWR4.js";
26
27
 
27
28
  // src/edit/layers/panel.ts
@@ -2011,7 +2012,9 @@ function findField(schema, fieldId) {
2011
2012
  if (!list || !sub) return void 0;
2012
2013
  const item = (Array.isArray(list.value) ? list.value : []).find((it) => it?.id === v.itemId);
2013
2014
  if (!item) return void 0;
2014
- return { ...sub, id: fieldId, value: item[v.subId], disabled: sub.disabled || list.disabled, readOnly: sub.readOnly || list.readOnly };
2015
+ const tokenId = sub.token?.bindable ? item[`${v.subId}Token`] : void 0;
2016
+ const token = sub.token ? { ...sub.token, ...typeof tokenId === "string" && tokenId ? { tokenId } : { tokenId: null } } : void 0;
2017
+ return { ...sub, id: fieldId, value: item[v.subId], disabled: sub.disabled || list.disabled, readOnly: sub.readOnly || list.readOnly, ...token ? { token } : {} };
2015
2018
  }
2016
2019
  function findSectionOf(schema, fieldId) {
2017
2020
  return schema.sections.find((s) => s.fields.some((f) => f.id === fieldId));
@@ -2629,6 +2632,23 @@ var InspectorPanelController = class {
2629
2632
  this.emit({ type: "action", sectionId, actionId: "setEnabled", value: next });
2630
2633
  return true;
2631
2634
  }
2635
+ /**
2636
+ * Copy a field's text. The clipboard is used when this runtime has one; otherwise the host gets
2637
+ * `action { sectionId, actionId: 'copy', value }` and copies on the panel's behalf.
2638
+ */
2639
+ copyField(fieldId) {
2640
+ const field = findField(this.schema, fieldId);
2641
+ if (!field) return false;
2642
+ const text11 = String(this.getFieldValue(fieldId) ?? "");
2643
+ const clip = globalThis.navigator?.clipboard;
2644
+ if (clip?.writeText) {
2645
+ void clip.writeText(text11).catch(() => void 0);
2646
+ return true;
2647
+ }
2648
+ const sectionId = findSectionOf(this.schema, fieldId)?.id ?? "";
2649
+ this.emit({ type: "action", sectionId, actionId: "copy", value: text11 });
2650
+ return true;
2651
+ }
2632
2652
  /** Run a section action button. */
2633
2653
  runAction(sectionId, actionId) {
2634
2654
  this.emit({ type: "action", sectionId, actionId });
@@ -2673,6 +2693,13 @@ var InspectorPanelController = class {
2673
2693
  bindToken(fieldId, tokenId) {
2674
2694
  const field = findField(this.schema, fieldId);
2675
2695
  if (!field) return false;
2696
+ const virtual = parseListFieldId(fieldId);
2697
+ if (virtual) {
2698
+ const items = this.getFieldValue(virtual.listId) ?? [];
2699
+ const next = items.map((it) => it.id === virtual.itemId ? { ...it, [`${virtual.subId}Token`]: tokenId ?? "" } : it);
2700
+ this.emit({ type: "set", targetIds: this.targetIds(), fieldId: virtual.listId, value: next });
2701
+ return true;
2702
+ }
2676
2703
  this.emit({ type: "bindToken", targetIds: this.targetIds(), fieldId, tokenId });
2677
2704
  return true;
2678
2705
  }
@@ -3052,6 +3079,7 @@ function inspectorViewState(c) {
3052
3079
  showToken: showTokens && field.token?.bindable !== false,
3053
3080
  tokenActionLabel: bound ? "Unbind token" : "Bind token",
3054
3081
  showReset: field.defaultValue !== void 0 && isFieldEditable(field),
3082
+ showCopy: !!field.copyable,
3055
3083
  showInfoNote: !!field.description && field.type === "info",
3056
3084
  infoNote: field.description,
3057
3085
  field
@@ -3114,6 +3142,9 @@ function applyInspectorUi(c, intent) {
3114
3142
  case "resetField":
3115
3143
  c.resetField(intent.fieldId);
3116
3144
  break;
3145
+ case "copyField":
3146
+ c.copyField(intent.fieldId);
3147
+ break;
3117
3148
  }
3118
3149
  }
3119
3150
  var inspectorViewManifest = {
@@ -3235,7 +3266,8 @@ var INSPECTOR_DECLARATION = {
3235
3266
  props: { size: { value: "sm" }, trigger: { node: { component: "icons/Link" } }, items: { item: "tokenItems" }, label: { item: "tokenActionLabel" }, align: { value: "end" } },
3236
3267
  on: { onSelect: ui2("pickToken", { fieldId: { item: "id" }, itemId: { arg: "" } }) }
3237
3268
  },
3238
- iconButton("Reset", { value: "Reset to default" }, ui2("resetField", { fieldId: { item: "id" } }), { when: { item: "showReset" } })
3269
+ iconButton("Reset", { value: "Reset to default" }, ui2("resetField", { fieldId: { item: "id" } }), { when: { item: "showReset" } }),
3270
+ iconButton("Copy", { value: "Copy" }, ui2("copyField", { fieldId: { item: "id" } }), { when: { item: "showCopy" } })
3239
3271
  ]
3240
3272
  }
3241
3273
  }
@@ -3266,15 +3298,16 @@ var INSPECTOR_DECLARATION = {
3266
3298
  };
3267
3299
 
3268
3300
  // src/edit/inspector/react/InspectorPanelView.tsx
3269
- import { Badge as Badge2, Column as Column2, EmptyState as EmptyState2, Field, FieldGroup, IconButton as IconButton3, Menu, ScrollArea as ScrollArea2, SearchField as SearchField2, Section, Select as Select2, Stack as Stack2, Text as Text3 } from "@xenosystem/components/chrome";
3301
+ import { Badge as Badge2, Column as Column2, EmptyState as EmptyState2, Field, FieldGroup, IconButton as IconButton3, Menu as Menu2, ScrollArea as ScrollArea2, SearchField as SearchField2, Section, Select as Select2, Stack as Stack2, Text as Text3 } from "@xenosystem/components/chrome";
3270
3302
 
3271
3303
  // src/edit/inspector/react/widgets.tsx
3272
3304
  import { useCallback as useCallback2, useRef as useRef3 } from "react";
3273
- import { Fragment as Fragment2 } from "react";
3305
+ import "react";
3274
3306
  import {
3275
3307
  AxisLabel,
3276
3308
  IconButton as IconButton2,
3277
3309
  Input as Input2,
3310
+ Menu,
3278
3311
  ProportionBar,
3279
3312
  SegmentedControl,
3280
3313
  Select,
@@ -3561,7 +3594,27 @@ function ListWidget({ field, controller, renderField }) {
3561
3594
  field.itemLabel ? /* @__PURE__ */ jsx3(Text2, { role: "caption", children: `${field.itemLabel} ${i + 1}` }) : null,
3562
3595
  field.itemFields.filter((sub) => itemShowsSubField(sub, item)).map((sub) => {
3563
3596
  const virtual = controller.getField(`${field.id}[${item.id}].${sub.id}`);
3564
- return virtual ? /* @__PURE__ */ jsx3(Fragment2, { children: renderField(virtual) }, sub.id) : null;
3597
+ if (!virtual) return null;
3598
+ const bound = !!virtual.token?.tokenId;
3599
+ const tokens = controller.getTokens();
3600
+ const menu = sub.token?.bindable && controller.areTokensShown() ? /* @__PURE__ */ jsx3(
3601
+ Menu,
3602
+ {
3603
+ size: "sm",
3604
+ trigger: /* @__PURE__ */ jsx3(LinkIcon, {}),
3605
+ items: tokenItemsFor(virtual, tokens, bound),
3606
+ label: bound ? "Unbind token" : "Bind token",
3607
+ align: "end",
3608
+ onSelect: (id) => {
3609
+ if (id === "__unbind") controller.bindToken(virtual.id, null);
3610
+ else if (id.startsWith("token:")) controller.bindToken(virtual.id, id.slice(6));
3611
+ }
3612
+ }
3613
+ ) : null;
3614
+ return /* @__PURE__ */ jsxs2(Stack, { direction: "row", gap: 2, pad: "none", children: [
3615
+ bound ? /* @__PURE__ */ jsx3(Text2, { role: "meta", children: virtual.token?.tokenName ?? tokens.find((t) => t.id === virtual.token?.tokenId)?.name ?? virtual.token?.tokenId }) : renderField(virtual),
3616
+ menu
3617
+ ] }, sub.id);
3565
3618
  })
3566
3619
  ] }) : /* @__PURE__ */ jsx3(Text2, { role: "meta", children: typeof item === "string" ? item : JSON.stringify(item) }),
3567
3620
  editable ? /* @__PURE__ */ jsxs2(Fragment3, { children: [
@@ -3646,7 +3699,7 @@ function SectionView({ section, controller }) {
3646
3699
  badge: section.badge ? /* @__PURE__ */ jsx4(Badge2, { children: section.badge }) : null,
3647
3700
  actions: hasActions ? /* @__PURE__ */ jsxs3(Fragment4, { children: [
3648
3701
  section.actions.map((action) => /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ActionGlyph, { glyph: action.glyph }), label: action.label, disabled: action.disabled, onClick: () => ui10({ kind: "runAction", sectionId: section.id, actionId: action.id }) }, action.id)),
3649
- section.hasAddMenu ? /* @__PURE__ */ jsx4(Menu, { size: "sm", trigger: "\uFF0B", items: section.addItems, label: section.addLabel, align: "end", onSelect: (id) => ui10({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3702
+ section.hasAddMenu ? /* @__PURE__ */ jsx4(Menu2, { size: "sm", trigger: "\uFF0B", items: section.addItems, label: section.addLabel, align: "end", onSelect: (id) => ui10({ kind: "addItem", sectionId: section.id, itemId: id }) }) : null
3650
3703
  ] }) : null,
3651
3704
  children: [
3652
3705
  section.showEmptyHint ? /* @__PURE__ */ jsx4(Stack2, { pad: "inset", children: /* @__PURE__ */ jsx4(Text3, { role: "caption", children: section.emptyHint }) }) : null,
@@ -3675,8 +3728,9 @@ function FieldRow({ row: row2, controller }) {
3675
3728
  trailing: /* @__PURE__ */ jsxs3(Fragment4, { children: [
3676
3729
  row2.showEasing ? /* @__PURE__ */ jsx4(Select2, { size: "sm", options: row2.easingOptions, value: row2.easing, label: "Easing", onChange: (v) => ui10({ kind: "setEasing", fieldId: row2.id, easing: v }) }) : null,
3677
3730
  row2.showKeyframes ? /* @__PURE__ */ jsx4(Badge2, { size: "sm", children: row2.keyframeChip }) : null,
3678
- row2.showToken ? /* @__PURE__ */ jsx4(Menu, { size: "sm", trigger: /* @__PURE__ */ jsx4(LinkIcon, {}), items: row2.tokenItems, label: row2.tokenActionLabel, align: "end", onSelect: (id) => ui10({ kind: "pickToken", fieldId: row2.id, itemId: id }) }) : null,
3679
- row2.showReset ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ResetIcon, {}), label: "Reset to default", onClick: () => ui10({ kind: "resetField", fieldId: row2.id }) }) : null
3731
+ row2.showToken ? /* @__PURE__ */ jsx4(Menu2, { size: "sm", trigger: /* @__PURE__ */ jsx4(LinkIcon, {}), items: row2.tokenItems, label: row2.tokenActionLabel, align: "end", onSelect: (id) => ui10({ kind: "pickToken", fieldId: row2.id, itemId: id }) }) : null,
3732
+ row2.showReset ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(ResetIcon, {}), label: "Reset to default", onClick: () => ui10({ kind: "resetField", fieldId: row2.id }) }) : null,
3733
+ row2.showCopy ? /* @__PURE__ */ jsx4(IconButton3, { size: "sm", icon: /* @__PURE__ */ jsx4(CopyIcon, {}), label: "Copy", onClick: () => ui10({ kind: "copyField", fieldId: row2.id }) }) : null
3680
3734
  ] }),
3681
3735
  children: [
3682
3736
  row2.bound ? /* @__PURE__ */ jsx4(Badge2, { title: row2.tokenTitle, children: row2.tokenLabel }) : null,
@@ -2,7 +2,7 @@ import {
2
2
  ICONS,
3
3
  ICON_UNITS,
4
4
  Svg
5
- } from "../chunk-4IDY5FHM.js";
5
+ } from "../chunk-LKYPCOTE.js";
6
6
  import "../chunk-2KG3PWR4.js";
7
7
  export {
8
8
  ICONS,
package/dist/ops/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  ICONS,
11
11
  PlusGlyph,
12
12
  XGlyph
13
- } from "../chunk-4IDY5FHM.js";
13
+ } from "../chunk-LKYPCOTE.js";
14
14
  import "../chunk-2KG3PWR4.js";
15
15
 
16
16
  // src/ops/console/panel.ts
@@ -13,7 +13,7 @@ import {
13
13
  StopIcon,
14
14
  TRANSPORT_ICONS,
15
15
  TRANSPORT_RUNTIME_UNITS
16
- } from "../chunk-4IDY5FHM.js";
16
+ } from "../chunk-LKYPCOTE.js";
17
17
  import {
18
18
  __reExport
19
19
  } from "../chunk-2KG3PWR4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
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",