@elaraai/east-ui 1.0.28 → 1.0.30

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.
Files changed (188) hide show
  1. package/README.md +3 -3
  2. package/dist/src/charts/chart/index.d.ts +102 -0
  3. package/dist/src/charts/chart/index.d.ts.map +1 -1
  4. package/dist/src/charts/chart/index.js +30 -11
  5. package/dist/src/charts/chart/index.js.map +1 -1
  6. package/dist/src/charts/spec/index.d.ts +204 -0
  7. package/dist/src/charts/spec/index.d.ts.map +1 -1
  8. package/dist/src/charts/spec/types.d.ts +3 -11
  9. package/dist/src/charts/spec/types.d.ts.map +1 -1
  10. package/dist/src/charts/spec/types.js +5 -20
  11. package/dist/src/charts/spec/types.js.map +1 -1
  12. package/dist/src/collections/blend/index.d.ts.map +1 -1
  13. package/dist/src/collections/blend/index.js +2 -1
  14. package/dist/src/collections/blend/index.js.map +1 -1
  15. package/dist/src/collections/board/index.d.ts +490 -0
  16. package/dist/src/collections/board/index.d.ts.map +1 -0
  17. package/dist/src/collections/board/index.js +282 -0
  18. package/dist/src/collections/board/index.js.map +1 -0
  19. package/dist/src/collections/board/types.d.ts +308 -0
  20. package/dist/src/collections/board/types.d.ts.map +1 -0
  21. package/dist/src/collections/board/types.js +156 -0
  22. package/dist/src/collections/board/types.js.map +1 -0
  23. package/dist/src/collections/calendar/index.d.ts.map +1 -1
  24. package/dist/src/collections/calendar/index.js +2 -1
  25. package/dist/src/collections/calendar/index.js.map +1 -1
  26. package/dist/src/collections/gantt/index.d.ts +1 -1
  27. package/dist/src/collections/gantt/index.d.ts.map +1 -1
  28. package/dist/src/collections/gantt/index.js +45 -52
  29. package/dist/src/collections/gantt/index.js.map +1 -1
  30. package/dist/src/collections/index.d.ts +1 -0
  31. package/dist/src/collections/index.d.ts.map +1 -1
  32. package/dist/src/collections/index.js +1 -0
  33. package/dist/src/collections/index.js.map +1 -1
  34. package/dist/src/collections/library/index.d.ts.map +1 -1
  35. package/dist/src/collections/library/index.js +2 -1
  36. package/dist/src/collections/library/index.js.map +1 -1
  37. package/dist/src/collections/map/index.d.ts.map +1 -1
  38. package/dist/src/collections/map/index.js +5 -4
  39. package/dist/src/collections/map/index.js.map +1 -1
  40. package/dist/src/collections/matrix/index.d.ts.map +1 -1
  41. package/dist/src/collections/matrix/index.js +2 -1
  42. package/dist/src/collections/matrix/index.js.map +1 -1
  43. package/dist/src/collections/planner/index.d.ts.map +1 -1
  44. package/dist/src/collections/planner/index.js +2 -1
  45. package/dist/src/collections/planner/index.js.map +1 -1
  46. package/dist/src/collections/roster/index.d.ts +3 -0
  47. package/dist/src/collections/roster/index.d.ts.map +1 -1
  48. package/dist/src/collections/roster/index.js +4 -2
  49. package/dist/src/collections/roster/index.js.map +1 -1
  50. package/dist/src/collections/roster/types.d.ts +3 -0
  51. package/dist/src/collections/roster/types.d.ts.map +1 -1
  52. package/dist/src/collections/roster/types.js +3 -0
  53. package/dist/src/collections/roster/types.js.map +1 -1
  54. package/dist/src/collections/schematic/index.d.ts +537 -709
  55. package/dist/src/collections/schematic/index.d.ts.map +1 -1
  56. package/dist/src/collections/schematic/index.js +289 -9
  57. package/dist/src/collections/schematic/index.js.map +1 -1
  58. package/dist/src/collections/schematic/types.d.ts +562 -841
  59. package/dist/src/collections/schematic/types.d.ts.map +1 -1
  60. package/dist/src/collections/schematic/types.js +416 -46
  61. package/dist/src/collections/schematic/types.js.map +1 -1
  62. package/dist/src/collections/table/index.d.ts +19 -11
  63. package/dist/src/collections/table/index.d.ts.map +1 -1
  64. package/dist/src/collections/table/index.js +65 -78
  65. package/dist/src/collections/table/index.js.map +1 -1
  66. package/dist/src/component.d.ts +1572 -63
  67. package/dist/src/component.d.ts.map +1 -1
  68. package/dist/src/component.js +55 -15
  69. package/dist/src/component.js.map +1 -1
  70. package/dist/src/contracts/format.d.ts +39 -0
  71. package/dist/src/contracts/format.d.ts.map +1 -0
  72. package/dist/src/contracts/format.js +38 -0
  73. package/dist/src/contracts/format.js.map +1 -0
  74. package/dist/src/contracts/index.d.ts +1 -0
  75. package/dist/src/contracts/index.d.ts.map +1 -1
  76. package/dist/src/contracts/index.js +1 -0
  77. package/dist/src/contracts/index.js.map +1 -1
  78. package/dist/src/contracts/slice-affordances.d.ts +13 -3
  79. package/dist/src/contracts/slice-affordances.d.ts.map +1 -1
  80. package/dist/src/contracts/slice-affordances.js +12 -2
  81. package/dist/src/contracts/slice-affordances.js.map +1 -1
  82. package/dist/src/index.d.ts +3 -2
  83. package/dist/src/index.d.ts.map +1 -1
  84. package/dist/src/index.js +3 -2
  85. package/dist/src/index.js.map +1 -1
  86. package/dist/src/internal.d.ts +1 -1
  87. package/dist/src/internal.d.ts.map +1 -1
  88. package/dist/src/internal.js +1 -1
  89. package/dist/src/internal.js.map +1 -1
  90. package/dist/src/platform/slice/impl.d.ts +1 -0
  91. package/dist/src/platform/slice/impl.d.ts.map +1 -1
  92. package/dist/src/platform/slice/impl.js +85 -34
  93. package/dist/src/platform/slice/impl.js.map +1 -1
  94. package/dist/src/platform/slice/index.d.ts +2098 -29
  95. package/dist/src/platform/slice/index.d.ts.map +1 -1
  96. package/dist/src/platform/slice/index.js +145 -12
  97. package/dist/src/platform/slice/index.js.map +1 -1
  98. package/dist/src/runtime/collections/board.d.ts +73 -0
  99. package/dist/src/runtime/collections/board.d.ts.map +1 -0
  100. package/dist/src/runtime/collections/board.js +68 -0
  101. package/dist/src/runtime/collections/board.js.map +1 -0
  102. package/dist/src/runtime/collections/index.d.ts +1 -0
  103. package/dist/src/runtime/collections/index.d.ts.map +1 -1
  104. package/dist/src/runtime/collections/index.js +1 -0
  105. package/dist/src/runtime/collections/index.js.map +1 -1
  106. package/dist/src/runtime/collections/schematic.d.ts +11 -4
  107. package/dist/src/runtime/collections/schematic.d.ts.map +1 -1
  108. package/dist/src/runtime/collections/schematic.js +8 -2
  109. package/dist/src/runtime/collections/schematic.js.map +1 -1
  110. package/dist/src/runtime/slice.d.ts +7 -3
  111. package/dist/src/runtime/slice.d.ts.map +1 -1
  112. package/dist/src/runtime/slice.js +62 -14
  113. package/dist/src/runtime/slice.js.map +1 -1
  114. package/dist/src/shared/reify.d.ts +76 -0
  115. package/dist/src/shared/reify.d.ts.map +1 -0
  116. package/dist/src/shared/reify.js +85 -0
  117. package/dist/src/shared/reify.js.map +1 -0
  118. package/dist/src/slice/breakdown/index.d.ts +100 -0
  119. package/dist/src/slice/breakdown/index.d.ts.map +1 -1
  120. package/dist/src/slice/breakdown/types.d.ts +100 -0
  121. package/dist/src/slice/breakdown/types.d.ts.map +1 -1
  122. package/dist/src/slice/cohort/index.d.ts +787 -10
  123. package/dist/src/slice/cohort/index.d.ts.map +1 -1
  124. package/dist/src/slice/cohort/index.js +33 -10
  125. package/dist/src/slice/cohort/index.js.map +1 -1
  126. package/dist/src/slice/cohort/types.d.ts +225 -99
  127. package/dist/src/slice/cohort/types.d.ts.map +1 -1
  128. package/dist/src/slice/cohort/types.js +31 -9
  129. package/dist/src/slice/cohort/types.js.map +1 -1
  130. package/dist/src/slice/filter/index.d.ts +100 -0
  131. package/dist/src/slice/filter/index.d.ts.map +1 -1
  132. package/dist/src/slice/filter/types.d.ts +100 -0
  133. package/dist/src/slice/filter/types.d.ts.map +1 -1
  134. package/dist/src/slice/index.d.ts +2862 -126
  135. package/dist/src/slice/index.d.ts.map +1 -1
  136. package/dist/src/slice/index.js +6 -4
  137. package/dist/src/slice/index.js.map +1 -1
  138. package/dist/src/slice/legend/index.d.ts +124 -7
  139. package/dist/src/slice/legend/index.d.ts.map +1 -1
  140. package/dist/src/slice/legend/index.js +20 -10
  141. package/dist/src/slice/legend/index.js.map +1 -1
  142. package/dist/src/slice/legend/types.d.ts +233 -108
  143. package/dist/src/slice/legend/types.d.ts.map +1 -1
  144. package/dist/src/slice/legend/types.js +26 -5
  145. package/dist/src/slice/legend/types.js.map +1 -1
  146. package/dist/src/slice/rail/index.d.ts +22 -0
  147. package/dist/src/slice/rail/index.d.ts.map +1 -1
  148. package/dist/src/slice/rail/index.js +9 -1
  149. package/dist/src/slice/rail/index.js.map +1 -1
  150. package/dist/src/slice/range/index.d.ts +100 -0
  151. package/dist/src/slice/range/index.d.ts.map +1 -1
  152. package/dist/src/slice/range/types.d.ts +100 -0
  153. package/dist/src/slice/range/types.d.ts.map +1 -1
  154. package/dist/src/slice/search/index.d.ts +100 -0
  155. package/dist/src/slice/search/index.d.ts.map +1 -1
  156. package/dist/src/slice/search/types.d.ts +100 -0
  157. package/dist/src/slice/search/types.d.ts.map +1 -1
  158. package/dist/src/slice/summary/index.d.ts +100 -0
  159. package/dist/src/slice/summary/index.d.ts.map +1 -1
  160. package/dist/src/slice/summary/types.d.ts +100 -0
  161. package/dist/src/slice/summary/types.d.ts.map +1 -1
  162. package/dist/test/collections/board.examples.d.ts +7 -0
  163. package/dist/test/collections/board.examples.d.ts.map +1 -0
  164. package/dist/test/collections/board.examples.js +191 -0
  165. package/dist/test/collections/board.examples.js.map +1 -0
  166. package/dist/test/collections/gantt.examples.js +2 -2
  167. package/dist/test/collections/gantt.examples.js.map +1 -1
  168. package/dist/test/collections/matrix.examples.d.ts.map +1 -1
  169. package/dist/test/collections/matrix.examples.js +4 -3
  170. package/dist/test/collections/matrix.examples.js.map +1 -1
  171. package/dist/test/collections/schematic.examples.d.ts +11 -1
  172. package/dist/test/collections/schematic.examples.d.ts.map +1 -1
  173. package/dist/test/collections/schematic.examples.js +354 -24
  174. package/dist/test/collections/schematic.examples.js.map +1 -1
  175. package/dist/test/collections/table.examples.d.ts.map +1 -1
  176. package/dist/test/collections/table.examples.js +9 -6
  177. package/dist/test/collections/table.examples.js.map +1 -1
  178. package/dist/test/disclosure/story.examples.js +1 -1
  179. package/dist/test/disclosure/story.examples.js.map +1 -1
  180. package/dist/test/display/combine.examples.js +2 -2
  181. package/dist/test/display/combine.examples.js.map +1 -1
  182. package/dist/test/display/editable-chip.examples.js +1 -1
  183. package/dist/test/display/editable-chip.examples.js.map +1 -1
  184. package/dist/test/slice/slice.examples.d.ts +5 -0
  185. package/dist/test/slice/slice.examples.d.ts.map +1 -1
  186. package/dist/test/slice/slice.examples.js +216 -6
  187. package/dist/test/slice/slice.examples.js.map +1 -1
  188. package/package.json +3 -3
@@ -4,11 +4,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "@elaraai/east-ui/jsx-runtime";
4
4
  * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
5
5
  */
6
6
  /** @jsxImportSource @elaraai/east-ui */
7
- import { East, ArrayType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from "@elaraai/east";
7
+ import { East, ArrayType, BooleanType, FloatType, IntegerType, NullType, OptionType, StringType, StructType, example, variant, some, none } from "@elaraai/east";
8
8
  import { State, StatusTokenType, UIComponentType } from "@elaraai/east-ui";
9
- import { Reactive, Schematic, Slice, Text, VStack } from "@elaraai/east-ui";
9
+ import { HStack, Reactive, Schematic, Slice, Slider, Sparkline, Switch, Text, VStack } from "@elaraai/east-ui";
10
10
  export const schematicPlant = example({
11
- keywords: ["Schematic", "canvas", "items", "zones", "links", "meter", "status", "hatch"],
11
+ keywords: ["Schematic", "canvas", "items", "zones", "links", "meter", "status", "hatch", "label", "metric", "parallel", "fan-out", "flow"],
12
12
  description: "Plant floor — tanks and lines in halls with a walkway band and pipe runs",
13
13
  fn: East.function([], UIComponentType, ($) => {
14
14
  const equipment = $.const([
@@ -28,11 +28,15 @@ export const schematicPlant = example({
28
28
  ]);
29
29
  // Already the resolved link type — passed through with no `link` mapper.
30
30
  const orthogonal = variant("orthogonal", { corner: none });
31
+ // p2 + p2b share the same endpoint pair — the renderer fans them into
32
+ // parallel lanes (#180) so both edges (feed + CIP return) stay readable;
33
+ // labels/metrics render mid-path at label zoom.
31
34
  const pipes = $.const([
32
- { key: "p1", from: "UNIT-04", to: "LINE-2", style: Schematic.solid(), route: orthogonal, via: [] },
33
- { key: "p2", from: "UNIT-05", to: "LINE-2", style: Schematic.solid(), route: orthogonal, via: [] },
34
- { key: "p3", from: "UNIT-06", to: "LINE-2", style: Schematic.solid(), route: orthogonal, via: [] },
35
- { key: "p4", from: "QA-1", to: "BAY-OUT", style: Schematic.dashed(), route: orthogonal, via: [{ x: 26.5, y: 6.8 }] },
35
+ { key: "p1", from: "UNIT-04", to: "LINE-2", label: some("feed"), metric: some("12 m³/h"), style: Schematic.solid(), route: orthogonal, via: [], layer: none },
36
+ { key: "p2", from: "UNIT-05", to: "LINE-2", label: some("feed"), metric: some("9 m³/h"), style: Schematic.solid(), route: orthogonal, via: [], layer: none },
37
+ { key: "p2b", from: "UNIT-05", to: "LINE-2", label: some("CIP return"), metric: none, style: Schematic.dashed(), route: orthogonal, via: [], layer: none },
38
+ { key: "p3", from: "UNIT-06", to: "LINE-2", label: none, metric: none, style: Schematic.solid(), route: orthogonal, via: [], layer: none },
39
+ { key: "p4", from: "QA-1", to: "BAY-OUT", label: none, metric: some("3 pallets/h"), style: Schematic.dashed(), route: orthogonal, via: [{ x: 26.5, y: 6.8 }], layer: none },
36
40
  ], ArrayType(Schematic.Types.Link));
37
41
  return (_jsx(Schematic, { extent: { width: 29, height: 12.5 }, height: "440px", items: equipment, item: e => ({
38
42
  key: e.id, x: e.x, y: e.y, label: e.id,
@@ -45,58 +49,384 @@ export const schematicPlant = example({
45
49
  }),
46
50
  inputs: [],
47
51
  });
48
- export const schematicSlice = example({
49
- keywords: ["Schematic", "slice", "chrome", "search", "filter", "rail", "affordances"],
50
- description: "Schematic with a full-width top-edge Slice rail (search + filter) replacing the built-in navigator search; rows fed explicitly via Slice.rows",
52
+ export const schematicSliceEffect = example({
53
+ keywords: ["Schematic", "slice", "sliceEffect", "excluded", "ghost", "desaturate", "pulse", "halo", "frame", "partition", "filter", "reactive", "switch", "State"],
54
+ description: "Schematic slice effect filtered-out items stay as ghosted / desaturated context (Slice.partition tags each row, `excluded` marks the losers) while the matched remainder is emphasised, instead of vanishing; the flat `slice*` props are SubtypeExprOrValue, so State-bound switches toggle pulse / frame / desaturate reactively",
51
55
  fn: East.function([], UIComponentType, (_$) => {
52
- const EquipType = StructType({ id: StringType, x: FloatType, y: FloatType, kind: StringType });
56
+ const EquipType = StructType({ id: StringType, x: FloatType, y: FloatType, kind: StringType, st: OptionType(StatusTokenType) });
53
57
  const cfg = Slice.config(EquipType, {
54
58
  fields: { id: { label: "ID" }, kind: { label: "Kind" } },
55
59
  searchFieldIds: ["id", "kind"],
56
60
  });
57
61
  return (_jsx(Reactive, { children: $ => {
62
+ // Small extent ⇒ items render as CARDS, where the ghost / desaturate
63
+ // / ring effects read clearly (on labelled pins they'd be too subtle).
64
+ // Each carries a status tone so `desaturate` visibly drains its colour.
58
65
  const data = $.const([
59
- { id: "UNIT-04", x: 3.0, y: 2.6, kind: "unit" },
60
- { id: "UNIT-05", x: 8.0, y: 2.6, kind: "unit" },
61
- { id: "LINE-2", x: 8.0, y: 9.8, kind: "pack" },
62
- { id: "BAY-OUT", x: 24.0, y: 9.8, kind: "pallets" },
66
+ { id: "UNIT-04", x: 3.5, y: 2.6, kind: "unit", st: some(variant("success", null)) },
67
+ { id: "UNIT-05", x: 11.5, y: 2.6, kind: "unit", st: some(variant("success", null)) },
68
+ { id: "LINE-2", x: 4.5, y: 6.2, kind: "pack", st: some(variant("warning", null)) },
69
+ { id: "BAY-OUT", x: 12.0, y: 6.2, kind: "pallets", st: some(variant("info", null)) },
63
70
  ], ArrayType(EquipType));
64
- const slice = $.let(Slice.bind([EquipType], "ex.schematic.slice", cfg, Slice.state(), data, none));
65
- const narrowed = $.let(Slice.rows([EquipType], slice));
66
- return (_jsx(Schematic, { extent: { width: 29, height: 12.5 }, height: "400px", items: narrowed, item: e => ({ key: e.id, x: e.x, y: e.y, label: e.id, sublabel: e.kind, icon: "database" }), slice: slice, affordances: ["search", "filter"] }));
71
+ // Seed an active filter (kind = "unit") so two rows already fail
72
+ // the narrowing the effect is visible at first render: the
73
+ // "pack" / "pallets" units ghost out, the "unit" survivors pulse.
74
+ const slice = $.let(Slice.bind([EquipType], "ex.schematic.effect", cfg, Slice.state({
75
+ filters: [variant("string", { fieldId: "kind", op: variant("eq", "unit") })],
76
+ }), data, none));
77
+ // Full set tagged with pass/fail — excluded rows STAY (ghosted),
78
+ // rather than the pre-narrowed `Slice.rows`.
79
+ const tagged = $.let(Slice.partition([EquipType], slice));
80
+ // Every effect setting is a flat `slice*` prop (SubtypeExprOrValue),
81
+ // so a State-bound switch drives it reactively — flip a switch, the
82
+ // schematic re-renders in place. No builder, no escape hatch.
83
+ const hide = $.let(State.bind([BooleanType], "ex.effect.hide", false));
84
+ const desat = $.let(State.bind([BooleanType], "ex.effect.desat", true));
85
+ const pulse = $.let(State.bind([BooleanType], "ex.effect.pulse", true));
86
+ const frame = $.let(State.bind([BooleanType], "ex.effect.frame", true));
87
+ const opacity = $.let(State.bind([FloatType], "ex.effect.opacity", 0.35));
88
+ const hideOn = $.let(hide.read());
89
+ const desatOn = $.let(desat.read());
90
+ const pulseOn = $.let(pulse.read());
91
+ const frameOn = $.let(frame.read());
92
+ const opacityVal = $.let(opacity.read());
93
+ const onHide = $.const(East.function([BooleanType], NullType, ($, v) => { $(hide.write(v)); }));
94
+ const onDesat = $.const(East.function([BooleanType], NullType, ($, v) => { $(desat.write(v)); }));
95
+ const onPulse = $.const(East.function([BooleanType], NullType, ($, v) => { $(pulse.write(v)); }));
96
+ const onFrame = $.const(East.function([BooleanType], NullType, ($, v) => { $(frame.write(v)); }));
97
+ const onOpacity = $.const(East.function([FloatType], NullType, ($, v) => { $(opacity.write(v)); }));
98
+ // Reactive emphasis option — `some(pulse)` on, `none` (ring off) off.
99
+ const emphasis = $.let(pulseOn.ifElse(_$ => some(variant("pulse", null)), _$ => none), OptionType(Schematic.Types.Emphasis));
100
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsxs(HStack, { gap: "4", children: [_jsx(Switch, { checked: hideOn, label: "Hide filtered", onChange: onHide }), _jsx(Switch, { checked: desatOn, label: "Desaturate", onChange: onDesat }), _jsx(Switch, { checked: pulseOn, label: "Pulse ring", onChange: onPulse }), _jsx(Switch, { checked: frameOn, label: "Frame", onChange: onFrame })] }), _jsxs(HStack, { gap: "3", align: "center", children: [_jsx(Text.MonoLabel, { children: "OPACITY" }), _jsx(Slider, { value: opacityVal, min: 0, max: 1, step: 0.05, onChange: onOpacity })] }), _jsx(Schematic, { extent: { width: 16, height: 9 }, height: "400px", items: tagged, item: t => ({
101
+ key: t.value.id, x: t.value.x, y: t.value.y, label: t.value.id,
102
+ sublabel: t.value.kind, icon: "database", status: t.value.st,
103
+ excluded: t.matched.not(),
104
+ }), slice: slice, affordances: ["search", "filter"], sliceOpacity: opacityVal, sliceDesaturate: desatOn, sliceEmphasis: emphasis, sliceFrame: frameOn, sliceHidden: hideOn })] }));
67
105
  } }));
68
106
  }),
69
107
  inputs: [],
70
108
  });
109
+ export const schematicLayers = example({
110
+ keywords: ["Schematic", "layers", "layer", "visibility", "solo", "lock", "opacity", "toggle", "legend"],
111
+ description: "Named layers — items / zones / links grouped into toggleable layers (a locked + dimmed building shell, and a maintenance layer that ships hidden); the canvas layer button opens a panel to show / hide / solo / lock each layer",
112
+ fn: East.function([], UIComponentType, ($) => {
113
+ const equipment = $.const([
114
+ { id: "GATE", x: 2.5, y: 6.0, kind: "entry", sys: "shell" },
115
+ { id: "PUMP-1", x: 6.0, y: 4.0, kind: "pump", sys: "process" },
116
+ { id: "TANK-2", x: 12.0, y: 4.0, kind: "tank", sys: "process" },
117
+ { id: "VALVE-3", x: 18.0, y: 7.0, kind: "valve", sys: "utilities" },
118
+ { id: "SENS-4", x: 9.0, y: 9.0, kind: "sensor", sys: "maintenance" },
119
+ ]);
120
+ const rooms = $.const([
121
+ { id: "hall", name: "Hall A", x: 1.0, y: 1.0, w: 22.0, h: 11.0 },
122
+ ]);
123
+ const pipes = $.const([
124
+ { id: "p1", a: "PUMP-1", b: "TANK-2" },
125
+ { id: "p2", a: "TANK-2", b: "VALVE-3" },
126
+ ]);
127
+ return (_jsx(Schematic, { extent: { width: 24, height: 13 }, height: "420px", items: equipment, item: e => ({ key: e.id, x: e.x, y: e.y, label: e.id, sublabel: e.kind, icon: "gear", layer: e.sys }), zones: rooms, zone: z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h, layer: "shell" }), links: pipes, link: l => ({ key: l.id, from: l.a, to: l.b, layer: "utilities" }), layers: [
128
+ // Locked + dimmed backdrop (the GATE item + Hall zone read as context).
129
+ { key: "shell", label: "Building shell", tone: "muted", locked: true, opacity: 0.45 },
130
+ { key: "process", label: "Process", tone: "brand" },
131
+ { key: "utilities", label: "Utilities", tone: "success" },
132
+ // Ships hidden — SENS-4 is absent until toggled on in the panel.
133
+ { key: "maintenance", label: "Maintenance", tone: "warning", visible: false },
134
+ ], scaleUnit: "m" }));
135
+ }),
136
+ inputs: [],
137
+ });
71
138
  export const schematicMinimal = example({
72
- keywords: ["Schematic", "canvas", "minimal"],
73
- description: "Minimal placement — items only, no zones or links",
139
+ keywords: ["Schematic", "canvas", "minimal", "readOnly", "static"],
140
+ description: "Minimal placement — items only, no zones or links; readOnly makes it a pure static picture (the connect / move edit tools never render), leaving just pan / zoom / selection",
74
141
  fn: East.function([], UIComponentType, ($) => {
75
142
  const rooms = $.const([
76
143
  { id: "DOCK-1", x: 3.0, y: 2.0 },
77
144
  { id: "DOCK-2", x: 9.0, y: 2.0 },
78
145
  { id: "STAGE", x: 6.0, y: 5.5 },
79
146
  ]);
80
- return (_jsx(Schematic, { extent: { width: 12, height: 7 }, height: "440px", items: rooms, item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "warehouse" }) }));
147
+ return (_jsx(Schematic, { extent: { width: 12, height: 7 }, height: "440px", items: rooms, item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "warehouse" }), readOnly: true }));
81
148
  }),
82
149
  inputs: [],
83
150
  });
84
151
  export const schematicInteractive = example({
85
- keywords: ["Schematic", "Reactive", "State", "onSelect", "interactive"],
86
- description: "Canvas whose onSelect tracks the inspected item",
152
+ keywords: ["Schematic", "Reactive", "State", "onSelect", "onItemOpen", "double-click", "open", "drill-in", "interactive"],
153
+ description: "Canvas whose onSelect tracks the inspected item and onItemOpen (double-click drill-in) tracks the opened one; a background double-click still resets the view",
87
154
  fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
88
155
  const bind = $.let(State.bind([StringType], "schematic_selected", "none"));
156
+ const opened = $.let(State.bind([StringType], "schematic_opened", "none"));
89
157
  const onSelect = $.const(East.function([StringType], NullType, ($, key) => {
90
158
  $(bind.write(key));
91
159
  }));
160
+ const onItemOpen = $.const(East.function([StringType], NullType, ($, key) => {
161
+ $(opened.write(key));
162
+ }));
92
163
  const selected = $.let(bind.read());
164
+ const openedKey = $.let(opened.read());
93
165
  return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsx(Schematic, { extent: { width: 12, height: 6 }, height: "440px", items: [
94
166
  { id: "CELL-A", x: 3.0, y: 3.0 },
95
167
  { id: "CELL-B", x: 9.0, y: 3.0 },
96
- ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "industry" }), onSelect: onSelect }), _jsx(Text.MonoLabel, { children: East.str `INSPECTING · ${selected}` })] }));
168
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "industry" }), onSelect: onSelect, onItemOpen: onItemOpen }), _jsx(Text.MonoLabel, { children: East.str `INSPECTING · ${selected} · OPENED · ${openedKey}` })] }));
169
+ } }))),
170
+ inputs: [],
171
+ });
172
+ export const schematicRangeSelect = example({
173
+ keywords: ["Schematic", "selection", "selectionMode", "single", "multiple", "marquee", "onSelectionChange", "selectZoomFocus", "focus", "zoom", "multi-select", "Reactive", "State", "Switch"],
174
+ description: "Multi-select — drag a marquee (or Shift-click) to select many items; onSelectionChange writes the whole key set to State. A reactive Switch toggles `selectZoomFocus`: when on, a tap flies to the item and a marquee fits the camera to the selected set; when off, selection never moves the camera",
175
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
176
+ // The full selected-key set is mirrored into State on every change.
177
+ const bind = $.let(State.bind([ArrayType(StringType)], "schematic_selected_keys", []));
178
+ const onSelectionChange = $.const(East.function([Schematic.Types.SelectionEvent], NullType, ($, ev) => {
179
+ $(bind.write(ev.selectedKeys));
180
+ }));
181
+ const keys = $.let(bind.read());
182
+ const count = $.let(keys.size());
183
+ // Reactive toggle for the camera-on-select behaviour — flip it and click /
184
+ // marquee to compare: focus ON flies / fits, OFF leaves the camera put.
185
+ const focus = $.let(State.bind([BooleanType], "ex.schematic.focus", true));
186
+ const focusOn = $.let(focus.read());
187
+ const onFocus = $.const(East.function([BooleanType], NullType, ($, v) => { $(focus.write(v)); }));
188
+ // Reactive selection mode — Switch between `single` (one item, no marquee)
189
+ // and `multiple` (marquee + Shift-extend), as an East variant expression.
190
+ const multi = $.let(State.bind([BooleanType], "ex.schematic.multi", true));
191
+ const multiOn = $.let(multi.read());
192
+ const onMulti = $.const(East.function([BooleanType], NullType, ($, v) => { $(multi.write(v)); }));
193
+ const selMode = $.let(multiOn.ifElse(_$ => variant("multiple", null), _$ => variant("single", null)), Schematic.Types.SelectionMode);
194
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsxs(HStack, { gap: "4", align: "center", children: [_jsx(Switch, { checked: multiOn, label: "Multi-select", onChange: onMulti }), _jsx(Switch, { checked: focusOn, label: "Zoom to focus on select", onChange: onFocus }), _jsx(Text.MonoLabel, { children: East.str `SELECTED · ${East.print(count)}` })] }), _jsx(Schematic, { extent: { width: 40, height: 24 }, height: "440px", items: [
195
+ // Spread across a large extent + clustered, so focus is
196
+ // obvious: a tap zooms in hard, a marquee fits to a cluster.
197
+ { id: "A", x: 4.0, y: 4.0 }, { id: "B", x: 7.0, y: 6.5 }, { id: "C", x: 5.0, y: 9.0 },
198
+ { id: "D", x: 31.0, y: 4.0 }, { id: "E", x: 35.0, y: 6.0 }, { id: "F", x: 33.0, y: 9.5 },
199
+ { id: "G", x: 18.0, y: 18.0 }, { id: "H", x: 22.0, y: 20.0 }, { id: "I", x: 20.0, y: 14.5 },
200
+ { id: "J", x: 34.0, y: 19.0 }, { id: "K", x: 37.0, y: 21.5 }, { id: "L", x: 13.0, y: 11.5 },
201
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "cube" }), selectionMode: selMode, selectZoomFocus: focusOn, onSelectionChange: onSelectionChange })] }));
97
202
  } }))),
98
203
  inputs: [],
99
204
  });
205
+ export const schematicZoneSelect = example({
206
+ keywords: ["Schematic", "zone", "area", "selection", "onSelectZone", "onZoneSelectionChange", "childItemKeys", "Reactive", "State", "Switch"],
207
+ description: "Zone (area) selection — click a hall / cell body to select the zone (items take hit-test precedence; innermost zone wins); onZoneSelectionChange reports the selected zones AND their child items; the Multi-select switch turns on Shift-extend",
208
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
209
+ const readout = $.let(State.bind([StringType], "schematic_zone_sel", "—"));
210
+ const onZoneSelectionChange = $.const(East.function([Schematic.Types.ZoneSelectionEvent], NullType, ($, ev) => {
211
+ $(readout.write(East.str `${East.print(ev.selectedKeys.size())} zone(s) · ${East.print(ev.childItemKeys.size())} item(s) inside`));
212
+ }));
213
+ // Zone gestures follow the same selectionMode as items — flip the
214
+ // switch and Shift+click zones to extend the area set.
215
+ const multi = $.let(State.bind([BooleanType], "ex.zone.multi", true));
216
+ const multiOn = $.let(multi.read());
217
+ const onMulti = $.const(East.function([BooleanType], NullType, ($, v) => { $(multi.write(v)); }));
218
+ const selMode = $.let(multiOn.ifElse(_$ => variant("multiple", null), _$ => variant("single", null)), Schematic.Types.SelectionMode);
219
+ const txt = $.let(readout.read());
220
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsxs(HStack, { gap: "4", align: "center", children: [_jsx(Switch, { checked: multiOn, label: "Multi-select (Shift extends)", onChange: onMulti }), _jsx(Text.MonoLabel, { children: East.str `ZONES · ${txt}` })] }), _jsx(Schematic, { extent: { width: 24, height: 12 }, height: "420px", items: [
221
+ { id: "T1", x: 3.0, y: 3.0 }, { id: "T2", x: 7.0, y: 3.0 },
222
+ { id: "M1", x: 15.0, y: 3.0 },
223
+ { id: "P1", x: 6.0, y: 9.0 }, { id: "P2", x: 18.0, y: 9.0 },
224
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "cube" }), zones: [
225
+ { id: "hall-a", name: "Hall A", x: 1.0, y: 1.0, w: 10.0, h: 4.5 },
226
+ { id: "hall-b", name: "Hall B", x: 12.5, y: 1.0, w: 10.5, h: 4.5 },
227
+ { id: "dock", name: "Dock", x: 1.0, y: 7.0, w: 22.0, h: 4.0 },
228
+ ], zone: z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h }), selectionMode: selMode, onZoneSelectionChange: onZoneSelectionChange })] }));
229
+ } }))),
230
+ inputs: [],
231
+ });
232
+ export const schematicLinkEdit = example({
233
+ keywords: ["Schematic", "link", "editing", "connect", "linkMode", "session", "onCreateLink", "onSelectLink", "onEditLink", "onDeleteLink", "readOnlyLinks", "readOnly", "draw", "Reactive", "State", "Switch"],
234
+ description: "Link editing — the connect tool drags item→item to create links (draw mode adds them locally, form-input style; connect mode is event-only for planning operations between areas); Shift+drag ADDS to the session and onCreateLink reports the accumulated links + the pair's existing links; click a link to select it, drag an endpoint handle to re-target (SHIFT-drag from an endpoint instead SEEDS a net session that absorbs the link), Del deletes; switches flip linkMode and readOnlyLinks reactively; canConnect forbids mixer→shipping directly (product must pass a packer)",
235
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
236
+ const log = $.let(State.bind([StringType], "schematic_link_log", "—"));
237
+ const onCreateLink = $.const(East.function([Schematic.Types.LinkCreateEvent], NullType, ($, ev) => {
238
+ $(log.write(East.str `created ${ev.link.from}→${ev.link.to} · session ${East.print(ev.links.size())} · existing ${East.print(ev.existing.size())}`));
239
+ }));
240
+ const onEditLink = $.const(East.function([Schematic.Types.LinkEditEvent], NullType, ($, ev) => {
241
+ $(log.write(East.str `edited ${ev.key} → ${ev.from}→${ev.to}`));
242
+ }));
243
+ const onDeleteLink = $.const(East.function([StringType], NullType, ($, key) => {
244
+ $(log.write(East.str `deleted ${key}`));
245
+ }));
246
+ // Reactive mode switches: `connect` = event-only (plan an operation,
247
+ // nothing drawn); editable off = readOnlyLinks (tool + handles hidden).
248
+ const connectMode = $.let(State.bind([BooleanType], "ex.link.connectmode", false));
249
+ const cOn = $.let(connectMode.read());
250
+ const onC = $.const(East.function([BooleanType], NullType, ($, v) => { $(connectMode.write(v)); }));
251
+ const lm = $.let(cOn.ifElse(_$ => variant("connect", null), _$ => variant("draw", null)), Schematic.Types.LinkMode);
252
+ const editable = $.let(State.bind([BooleanType], "ex.link.editable", true));
253
+ const eOn = $.let(editable.read());
254
+ const onE = $.const(East.function([BooleanType], NullType, ($, v) => { $(editable.write(v)); }));
255
+ const roLinks = $.let(eOn.not(), BooleanType);
256
+ // Connection validator: mixers never connect DIRECTLY to shipping —
257
+ // product must pass through a packer (drag MIX-1 → SHIP: no snap).
258
+ const canConnect = $.const(East.function([StringType, StringType], BooleanType, (_$, from, to) => from.substring(0n, 3n).equal("MIX").and(() => to.equal("SHIP")).not()));
259
+ // The mode READOUT follows the switch — the description must never
260
+ // claim event-only while draw mode is adding locally.
261
+ const modeTxt = $.let(cOn.ifElse(_$ => "CONNECT — event-only, nothing added", _$ => "DRAW — adds locally"));
262
+ const txt = $.let(log.read());
263
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsxs(HStack, { gap: "4", align: "center", children: [_jsx(Switch, { checked: eOn, label: "Links editable", onChange: onE }), _jsx(Switch, { checked: cOn, label: "Connect mode", onChange: onC }), _jsx(Text.MonoLabel, { children: East.str `${modeTxt} · ${txt}` })] }), _jsx(Schematic, { extent: { width: 22, height: 11 }, height: "420px", items: [
264
+ { id: "MIX-1", x: 3.0, y: 3.0 }, { id: "MIX-2", x: 3.0, y: 8.0 },
265
+ { id: "PACK-A", x: 12.0, y: 3.0 }, { id: "PACK-B", x: 12.0, y: 8.0 },
266
+ { id: "SHIP", x: 19.0, y: 5.5 },
267
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "industry" }), links: [{ id: "l1", a: "PACK-A", b: "SHIP" }], link: l => ({ key: l.id, from: l.a, to: l.b }), linkMode: lm, readOnlyLinks: roLinks, onCreateLink: onCreateLink, onEditLink: onEditLink, onDeleteLink: onDeleteLink, canConnect: canConnect })] }));
268
+ } }))),
269
+ inputs: [],
270
+ });
271
+ export const schematicItemMove = example({
272
+ keywords: ["Schematic", "move", "reposition", "drag", "onMoveItem", "readOnlyItems", "editing", "group", "Reactive", "State", "Switch"],
273
+ description: "Item drag-to-reposition — the move tool drags an item to a new world position (dragging a SELECTED item moves the whole selection rigidly); positions are local-first (form-input style) and onMoveItem fires once per gesture with the final position, every moved key, and the shared delta; the switch flips readOnlyItems reactively",
274
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
275
+ const log = $.let(State.bind([StringType], "schematic_move_log", "—"));
276
+ const onMoveItem = $.const(East.function([Schematic.Types.ItemMoveEvent], NullType, ($, ev) => {
277
+ $(log.write(East.str `moved ${East.print(ev.keys.size())} item(s) · ${ev.key} → ${East.Float.printFixed(ev.x, 1n)}, ${East.Float.printFixed(ev.y, 1n)}`));
278
+ }));
279
+ const movable = $.let(State.bind([BooleanType], "ex.move.on", true));
280
+ const mOn = $.let(movable.read());
281
+ const onM = $.const(East.function([BooleanType], NullType, ($, v) => { $(movable.write(v)); }));
282
+ const roItems = $.let(mOn.not(), BooleanType);
283
+ const txt = $.let(log.read());
284
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsxs(HStack, { gap: "4", align: "center", children: [_jsx(Switch, { checked: mOn, label: "Items movable", onChange: onM }), _jsx(Text.MonoLabel, { children: East.str `MOVE · ${txt}` })] }), _jsx(Schematic, { extent: { width: 20, height: 10 }, height: "420px", items: [
285
+ { id: "T1", x: 4.0, y: 3.0 }, { id: "T2", x: 8.0, y: 3.0 },
286
+ { id: "T3", x: 12.0, y: 3.0 }, { id: "PK", x: 8.0, y: 7.5 },
287
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "industry" }), links: [{ id: "l1", a: "T2", b: "PK" }], link: l => ({ key: l.id, from: l.a, to: l.b }), selectionMode: "multiple", readOnlyItems: roItems, onMoveItem: onMoveItem })] }));
288
+ } }))),
289
+ inputs: [],
290
+ });
291
+ // 320 generated demo units across 4 bays — authored at MODULE scope (an East
292
+ // function's body must be East all the way down; authoring-time constants are
293
+ // host-declared outside it).
294
+ const STRESS_GRID_ITEMS = Array.from({ length: 320 }, (_, i) => {
295
+ const bay = Math.floor(i / 80), col = i % 16, row = Math.floor((i % 80) / 16);
296
+ return {
297
+ id: `U-${String(i).padStart(3, "0")}`,
298
+ x: (bay % 2) * 32 + col * 1.9 + 2.0,
299
+ y: Math.floor(bay / 2) * 17 + row * 2.9 + 2.5,
300
+ load: (i * 37 % 100) / 100,
301
+ };
302
+ });
303
+ export const schematicStress = example({
304
+ keywords: ["Schematic", "stress", "performance", "LOD", "semantic zoom", "declutter", "minimap", "large", "many items"],
305
+ description: "Semantic-zoom stress probe — 320 generated items across 4 bays exercise the LOD ladder (cards ⇢ labelled dots ⇢ dots as you zoom out, dense rows degrading as one block instead of checkerboarding), the minimap, and 60 fps pan; the manual perf budget probe for the renderer",
306
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Schematic, { extent: { width: 64, height: 34 }, height: "460px", items: STRESS_GRID_ITEMS, item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "microchip", meter: { value: r.load, max: 1.0 } }), zones: [
307
+ { id: "bay-a", name: "Bay A", x: 0.5, y: 0.5, w: 31.0, h: 16.0 },
308
+ { id: "bay-b", name: "Bay B", x: 32.5, y: 0.5, w: 31.0, h: 16.0 },
309
+ { id: "bay-c", name: "Bay C", x: 0.5, y: 17.5, w: 31.0, h: 16.0 },
310
+ { id: "bay-d", name: "Bay D", x: 32.5, y: 17.5, w: 31.0, h: 16.0 },
311
+ ], zone: z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h, pattern: Schematic.outline() }), selectionMode: "multiple", minimap: true, scaleUnit: "m" }))),
312
+ inputs: [],
313
+ });
314
+ export const schematicHover = example({
315
+ keywords: ["Schematic", "hover", "HoverCard", "itemHover", "zoneHover", "linkHover", "Sparkline", "chart", "inspection", "lazy"],
316
+ description: "Hover cards — dwell on an item, zone body, or link/net stroke to open a card whose content an East function builds LAZILY from the hovered KEY (a Sparkline chart over the entity's history is the headline case); the card survives the pointer moving onto it, and any camera or edit gesture closes it",
317
+ fn: East.function([], UIComponentType, ($) => {
318
+ const itemHover = $.const(East.function([StringType], UIComponentType, (_$, key) => (_jsxs(VStack, { gap: "1", align: "flex-start", children: [_jsx(Text.MonoLabel, { children: East.str `${key} · throughput (7d)` }), _jsx(Sparkline, { data: [3.2, 4.1, 3.8, 5.0, 4.6, 5.4, 5.1], type: "area", width: "180px", height: "44px" })] }))));
319
+ const zoneHover = $.const(East.function([StringType], UIComponentType, (_$, key) => (_jsx(Text.MonoLabel, { children: East.str `${key} — CIP window 14:00–15:00` }))));
320
+ const linkHover = $.const(East.function([StringType], UIComponentType, (_$, key) => (_jsx(Text.MonoLabel, { children: East.str `${key} · 12.4 m³/h` }))));
321
+ return (_jsx(Schematic, { extent: { width: 20, height: 10 }, height: "420px", items: [
322
+ { id: "UNIT-01", x: 3.5, y: 3.0 }, { id: "UNIT-02", x: 8.0, y: 3.0 },
323
+ { id: "CIP-1", x: 3.5, y: 7.5 }, { id: "PK-1", x: 13.0, y: 5.0 },
324
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "industry" }), zones: [{ id: "hall-a", name: "Hall A", x: 1.0, y: 1.0, w: 9.5, h: 8.5 }], zone: z => ({ key: z.id, label: z.name, x: z.x, y: z.y, width: z.w, height: z.h, pattern: Schematic.outline() }), links: [{ id: "feed", a: "UNIT-02", b: "PK-1" }], link: l => ({ key: l.id, from: l.a, to: l.b, label: l.id }), itemHover: itemHover, zoneHover: zoneHover, linkHover: linkHover }));
325
+ }),
326
+ inputs: [],
327
+ });
328
+ export const schematicNets = example({
329
+ keywords: ["Schematic", "net", "nets", "manifold", "bus", "trunk", "header", "bar", "stubs", "sources", "destinations", "via", "label", "linkMode", "onCreateLink", "session", "Reactive", "State", "Switch"],
330
+ description: "Nets — a manifold as ONE row: many sources feeding many destinations drawn as a header BAR with stubs and junction-tap dots (no pairwise explosion); the second net bridges two banks along an explicit via trunk path; the connect tool creates nets too (Shift+drag grows the session into one net — draw adds locally, connect mode is event-only; Shift-drag OUT of a selected net's member adds the target as a new leg via onEditNet), with switches flipping linkMode and readOnlyLinks reactively; click a stub to select ONE leg and Del removes just that endpoint (onEditNet); canConnect makes CIP-1 supply-only — drafts never snap onto it",
331
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
332
+ const units = $.const([
333
+ { id: "CIP-1", x: 2.0, y: 6.0 },
334
+ { id: "U-01", x: 9.0, y: 2.0 }, { id: "U-02", x: 9.0, y: 4.5 },
335
+ { id: "U-03", x: 9.0, y: 7.0 }, { id: "U-04", x: 9.0, y: 9.5 },
336
+ { id: "PK-A", x: 17.0, y: 3.5 }, { id: "PK-B", x: 17.0, y: 8.0 },
337
+ { id: "DOCK", x: 23.0, y: 6.0 },
338
+ ]);
339
+ const manifolds = $.const([
340
+ { id: "cip", srcs: ["CIP-1"], dsts: ["U-01", "U-02", "U-03", "U-04"], name: "CIP supply", flow: "12 m³/h", path: [] },
341
+ { id: "header", srcs: ["PK-A", "PK-B"], dsts: ["DOCK"], name: "outfeed header", flow: "", path: [{ x: 20.0, y: 6.0 }] },
342
+ ], ArrayType(StructType({
343
+ id: StringType,
344
+ srcs: ArrayType(StringType),
345
+ dsts: ArrayType(StringType),
346
+ name: StringType,
347
+ flow: StringType,
348
+ path: ArrayType(StructType({ x: FloatType, y: FloatType })),
349
+ })));
350
+ const log = $.let(State.bind([StringType], "schematic_net_log", "—"));
351
+ // A Shift-session commit reports the WHOLE growing net — upsert by
352
+ // `net.key` to materialise it as one manifold row.
353
+ const onCreateLink = $.const(East.function([Schematic.Types.LinkCreateEvent], NullType, ($, ev) => {
354
+ $(log.write(East.str `net ${ev.net.key} · ${East.print(ev.net.sources.size())} src → ${East.print(ev.net.destinations.size())} dst · additive ${East.print(ev.additive)}`));
355
+ }));
356
+ // Click a STUB to select one leg (narrow halo), Del removes just that
357
+ // endpoint — onEditNet reports the net's endpoints AFTER the removal.
358
+ const onEditNet = $.const(East.function([Schematic.Types.NetEndpoints], NullType, ($, ev) => {
359
+ $(log.write(East.str `net ${ev.key} edited · ${East.print(ev.sources.size())} src → ${East.print(ev.destinations.size())} dst`));
360
+ }));
361
+ // Connection validator: CIP-1 is supply-only — the connect draft
362
+ // never snaps onto it (covers links, session/net extensions, and
363
+ // re-targets alike).
364
+ const canConnect = $.const(East.function([StringType, StringType], BooleanType, (_$, _from, to) => to.notEqual("CIP-1")));
365
+ // Reactive mode switches: `connect` = event-only (plan the manifold,
366
+ // nothing drawn); editable off = readOnlyLinks (connect tool hidden).
367
+ const connectMode = $.let(State.bind([BooleanType], "ex.net.connectmode", false));
368
+ const cOn = $.let(connectMode.read());
369
+ const onC = $.const(East.function([BooleanType], NullType, ($, v) => { $(connectMode.write(v)); }));
370
+ const lm = $.let(cOn.ifElse(_$ => variant("connect", null), _$ => variant("draw", null)), Schematic.Types.LinkMode);
371
+ const editable = $.let(State.bind([BooleanType], "ex.net.editable", true));
372
+ const eOn = $.let(editable.read());
373
+ const onE = $.const(East.function([BooleanType], NullType, ($, v) => { $(editable.write(v)); }));
374
+ const roLinks = $.let(eOn.not(), BooleanType);
375
+ // The mode READOUT follows the switch — the description must never
376
+ // claim event-only while draw mode is adding locally.
377
+ const modeTxt = $.let(cOn.ifElse(_$ => "CONNECT — event-only, nothing added", _$ => "DRAW — adds locally"));
378
+ const txt = $.let(log.read());
379
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsxs(HStack, { gap: "4", align: "center", children: [_jsx(Switch, { checked: eOn, label: "Links editable", onChange: onE }), _jsx(Switch, { checked: cOn, label: "Connect mode", onChange: onC }), _jsx(Text.MonoLabel, { children: East.str `${modeTxt} · ${txt}` })] }), _jsx(Schematic, { extent: { width: 26, height: 12 }, height: "420px", items: units, item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "industry" }), nets: manifolds, net: m => ({ key: m.id, sources: m.srcs, destinations: m.dsts, label: m.name, via: m.path }), linkMode: lm, readOnlyLinks: roLinks, onCreateLink: onCreateLink, onEditNet: onEditNet, canConnect: canConnect })] }));
380
+ } }))),
381
+ inputs: [],
382
+ });
383
+ export const schematicViewport = example({
384
+ keywords: ["Schematic", "viewport", "onViewportChange", "camera", "zoom", "bounds", "settle", "Reactive", "State"],
385
+ description: "Debounced viewport reporting — onViewportChange fires after a pan / zoom / fly settles with the zoom + the visible world rect, written to State as a live readout (never per-frame)",
386
+ fn: East.function([], UIComponentType, (_$) => (_jsx(Reactive, { children: $ => {
387
+ const bind = $.let(State.bind([StringType], "schematic_viewport", "—"));
388
+ const onViewportChange = $.const(East.function([Schematic.Types.ViewportEvent], NullType, ($, ev) => {
389
+ $(bind.write(East.str `zoom ${East.Float.printFixed(ev.zoom, 1n)} · x ${East.Float.printFixed(ev.minX, 1n)}..${East.Float.printFixed(ev.maxX, 1n)} · y ${East.Float.printFixed(ev.minY, 1n)}..${East.Float.printFixed(ev.maxY, 1n)}`));
390
+ }));
391
+ const readout = $.let(bind.read());
392
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsx(Schematic, { extent: { width: 24, height: 12 }, height: "400px", items: [
393
+ { id: "A", x: 4.0, y: 4.0 },
394
+ { id: "B", x: 12.0, y: 6.0 },
395
+ { id: "C", x: 20.0, y: 8.0 },
396
+ ], item: r => ({ key: r.id, x: r.x, y: r.y, label: r.id, icon: "cube" }), onViewportChange: onViewportChange }), _jsx(Text.MonoLabel, { children: East.str `VIEW · ${readout}` })] }));
397
+ } }))),
398
+ inputs: [],
399
+ });
400
+ export const schematicSelectFilter = example({
401
+ keywords: ["Schematic", "selection", "selectionMode", "sliceSelectField", "slice", "in", "filter", "marquee", "onSelectionChange", "partition", "ghost"],
402
+ description: "Marquee-select drives the bound slice — sliceSelectField writes an `in` filter of the selected keys, so Slice.partition re-tags the rest as excluded and the ghost slice effect fades them (selection → slice → ghost, one-directional)",
403
+ fn: East.function([], UIComponentType, (_$) => {
404
+ const EquipType = StructType({ id: StringType, x: FloatType, y: FloatType, kind: StringType });
405
+ const cfg = Slice.config(EquipType, {
406
+ fields: { id: { label: "ID" }, kind: { label: "Kind" } },
407
+ searchFieldIds: ["id", "kind"],
408
+ });
409
+ return (_jsx(Reactive, { children: $ => {
410
+ const data = $.const([
411
+ { id: "A", x: 3.5, y: 3.0, kind: "unit" },
412
+ { id: "B", x: 8.0, y: 3.0, kind: "unit" },
413
+ { id: "C", x: 12.5, y: 3.0, kind: "pack" },
414
+ { id: "D", x: 5.5, y: 6.4, kind: "unit" },
415
+ { id: "E", x: 10.0, y: 6.4, kind: "pack" },
416
+ ], ArrayType(EquipType));
417
+ // No initial filter — everything matches until a selection writes one.
418
+ const slice = $.let(Slice.bind([EquipType], "ex.schematic.selfilter", cfg, Slice.state({}), data, none));
419
+ // Full set tagged pass/fail; a marquee selection writes `in(id, keys)`
420
+ // via sliceSelectField, so the non-selected re-tag excluded → ghost.
421
+ const tagged = $.let(Slice.partition([EquipType], slice));
422
+ return (_jsxs(VStack, { gap: "3", align: "stretch", children: [_jsx(Schematic, { extent: { width: 16, height: 9 }, height: "400px", items: tagged, item: t => ({
423
+ key: t.value.id, x: t.value.x, y: t.value.y, label: t.value.id,
424
+ sublabel: t.value.kind, icon: "database", excluded: t.matched.not(),
425
+ }), slice: slice, affordances: ["filter"], selectionMode: "multiple", sliceSelectField: "id", sliceOpacity: 0.2, sliceDesaturate: true }), _jsx(Text.MonoLabel, { children: "DRAG A BOX TO SELECT \u00B7 the rest ghost via the slice" })] }));
426
+ } }));
427
+ }),
428
+ inputs: [],
429
+ });
100
430
  export const schematicFacility = example({
101
431
  keywords: ["Schematic", "facility", "navigator", "minimap", "zoom", "LOD", "click-to-fly", "large", "generate", "footprint", "geometry", "circle", "polygon", "shape"],
102
432
  description: "500-unit facility — navigator rail, minimap, semantic zoom, click-to-fly from the navigator; rows generated with East.Array.generate, each carrying a varied footprint shape/size (circle / square / triangle / diamond)",