@rpgjs/map-builder 0.0.0-stage → 5.0.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.
Files changed (287) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +341 -2
  3. package/dist/authority/authority.d.ts +118 -0
  4. package/dist/authority/authority.d.ts.map +1 -0
  5. package/dist/authority/authority.js +289 -0
  6. package/dist/authority/authority.js.map +1 -0
  7. package/dist/authority/index.d.ts +3 -0
  8. package/dist/authority/index.d.ts.map +1 -0
  9. package/dist/authority/index.js +3 -0
  10. package/dist/authority/store.d.ts +36 -0
  11. package/dist/authority/store.d.ts.map +1 -0
  12. package/dist/authority/store.js +35 -0
  13. package/dist/authority/store.js.map +1 -0
  14. package/dist/client/index.d.ts +72 -0
  15. package/dist/client/index.d.ts.map +1 -0
  16. package/dist/client/index.js +200 -0
  17. package/dist/client/index.js.map +1 -0
  18. package/dist/embed/client.d.ts +85 -0
  19. package/dist/embed/client.d.ts.map +1 -0
  20. package/dist/embed/client.js +255 -0
  21. package/dist/embed/client.js.map +1 -0
  22. package/dist/embed/host.d.ts +112 -0
  23. package/dist/embed/host.d.ts.map +1 -0
  24. package/dist/embed/host.js +278 -0
  25. package/dist/embed/host.js.map +1 -0
  26. package/dist/embed/index.d.ts +4 -0
  27. package/dist/embed/index.d.ts.map +1 -0
  28. package/dist/embed/index.js +4 -0
  29. package/dist/embed/protocol.d.ts +137 -0
  30. package/dist/embed/protocol.d.ts.map +1 -0
  31. package/dist/embed/protocol.js +161 -0
  32. package/dist/embed/protocol.js.map +1 -0
  33. package/dist/features/index.d.ts +66 -0
  34. package/dist/features/index.d.ts.map +1 -0
  35. package/dist/features/index.js +157 -0
  36. package/dist/features/index.js.map +1 -0
  37. package/dist/features/skin.d.ts +9 -0
  38. package/dist/features/skin.d.ts.map +1 -0
  39. package/dist/features/skin.js +43 -0
  40. package/dist/features/skin.js.map +1 -0
  41. package/dist/features/terrain.d.ts +66 -0
  42. package/dist/features/terrain.d.ts.map +1 -0
  43. package/dist/features/terrain.js +291 -0
  44. package/dist/features/terrain.js.map +1 -0
  45. package/dist/index.d.ts +9 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +51 -0
  48. package/dist/interaction/builtin-intents.d.ts +9 -0
  49. package/dist/interaction/builtin-intents.d.ts.map +1 -0
  50. package/dist/interaction/builtin-intents.js +61 -0
  51. package/dist/interaction/builtin-intents.js.map +1 -0
  52. package/dist/interaction/catalog.d.ts +24 -0
  53. package/dist/interaction/catalog.d.ts.map +1 -0
  54. package/dist/interaction/catalog.js +20 -0
  55. package/dist/interaction/catalog.js.map +1 -0
  56. package/dist/interaction/controller.d.ts +137 -0
  57. package/dist/interaction/controller.d.ts.map +1 -0
  58. package/dist/interaction/controller.js +215 -0
  59. package/dist/interaction/controller.js.map +1 -0
  60. package/dist/interaction/index.d.ts +14 -0
  61. package/dist/interaction/index.d.ts.map +1 -0
  62. package/dist/interaction/index.js +13 -0
  63. package/dist/interaction/input.d.ts +26 -0
  64. package/dist/interaction/input.d.ts.map +1 -0
  65. package/dist/interaction/input.js +33 -0
  66. package/dist/interaction/input.js.map +1 -0
  67. package/dist/interaction/intents.d.ts +80 -0
  68. package/dist/interaction/intents.d.ts.map +1 -0
  69. package/dist/interaction/keyboard.d.ts +26 -0
  70. package/dist/interaction/keyboard.d.ts.map +1 -0
  71. package/dist/interaction/keyboard.js +32 -0
  72. package/dist/interaction/keyboard.js.map +1 -0
  73. package/dist/interaction/picking.d.ts +33 -0
  74. package/dist/interaction/picking.d.ts.map +1 -0
  75. package/dist/interaction/picking.js +35 -0
  76. package/dist/interaction/picking.js.map +1 -0
  77. package/dist/interaction/pipeline.d.ts +45 -0
  78. package/dist/interaction/pipeline.d.ts.map +1 -0
  79. package/dist/interaction/pipeline.js +26 -0
  80. package/dist/interaction/pipeline.js.map +1 -0
  81. package/dist/interaction/placement.d.ts +76 -0
  82. package/dist/interaction/placement.d.ts.map +1 -0
  83. package/dist/interaction/placement.js +238 -0
  84. package/dist/interaction/placement.js.map +1 -0
  85. package/dist/interaction/selection-tool.d.ts +29 -0
  86. package/dist/interaction/selection-tool.d.ts.map +1 -0
  87. package/dist/interaction/selection-tool.js +190 -0
  88. package/dist/interaction/selection-tool.js.map +1 -0
  89. package/dist/interaction/steps.d.ts +62 -0
  90. package/dist/interaction/steps.d.ts.map +1 -0
  91. package/dist/interaction/steps.js +126 -0
  92. package/dist/interaction/steps.js.map +1 -0
  93. package/dist/interaction/store.d.ts +10 -0
  94. package/dist/interaction/store.d.ts.map +1 -0
  95. package/dist/interaction/store.js +28 -0
  96. package/dist/interaction/store.js.map +1 -0
  97. package/dist/interaction/tools.d.ts +26 -0
  98. package/dist/interaction/tools.d.ts.map +1 -0
  99. package/dist/interaction/tools.js +41 -0
  100. package/dist/interaction/tools.js.map +1 -0
  101. package/dist/model/assets.d.ts +36 -0
  102. package/dist/model/assets.d.ts.map +1 -0
  103. package/dist/model/assets.js +18 -0
  104. package/dist/model/assets.js.map +1 -0
  105. package/dist/model/committer.d.ts +66 -0
  106. package/dist/model/committer.d.ts.map +1 -0
  107. package/dist/model/committer.js +38 -0
  108. package/dist/model/committer.js.map +1 -0
  109. package/dist/model/core-patches.d.ts +2 -0
  110. package/dist/model/core-patches.d.ts.map +1 -0
  111. package/dist/model/core-patches.js +193 -0
  112. package/dist/model/core-patches.js.map +1 -0
  113. package/dist/model/define-map.d.ts +52 -0
  114. package/dist/model/define-map.d.ts.map +1 -0
  115. package/dist/model/define-map.js +66 -0
  116. package/dist/model/define-map.js.map +1 -0
  117. package/dist/model/definition.d.ts +57 -0
  118. package/dist/model/definition.d.ts.map +1 -0
  119. package/dist/model/definition.js +6 -0
  120. package/dist/model/definition.js.map +1 -0
  121. package/dist/model/errors.d.ts +21 -0
  122. package/dist/model/errors.d.ts.map +1 -0
  123. package/dist/model/errors.js +14 -0
  124. package/dist/model/errors.js.map +1 -0
  125. package/dist/model/history.d.ts +43 -0
  126. package/dist/model/history.d.ts.map +1 -0
  127. package/dist/model/history.js +141 -0
  128. package/dist/model/history.js.map +1 -0
  129. package/dist/model/index.d.ts +14 -0
  130. package/dist/model/index.d.ts.map +1 -0
  131. package/dist/model/index.js +14 -0
  132. package/dist/model/json.d.ts +15 -0
  133. package/dist/model/json.d.ts.map +1 -0
  134. package/dist/model/json.js +35 -0
  135. package/dist/model/json.js.map +1 -0
  136. package/dist/model/model.d.ts +68 -0
  137. package/dist/model/model.d.ts.map +1 -0
  138. package/dist/model/model.js +170 -0
  139. package/dist/model/model.js.map +1 -0
  140. package/dist/model/parse.d.ts +24 -0
  141. package/dist/model/parse.d.ts.map +1 -0
  142. package/dist/model/parse.js +165 -0
  143. package/dist/model/parse.js.map +1 -0
  144. package/dist/model/patches.d.ts +100 -0
  145. package/dist/model/patches.d.ts.map +1 -0
  146. package/dist/model/patches.js +21 -0
  147. package/dist/model/patches.js.map +1 -0
  148. package/dist/model/result.d.ts +22 -0
  149. package/dist/model/result.d.ts.map +1 -0
  150. package/dist/model/result.js +10 -0
  151. package/dist/model/result.js.map +1 -0
  152. package/dist/model/source.d.ts +52 -0
  153. package/dist/model/source.d.ts.map +1 -0
  154. package/dist/model/source.js +75 -0
  155. package/dist/model/source.js.map +1 -0
  156. package/dist/protocol/index.d.ts +92 -0
  157. package/dist/protocol/index.d.ts.map +1 -0
  158. package/dist/protocol/index.js +106 -0
  159. package/dist/protocol/index.js.map +1 -0
  160. package/dist/remote/committer.d.ts +43 -0
  161. package/dist/remote/committer.d.ts.map +1 -0
  162. package/dist/remote/committer.js +206 -0
  163. package/dist/remote/committer.js.map +1 -0
  164. package/dist/remote/index.d.ts +4 -0
  165. package/dist/remote/index.d.ts.map +1 -0
  166. package/dist/remote/index.js +4 -0
  167. package/dist/remote/join.d.ts +30 -0
  168. package/dist/remote/join.d.ts.map +1 -0
  169. package/dist/remote/join.js +36 -0
  170. package/dist/remote/join.js.map +1 -0
  171. package/dist/remote/memory-transport.d.ts +20 -0
  172. package/dist/remote/memory-transport.d.ts.map +1 -0
  173. package/dist/remote/memory-transport.js +48 -0
  174. package/dist/remote/memory-transport.js.map +1 -0
  175. package/dist/renderer/effects.d.ts +52 -0
  176. package/dist/renderer/effects.d.ts.map +1 -0
  177. package/dist/renderer/effects.js +111 -0
  178. package/dist/renderer/effects.js.map +1 -0
  179. package/dist/renderer/feature.d.ts +79 -0
  180. package/dist/renderer/feature.d.ts.map +1 -0
  181. package/dist/renderer/feature.js +50 -0
  182. package/dist/renderer/feature.js.map +1 -0
  183. package/dist/renderer/geometry.d.ts +59 -0
  184. package/dist/renderer/geometry.d.ts.map +1 -0
  185. package/dist/renderer/geometry.js +114 -0
  186. package/dist/renderer/geometry.js.map +1 -0
  187. package/dist/renderer/index.d.ts +7 -0
  188. package/dist/renderer/index.d.ts.map +1 -0
  189. package/dist/renderer/index.js +7 -0
  190. package/dist/renderer/scene.d.ts +77 -0
  191. package/dist/renderer/scene.d.ts.map +1 -0
  192. package/dist/renderer/scene.js +125 -0
  193. package/dist/renderer/scene.js.map +1 -0
  194. package/dist/renderer/textures.d.ts +20 -0
  195. package/dist/renderer/textures.d.ts.map +1 -0
  196. package/dist/renderer/textures.js +61 -0
  197. package/dist/renderer/textures.js.map +1 -0
  198. package/dist/renderer/view.d.ts +139 -0
  199. package/dist/renderer/view.d.ts.map +1 -0
  200. package/dist/renderer/view.js +349 -0
  201. package/dist/renderer/view.js.map +1 -0
  202. package/dist/rpgjs/shared.d.ts +41 -0
  203. package/dist/rpgjs/shared.d.ts.map +1 -0
  204. package/dist/rpgjs/shared.js +77 -0
  205. package/dist/rpgjs/shared.js.map +1 -0
  206. package/dist/server/index.d.ts +64 -0
  207. package/dist/server/index.d.ts.map +1 -0
  208. package/dist/server/index.js +205 -0
  209. package/dist/server/index.js.map +1 -0
  210. package/dist/server/storage.d.ts +16 -0
  211. package/dist/server/storage.d.ts.map +1 -0
  212. package/dist/server/storage.js +52 -0
  213. package/dist/server/storage.js.map +1 -0
  214. package/dist/skins/default.d.ts +27 -0
  215. package/dist/skins/default.d.ts.map +1 -0
  216. package/dist/skins/default.js +144 -0
  217. package/dist/skins/default.js.map +1 -0
  218. package/dist/skins/frames.d.ts +19 -0
  219. package/dist/skins/frames.d.ts.map +1 -0
  220. package/dist/skins/frames.js +49 -0
  221. package/dist/skins/frames.js.map +1 -0
  222. package/dist/skins/game.d.ts +21 -0
  223. package/dist/skins/game.d.ts.map +1 -0
  224. package/dist/skins/game.js +21 -0
  225. package/dist/skins/game.js.map +1 -0
  226. package/dist/skins/index.d.ts +5 -0
  227. package/dist/skins/index.d.ts.map +1 -0
  228. package/dist/skins/index.js +5 -0
  229. package/dist/skins/types.d.ts +46 -0
  230. package/dist/skins/types.d.ts.map +1 -0
  231. package/dist/skins/types.js +12 -0
  232. package/dist/skins/types.js.map +1 -0
  233. package/dist/terrain/data.d.ts +10 -0
  234. package/dist/terrain/data.d.ts.map +1 -0
  235. package/dist/terrain/data.js +83 -0
  236. package/dist/terrain/data.js.map +1 -0
  237. package/dist/terrain/define.d.ts +72 -0
  238. package/dist/terrain/define.d.ts.map +1 -0
  239. package/dist/terrain/define.js +91 -0
  240. package/dist/terrain/define.js.map +1 -0
  241. package/dist/terrain/editing.d.ts +54 -0
  242. package/dist/terrain/editing.d.ts.map +1 -0
  243. package/dist/terrain/editing.js +169 -0
  244. package/dist/terrain/editing.js.map +1 -0
  245. package/dist/terrain/index.d.ts +10 -0
  246. package/dist/terrain/index.d.ts.map +1 -0
  247. package/dist/terrain/index.js +10 -0
  248. package/dist/terrain/kernel.d.ts +57 -0
  249. package/dist/terrain/kernel.d.ts.map +1 -0
  250. package/dist/terrain/kernel.js +198 -0
  251. package/dist/terrain/kernel.js.map +1 -0
  252. package/dist/terrain/mask.d.ts +37 -0
  253. package/dist/terrain/mask.d.ts.map +1 -0
  254. package/dist/terrain/mask.js +244 -0
  255. package/dist/terrain/mask.js.map +1 -0
  256. package/dist/terrain/patches.d.ts +32 -0
  257. package/dist/terrain/patches.d.ts.map +1 -0
  258. package/dist/terrain/patches.js +135 -0
  259. package/dist/terrain/patches.js.map +1 -0
  260. package/dist/terrain/rle.d.ts +4 -0
  261. package/dist/terrain/rle.d.ts.map +1 -0
  262. package/dist/terrain/rle.js +68 -0
  263. package/dist/terrain/rle.js.map +1 -0
  264. package/dist/terrain/scheduler.d.ts +46 -0
  265. package/dist/terrain/scheduler.d.ts.map +1 -0
  266. package/dist/terrain/scheduler.js +148 -0
  267. package/dist/terrain/scheduler.js.map +1 -0
  268. package/dist/terrain/types.d.ts +110 -0
  269. package/dist/terrain/types.d.ts.map +1 -0
  270. package/dist/terrain/types.js +11 -0
  271. package/dist/terrain/types.js.map +1 -0
  272. package/dist/terrain-render/backend.d.ts +66 -0
  273. package/dist/terrain-render/backend.d.ts.map +1 -0
  274. package/dist/terrain-render/backend.js +96 -0
  275. package/dist/terrain-render/backend.js.map +1 -0
  276. package/dist/terrain-render/host.d.ts +21 -0
  277. package/dist/terrain-render/host.d.ts.map +1 -0
  278. package/dist/terrain-render/host.js +62 -0
  279. package/dist/terrain-render/host.js.map +1 -0
  280. package/dist/terrain-render/index.d.ts +3 -0
  281. package/dist/terrain-render/index.d.ts.map +1 -0
  282. package/dist/terrain-render/index.js +3 -0
  283. package/dist/terrain-render/worker.d.ts +2 -0
  284. package/dist/terrain-render/worker.d.ts.map +1 -0
  285. package/dist/terrain-render/worker.js +34 -0
  286. package/dist/terrain-render/worker.js.map +1 -0
  287. package/package.json +124 -3
@@ -0,0 +1,137 @@
1
+ import { AssetResolver } from '../model/assets';
2
+ import { MapState } from '../model/definition';
3
+ import { MapHistory } from '../model/history';
4
+ import { MapChange } from '../model/model';
5
+ import { MapPatch } from '../model/patches';
6
+ import { ApplyFailure, ApplyResult, MaybePromise, Unsubscribe } from '../model/result';
7
+ import { Rect } from '../renderer/geometry';
8
+ import { DescribeContext, SceneNode } from '../renderer/scene';
9
+ import { IntentDefinition, IntentOf, IntentType, MapIntent, Rejection } from './intents';
10
+ import { KeyInput, PointerInput } from './input';
11
+ import { PickOptions, Point, TargetRef } from './picking';
12
+ import { PipelineResult, PipelineStep } from './pipeline';
13
+ import { Store } from './store';
14
+ export interface DragState {
15
+ readonly targets: readonly TargetRef[];
16
+ readonly origin: Point;
17
+ readonly delta: Point;
18
+ /** False when the pipeline refuses where the targets would land. */
19
+ readonly valid: boolean;
20
+ readonly reason?: string;
21
+ /** True when the model follows the pointer while dragging, false when it changes only on drop. */
22
+ readonly live: boolean;
23
+ }
24
+ export interface GhostState {
25
+ readonly catalogId: string;
26
+ /** Asset drawn by the ghost. */
27
+ readonly kind: string;
28
+ /** Where it would be placed, after the pipeline (snapped…). */
29
+ readonly x: number;
30
+ readonly y: number;
31
+ readonly valid: boolean;
32
+ readonly reason?: string;
33
+ readonly bounds?: Rect;
34
+ readonly hitbox?: Rect;
35
+ }
36
+ /** Everything the interaction layer publishes. Skins and UIs read it; nothing here is drawn. */
37
+ export interface InteractionSnapshot {
38
+ readonly modes: readonly string[];
39
+ readonly hovered: TargetRef | null;
40
+ readonly selection: readonly TargetRef[];
41
+ readonly drag: DragState | null;
42
+ /** Rubber-band rectangle of a selection in progress. */
43
+ readonly marquee: Rect | null;
44
+ /** Id of the catalog item being placed, once `placement.start` was called. */
45
+ readonly placing: string | null;
46
+ /** Where the item being placed would land. Absent until the pointer has moved. */
47
+ readonly ghost: GhostState | null;
48
+ /** The last intent that was refused, and why, so the UI can tell the user. */
49
+ readonly lastRejection: (Rejection & {
50
+ readonly intent: MapIntent;
51
+ }) | null;
52
+ }
53
+ /** The part of a model the interaction layer needs. Any `MapModel` fits. */
54
+ export interface ModelApi<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {
55
+ readonly state: MapState;
56
+ subscribe(listener: (change: MapChange) => void): Unsubscribe;
57
+ apply(input: MapPatch | readonly MapPatch[], options?: {
58
+ readonly coalesce?: string;
59
+ readonly intents?: readonly unknown[];
60
+ }): R;
61
+ /** Used to take back a drag that applied its moves as it went. */
62
+ readonly history?: MapHistory<R>;
63
+ }
64
+ export interface Tool {
65
+ readonly id: string;
66
+ /** The tool is active while one of these modes is. */
67
+ readonly modes: readonly string[];
68
+ /** Tools with a higher priority see events first. Defaults to 0. */
69
+ readonly priority?: number;
70
+ /** Gate: the tool only reacts to events for which this is true, e.g. a modifier key. */
71
+ readonly when?: (input: PointerInput | KeyInput) => boolean;
72
+ /** Return `true` to take the event: the tool then also gets the moves and the release. */
73
+ pointerDown?(input: PointerInput, controller: InteractionController): boolean | void;
74
+ pointerMove?(input: PointerInput, controller: InteractionController): boolean | void;
75
+ pointerUp?(input: PointerInput, controller: InteractionController): void;
76
+ key?(input: KeyInput, controller: InteractionController): boolean | void;
77
+ /** The tool stops being active (mode change, `controller.cancel()`): drop what is in progress. */
78
+ cancel?(controller: InteractionController): void;
79
+ }
80
+ export interface SubmitOptions {
81
+ /** Consecutive submissions with the same key become one undo step. */
82
+ readonly coalesce?: string;
83
+ }
84
+ export interface PreviewOptions {
85
+ /** Targets to leave out of the obstacles, e.g. those being moved. */
86
+ readonly ignore?: readonly TargetRef[];
87
+ }
88
+ export interface InteractionController<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {
89
+ readonly state: Store<InteractionSnapshot>;
90
+ readonly model: ModelApi<R>;
91
+ readonly assets: AssetResolver;
92
+ readonly describeContext: DescribeContext;
93
+ readonly modes: readonly string[];
94
+ /** Replaces the active modes. Tools of a mode that is no longer active are cancelled. */
95
+ setModes(modes: readonly string[]): void;
96
+ readonly tools: readonly Tool[];
97
+ addTool(tool: Tool): Unsubscribe;
98
+ registerIntent<K extends IntentType>(type: K, definition: IntentDefinition<K>): Unsubscribe;
99
+ hasIntent(type: IntentType): boolean;
100
+ /** Adds steps to the chain an intent goes through. Steps run in the order they were added. */
101
+ usePipeline<K extends IntentType>(type: K, steps: readonly PipelineStep<K>[]): Unsubscribe;
102
+ /** Runs the pipeline only, without changing anything: what the ghost and the drag preview use. */
103
+ preview<K extends IntentType>(intent: IntentOf<K>, options?: PreviewOptions): PipelineResult<K>;
104
+ /**
105
+ * Turns intents into patches through the pipeline and applies them. Several
106
+ * intents are applied as one atomic batch. A refusal comes back as a failure
107
+ * whose `rejection.code` is the reason, and is kept in `state.lastRejection`.
108
+ */
109
+ submit(intent: MapIntent | readonly MapIntent[], options?: SubmitOptions): R | ApplyFailure;
110
+ /** What is drawn right now. */
111
+ nodes(): readonly SceneNode[];
112
+ pick(point: Point, options?: PickOptions): SceneNode | undefined;
113
+ pickAll(point: Point, options?: PickOptions): SceneNode[];
114
+ /** Last known pointer position in world coordinates. */
115
+ readonly pointer: Point | null;
116
+ feedPointer(kind: "down" | "move" | "up", input: PointerInput): boolean;
117
+ feedKey(input: KeyInput): boolean;
118
+ /** Cancels whatever is in progress: a drag, a placement. */
119
+ cancel(): void;
120
+ destroy(): void;
121
+ }
122
+ export interface InteractionOptions<R extends MaybePromise<ApplyResult>> {
123
+ readonly model: ModelApi<R>;
124
+ readonly assets: AssetResolver;
125
+ readonly describe: DescribeContext;
126
+ /** What is drawn right now. */
127
+ readonly nodes: () => readonly SceneNode[];
128
+ /** Who is acting, handed to the steps of the pipeline. */
129
+ readonly actor?: () => unknown;
130
+ /** Active modes at the start. Defaults to `["edit"]`. */
131
+ readonly modes?: readonly string[];
132
+ readonly onError?: (error: unknown) => void;
133
+ }
134
+ declare const unique: (targets: readonly TargetRef[]) => TargetRef[];
135
+ export declare function createInteraction<R extends MaybePromise<ApplyResult>>(options: InteractionOptions<R>): InteractionController<R>;
136
+ export { unique as uniqueTargets };
137
+ //# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/interaction/controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,215 @@
1
+ import { pickNode, pickNodes, sameTarget, targetOf } from "./picking.js";
2
+ import { runPipeline } from "./pipeline.js";
3
+ import { createStore } from "./store.js";
4
+ //#region src/interaction/controller.ts
5
+ var unique = (targets) => targets.filter((target, index) => targets.findIndex((other) => sameTarget(other, target)) === index);
6
+ var exists = (state, target) => (target.type === "element" ? state.elements : state.events).some((entry) => entry.id === target.id);
7
+ function createInteraction(options) {
8
+ const reportError = options.onError ?? ((error) => console.error(error));
9
+ const store = createStore({
10
+ modes: [...options.modes ?? ["edit"]],
11
+ hovered: null,
12
+ selection: [],
13
+ drag: null,
14
+ marquee: null,
15
+ placing: null,
16
+ ghost: null,
17
+ lastRejection: null
18
+ }, reportError);
19
+ const tools = [];
20
+ const intents = /* @__PURE__ */ new Map();
21
+ const pipelines = /* @__PURE__ */ new Map();
22
+ let captured = null;
23
+ let lastPointer = null;
24
+ let destroyed = false;
25
+ const isActive = (tool) => tool.modes.some((mode) => store.get().modes.includes(mode));
26
+ const ordered = () => [...tools].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
27
+ const intentContext = () => ({
28
+ state: options.model.state,
29
+ assets: options.assets,
30
+ describe: options.describe,
31
+ actor: options.actor?.()
32
+ });
33
+ const footprintOf = (intent) => intents.get(intent.type)?.footprint?.(intent, intentContext());
34
+ const pipelineContext = (nodes, preview) => ({
35
+ state: options.model.state,
36
+ assets: options.assets,
37
+ nodes,
38
+ scene: options.nodes(),
39
+ actor: options.actor?.(),
40
+ preview,
41
+ footprint: footprintOf,
42
+ reject: (reason, message) => ({
43
+ ok: false,
44
+ reason,
45
+ ...message !== void 0 ? { message } : {}
46
+ })
47
+ });
48
+ const refuse = (rejection, intent, patchIndex) => {
49
+ store.update({ lastRejection: {
50
+ ...rejection,
51
+ intent
52
+ } });
53
+ return {
54
+ ok: false,
55
+ rejection: {
56
+ code: rejection.reason,
57
+ message: rejection.message ?? rejection.reason,
58
+ ...patchIndex !== void 0 ? { patchIndex } : {}
59
+ }
60
+ };
61
+ };
62
+ const stepsFor = (type) => pipelines.get(type) ?? [];
63
+ const controller = {
64
+ state: store,
65
+ model: options.model,
66
+ assets: options.assets,
67
+ describeContext: options.describe,
68
+ get modes() {
69
+ return store.get().modes;
70
+ },
71
+ setModes(modes) {
72
+ const next = [...new Set(modes)];
73
+ const before = store.get().modes;
74
+ if (next.length === before.length && next.every((mode, index) => mode === before[index])) return;
75
+ store.update({ modes: next });
76
+ for (const tool of tools) if (!isActive(tool)) {
77
+ if (captured === tool) captured = null;
78
+ tool.cancel?.(controller);
79
+ }
80
+ },
81
+ get tools() {
82
+ return tools;
83
+ },
84
+ addTool(tool) {
85
+ if (tools.some((other) => other.id === tool.id)) throw new Error(`A tool with the id "${tool.id}" already exists`);
86
+ tools.push(tool);
87
+ return () => {
88
+ const index = tools.indexOf(tool);
89
+ if (index >= 0) tools.splice(index, 1);
90
+ if (captured === tool) captured = null;
91
+ tool.cancel?.(controller);
92
+ };
93
+ },
94
+ registerIntent(type, definition) {
95
+ if (intents.has(type)) throw new Error(`Intent "${type}" is already registered`);
96
+ intents.set(type, definition);
97
+ return () => intents.delete(type);
98
+ },
99
+ hasIntent: (type) => intents.has(type),
100
+ usePipeline(type, steps) {
101
+ const list = pipelines.get(type) ?? [];
102
+ list.push(...steps);
103
+ pipelines.set(type, list);
104
+ return () => {
105
+ const current = pipelines.get(type);
106
+ if (!current) return;
107
+ pipelines.set(type, current.filter((step) => !steps.includes(step)));
108
+ };
109
+ },
110
+ preview(intent, previewOptions = {}) {
111
+ const ignore = previewOptions.ignore ?? [];
112
+ const nodes = options.nodes().filter((node) => !ignore.some((target) => sameTarget(target, targetOf(node))));
113
+ return runPipeline(stepsFor(intent.type), intent, pipelineContext(nodes, true));
114
+ },
115
+ submit(input, submitOptions) {
116
+ const list = Array.isArray(input) ? input : [input];
117
+ const leaving = list.flatMap((intent) => intent.type === "element.move" || intent.type === "element.remove" ? [{
118
+ type: "element",
119
+ id: intent.id
120
+ }] : []);
121
+ const nodes = options.nodes().filter((node) => !leaving.some((target) => sameTarget(target, targetOf(node))));
122
+ const context = pipelineContext(nodes, false);
123
+ const patches = [];
124
+ for (const [index, intent] of list.entries()) {
125
+ const definition = intents.get(intent.type);
126
+ if (!definition) return refuse({
127
+ ok: false,
128
+ reason: "unknown-intent",
129
+ message: `No intent "${intent.type}"`
130
+ }, intent, index);
131
+ const result = runPipeline(stepsFor(intent.type), intent, context);
132
+ if (!result.ok) return refuse(result, intent, index);
133
+ const resolved = definition.resolve(result.intent, intentContext());
134
+ if (typeof resolved === "object" && resolved !== null && "ok" in resolved && resolved.ok === false) return refuse(resolved, intent, index);
135
+ patches.push(...Array.isArray(resolved) ? resolved : [resolved]);
136
+ }
137
+ store.update({ lastRejection: null });
138
+ return options.model.apply(patches.length === 1 ? patches[0] : patches, {
139
+ ...submitOptions,
140
+ intents: list
141
+ });
142
+ },
143
+ nodes: () => options.nodes(),
144
+ pick: (point, pickOptions) => pickNode(options.nodes(), point, pickOptions),
145
+ pickAll: (point, pickOptions) => pickNodes(options.nodes(), point, pickOptions),
146
+ get pointer() {
147
+ return lastPointer;
148
+ },
149
+ feedPointer(kind, input) {
150
+ if (destroyed) return false;
151
+ lastPointer = {
152
+ x: input.x,
153
+ y: input.y
154
+ };
155
+ const run = (tool) => {
156
+ if (tool.when && !tool.when(input)) return false;
157
+ return kind === "down" ? tool.pointerDown?.(input, controller) : kind === "move" ? tool.pointerMove?.(input, controller) : void 0;
158
+ };
159
+ if (kind === "up") {
160
+ const tool = captured;
161
+ captured = null;
162
+ tool?.pointerUp?.(input, controller);
163
+ return tool !== null;
164
+ }
165
+ if (captured && kind === "move") {
166
+ captured.pointerMove?.(input, controller);
167
+ return true;
168
+ }
169
+ for (const tool of ordered()) {
170
+ if (!isActive(tool)) continue;
171
+ if (run(tool) === true) {
172
+ if (kind === "down") captured = tool;
173
+ return true;
174
+ }
175
+ }
176
+ return false;
177
+ },
178
+ feedKey(input) {
179
+ if (destroyed) return false;
180
+ for (const tool of ordered()) {
181
+ if (!isActive(tool) || tool.when && !tool.when(input)) continue;
182
+ if (tool.key?.(input, controller) === true) return true;
183
+ }
184
+ return false;
185
+ },
186
+ cancel() {
187
+ captured = null;
188
+ for (const tool of tools) tool.cancel?.(controller);
189
+ },
190
+ destroy() {
191
+ if (destroyed) return;
192
+ destroyed = true;
193
+ unsubscribe();
194
+ controller.cancel();
195
+ tools.length = 0;
196
+ intents.clear();
197
+ pipelines.clear();
198
+ }
199
+ };
200
+ const unsubscribe = options.model.subscribe(() => {
201
+ const snapshot = store.get();
202
+ const state = options.model.state;
203
+ const selection = snapshot.selection.filter((target) => exists(state, target));
204
+ store.update({
205
+ ...selection.length !== snapshot.selection.length ? { selection } : {},
206
+ ...snapshot.hovered && !exists(state, snapshot.hovered) ? { hovered: null } : {},
207
+ ...snapshot.drag && snapshot.drag.targets.some((target) => !exists(state, target)) ? { drag: null } : {}
208
+ });
209
+ });
210
+ return controller;
211
+ }
212
+ //#endregion
213
+ export { createInteraction, unique as uniqueTargets };
214
+
215
+ //# sourceMappingURL=controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.js","names":[],"sources":["../../src/interaction/controller.ts"],"sourcesContent":["import type { AssetResolver } from \"../model/assets\";\nimport type { MapState } from \"../model/definition\";\nimport type { MapHistory } from \"../model/history\";\nimport type { MapChange } from \"../model/model\";\nimport type { MapPatch } from \"../model/patches\";\nimport type { ApplyFailure, ApplyResult, MaybePromise, Unsubscribe } from \"../model/result\";\nimport type { Rect } from \"../renderer/geometry\";\nimport type { DescribeContext, SceneNode } from \"../renderer/scene\";\nimport type { Footprint, IntentContext, IntentDefinition, IntentOf, IntentType, MapIntent, Rejection } from \"./intents\";\nimport type { KeyInput, PointerInput } from \"./input\";\nimport { pickNode, pickNodes, sameTarget, targetOf, type PickOptions, type Point, type TargetRef } from \"./picking\";\nimport { runPipeline, type PipelineContext, type PipelineResult, type PipelineStep } from \"./pipeline\";\nimport { createStore, type Store } from \"./store\";\n\nexport interface DragState {\n readonly targets: readonly TargetRef[];\n readonly origin: Point;\n readonly delta: Point;\n /** False when the pipeline refuses where the targets would land. */\n readonly valid: boolean;\n readonly reason?: string;\n /** True when the model follows the pointer while dragging, false when it changes only on drop. */\n readonly live: boolean;\n}\n\nexport interface GhostState {\n readonly catalogId: string;\n /** Asset drawn by the ghost. */\n readonly kind: string;\n /** Where it would be placed, after the pipeline (snapped…). */\n readonly x: number;\n readonly y: number;\n readonly valid: boolean;\n readonly reason?: string;\n readonly bounds?: Rect;\n readonly hitbox?: Rect;\n}\n\n/** Everything the interaction layer publishes. Skins and UIs read it; nothing here is drawn. */\nexport interface InteractionSnapshot {\n readonly modes: readonly string[];\n readonly hovered: TargetRef | null;\n readonly selection: readonly TargetRef[];\n readonly drag: DragState | null;\n /** Rubber-band rectangle of a selection in progress. */\n readonly marquee: Rect | null;\n /** Id of the catalog item being placed, once `placement.start` was called. */\n readonly placing: string | null;\n /** Where the item being placed would land. Absent until the pointer has moved. */\n readonly ghost: GhostState | null;\n /** The last intent that was refused, and why, so the UI can tell the user. */\n readonly lastRejection: (Rejection & { readonly intent: MapIntent }) | null;\n}\n\n/** The part of a model the interaction layer needs. Any `MapModel` fits. */\nexport interface ModelApi<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {\n readonly state: MapState;\n subscribe(listener: (change: MapChange) => void): Unsubscribe;\n apply(input: MapPatch | readonly MapPatch[], options?: { readonly coalesce?: string; readonly intents?: readonly unknown[] }): R;\n /** Used to take back a drag that applied its moves as it went. */\n readonly history?: MapHistory<R>;\n}\n\nexport interface Tool {\n readonly id: string;\n /** The tool is active while one of these modes is. */\n readonly modes: readonly string[];\n /** Tools with a higher priority see events first. Defaults to 0. */\n readonly priority?: number;\n /** Gate: the tool only reacts to events for which this is true, e.g. a modifier key. */\n readonly when?: (input: PointerInput | KeyInput) => boolean;\n /** Return `true` to take the event: the tool then also gets the moves and the release. */\n pointerDown?(input: PointerInput, controller: InteractionController): boolean | void;\n pointerMove?(input: PointerInput, controller: InteractionController): boolean | void;\n pointerUp?(input: PointerInput, controller: InteractionController): void;\n key?(input: KeyInput, controller: InteractionController): boolean | void;\n /** The tool stops being active (mode change, `controller.cancel()`): drop what is in progress. */\n cancel?(controller: InteractionController): void;\n}\n\nexport interface SubmitOptions {\n /** Consecutive submissions with the same key become one undo step. */\n readonly coalesce?: string;\n}\n\nexport interface PreviewOptions {\n /** Targets to leave out of the obstacles, e.g. those being moved. */\n readonly ignore?: readonly TargetRef[];\n}\n\nexport interface InteractionController<R extends MaybePromise<ApplyResult> = MaybePromise<ApplyResult>> {\n readonly state: Store<InteractionSnapshot>;\n readonly model: ModelApi<R>;\n readonly assets: AssetResolver;\n readonly describeContext: DescribeContext;\n readonly modes: readonly string[];\n /** Replaces the active modes. Tools of a mode that is no longer active are cancelled. */\n setModes(modes: readonly string[]): void;\n readonly tools: readonly Tool[];\n addTool(tool: Tool): Unsubscribe;\n registerIntent<K extends IntentType>(type: K, definition: IntentDefinition<K>): Unsubscribe;\n hasIntent(type: IntentType): boolean;\n /** Adds steps to the chain an intent goes through. Steps run in the order they were added. */\n usePipeline<K extends IntentType>(type: K, steps: readonly PipelineStep<K>[]): Unsubscribe;\n /** Runs the pipeline only, without changing anything: what the ghost and the drag preview use. */\n preview<K extends IntentType>(intent: IntentOf<K>, options?: PreviewOptions): PipelineResult<K>;\n /**\n * Turns intents into patches through the pipeline and applies them. Several\n * intents are applied as one atomic batch. A refusal comes back as a failure\n * whose `rejection.code` is the reason, and is kept in `state.lastRejection`.\n */\n submit(intent: MapIntent | readonly MapIntent[], options?: SubmitOptions): R | ApplyFailure;\n /** What is drawn right now. */\n nodes(): readonly SceneNode[];\n pick(point: Point, options?: PickOptions): SceneNode | undefined;\n pickAll(point: Point, options?: PickOptions): SceneNode[];\n /** Last known pointer position in world coordinates. */\n readonly pointer: Point | null;\n feedPointer(kind: \"down\" | \"move\" | \"up\", input: PointerInput): boolean;\n feedKey(input: KeyInput): boolean;\n /** Cancels whatever is in progress: a drag, a placement. */\n cancel(): void;\n destroy(): void;\n}\n\nexport interface InteractionOptions<R extends MaybePromise<ApplyResult>> {\n readonly model: ModelApi<R>;\n readonly assets: AssetResolver;\n readonly describe: DescribeContext;\n /** What is drawn right now. */\n readonly nodes: () => readonly SceneNode[];\n /** Who is acting, handed to the steps of the pipeline. */\n readonly actor?: () => unknown;\n /** Active modes at the start. Defaults to `[\"edit\"]`. */\n readonly modes?: readonly string[];\n readonly onError?: (error: unknown) => void;\n}\n\nconst unique = (targets: readonly TargetRef[]): TargetRef[] =>\n targets.filter((target, index) => targets.findIndex((other) => sameTarget(other, target)) === index);\n\nconst exists = (state: MapState, target: TargetRef): boolean =>\n (target.type === \"element\" ? state.elements : state.events).some((entry) => entry.id === target.id);\n\nexport function createInteraction<R extends MaybePromise<ApplyResult>>(options: InteractionOptions<R>): InteractionController<R> {\n const reportError = options.onError ?? ((error: unknown) => console.error(error));\n const store = createStore<InteractionSnapshot>(\n {\n modes: [...(options.modes ?? [\"edit\"])],\n hovered: null,\n selection: [],\n drag: null,\n marquee: null,\n placing: null,\n ghost: null,\n lastRejection: null,\n },\n reportError,\n );\n\n const tools: Tool[] = [];\n const intents = new Map<string, IntentDefinition<IntentType>>();\n const pipelines = new Map<string, PipelineStep<IntentType>[]>();\n let captured: Tool | null = null;\n let lastPointer: Point | null = null;\n let destroyed = false;\n\n const isActive = (tool: Tool): boolean => tool.modes.some((mode) => store.get().modes.includes(mode));\n const ordered = (): Tool[] => [...tools].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));\n\n const intentContext = (): IntentContext => ({\n state: options.model.state,\n assets: options.assets,\n describe: options.describe,\n actor: options.actor?.(),\n });\n\n const footprintOf = (intent: IntentOf<IntentType>): Footprint | undefined =>\n intents.get(intent.type)?.footprint?.(intent as never, intentContext());\n\n const pipelineContext = (nodes: readonly SceneNode[], preview: boolean): PipelineContext => ({\n state: options.model.state,\n assets: options.assets,\n nodes,\n scene: options.nodes(),\n actor: options.actor?.(),\n preview,\n footprint: footprintOf,\n reject: (reason, message) => ({ ok: false, reason, ...(message !== undefined ? { message } : {}) }),\n });\n\n const refuse = (rejection: Rejection, intent: MapIntent, patchIndex?: number): ApplyFailure => {\n store.update({ lastRejection: { ...rejection, intent } });\n return {\n ok: false,\n rejection: {\n code: rejection.reason,\n message: rejection.message ?? rejection.reason,\n ...(patchIndex !== undefined ? { patchIndex } : {}),\n },\n };\n };\n\n const stepsFor = (type: IntentType): readonly PipelineStep<IntentType>[] => pipelines.get(type) ?? [];\n\n const controller: InteractionController<R> = {\n state: store,\n model: options.model,\n assets: options.assets,\n describeContext: options.describe,\n get modes() {\n return store.get().modes;\n },\n setModes(modes) {\n const next = [...new Set(modes)];\n const before = store.get().modes;\n if (next.length === before.length && next.every((mode, index) => mode === before[index])) return;\n store.update({ modes: next });\n for (const tool of tools) {\n if (!isActive(tool)) {\n if (captured === tool) captured = null;\n tool.cancel?.(controller);\n }\n }\n },\n get tools() {\n return tools;\n },\n addTool(tool) {\n if (tools.some((other) => other.id === tool.id)) throw new Error(`A tool with the id \"${tool.id}\" already exists`);\n tools.push(tool);\n return () => {\n const index = tools.indexOf(tool);\n if (index >= 0) tools.splice(index, 1);\n if (captured === tool) captured = null;\n tool.cancel?.(controller);\n };\n },\n registerIntent(type, definition) {\n if (intents.has(type)) throw new Error(`Intent \"${type}\" is already registered`);\n intents.set(type, definition as unknown as IntentDefinition<IntentType>);\n return () => intents.delete(type);\n },\n hasIntent: (type) => intents.has(type),\n usePipeline(type, steps) {\n const list = pipelines.get(type) ?? [];\n list.push(...(steps as unknown as PipelineStep<IntentType>[]));\n pipelines.set(type, list);\n return () => {\n const current = pipelines.get(type);\n if (!current) return;\n pipelines.set(type, current.filter((step) => !(steps as readonly unknown[]).includes(step)));\n };\n },\n preview(intent, previewOptions = {}) {\n const ignore = previewOptions.ignore ?? [];\n const nodes = options.nodes().filter((node) => !ignore.some((target) => sameTarget(target, targetOf(node))));\n return runPipeline(stepsFor(intent.type) as unknown as readonly PipelineStep<typeof intent.type>[], intent, pipelineContext(nodes, true));\n },\n submit(input, submitOptions) {\n const list: readonly MapIntent[] = Array.isArray(input) ? (input as readonly MapIntent[]) : [input as MapIntent];\n // Elements moved or removed together do not block one another.\n const leaving: TargetRef[] = list.flatMap((intent) =>\n intent.type === \"element.move\" || intent.type === \"element.remove\" ? [{ type: \"element\" as const, id: intent.id }] : [],\n );\n const nodes = options.nodes().filter((node) => !leaving.some((target) => sameTarget(target, targetOf(node))));\n const context = pipelineContext(nodes, false);\n\n const patches: MapPatch[] = [];\n for (const [index, intent] of list.entries()) {\n const definition = intents.get(intent.type);\n if (!definition) return refuse({ ok: false, reason: \"unknown-intent\", message: `No intent \"${intent.type}\"` }, intent, index);\n\n const result = runPipeline(stepsFor(intent.type) as unknown as readonly PipelineStep<typeof intent.type>[], intent, context);\n if (!result.ok) return refuse(result, intent, index);\n\n const resolved = definition.resolve(result.intent as never, intentContext());\n if (typeof resolved === \"object\" && resolved !== null && \"ok\" in resolved && resolved.ok === false) {\n return refuse(resolved, intent, index);\n }\n patches.push(...(Array.isArray(resolved) ? (resolved as MapPatch[]) : [resolved as MapPatch]));\n }\n\n store.update({ lastRejection: null });\n // The intents travel with the patches: a committer that talks to an authority sends those.\n return options.model.apply(patches.length === 1 ? patches[0]! : patches, { ...submitOptions, intents: list });\n },\n nodes: () => options.nodes(),\n pick: (point, pickOptions) => pickNode(options.nodes(), point, pickOptions),\n pickAll: (point, pickOptions) => pickNodes(options.nodes(), point, pickOptions),\n get pointer() {\n return lastPointer;\n },\n feedPointer(kind, input) {\n if (destroyed) return false;\n lastPointer = { x: input.x, y: input.y };\n const run = (tool: Tool): boolean | void => {\n if (tool.when && !tool.when(input)) return false;\n return kind === \"down\" ? tool.pointerDown?.(input, controller) : kind === \"move\" ? tool.pointerMove?.(input, controller) : undefined;\n };\n\n if (kind === \"up\") {\n const tool = captured;\n captured = null;\n tool?.pointerUp?.(input, controller);\n return tool !== null;\n }\n if (captured && kind === \"move\") {\n captured.pointerMove?.(input, controller);\n return true;\n }\n for (const tool of ordered()) {\n if (!isActive(tool)) continue;\n if (run(tool) === true) {\n if (kind === \"down\") captured = tool;\n return true;\n }\n }\n return false;\n },\n feedKey(input) {\n if (destroyed) return false;\n for (const tool of ordered()) {\n if (!isActive(tool) || (tool.when && !tool.when(input))) continue;\n if (tool.key?.(input, controller) === true) return true;\n }\n return false;\n },\n cancel() {\n captured = null;\n for (const tool of tools) tool.cancel?.(controller);\n },\n destroy() {\n if (destroyed) return;\n destroyed = true;\n unsubscribe();\n controller.cancel();\n tools.length = 0;\n intents.clear();\n pipelines.clear();\n },\n };\n\n // What no longer exists can neither be selected, hovered nor dragged.\n const unsubscribe = options.model.subscribe(() => {\n const snapshot = store.get();\n const state = options.model.state;\n const selection = snapshot.selection.filter((target) => exists(state, target));\n store.update({\n ...(selection.length !== snapshot.selection.length ? { selection } : {}),\n ...(snapshot.hovered && !exists(state, snapshot.hovered) ? { hovered: null } : {}),\n ...(snapshot.drag && snapshot.drag.targets.some((target) => !exists(state, target)) ? { drag: null } : {}),\n });\n });\n\n return controller;\n}\n\nexport { unique as uniqueTargets };\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export * from './input';
2
+ export * from './store';
3
+ export * from './picking';
4
+ export * from './intents';
5
+ export * from './pipeline';
6
+ export * from './steps';
7
+ export * from './catalog';
8
+ export * from './controller';
9
+ export * from './builtin-intents';
10
+ export * from './tools';
11
+ export * from './selection-tool';
12
+ export * from './placement';
13
+ export * from './keyboard';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interaction/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { bindKeyboard, toKeyInput } from "./keyboard.js";
2
+ import { nodesIn, pickNode, pickNodes, sameTarget, targetKey, targetOf } from "./picking.js";
3
+ import { compose, runPipeline } from "./pipeline.js";
4
+ import { createStore } from "./store.js";
5
+ import { createInteraction, uniqueTargets as unique } from "./controller.js";
6
+ import { registerElementIntents } from "./builtin-intents.js";
7
+ import { createPlacement, registerPlacementIntent } from "./placement.js";
8
+ import { selectionTool } from "./selection-tool.js";
9
+ import { NO_MODIFIERS, key, pointer } from "./input.js";
10
+ import { maxCount, minDistance, ownerOnly, protectedZone, rateLimit, requireFree, snapToGrid, withinMap } from "./steps.js";
11
+ import { defineCatalog } from "./catalog.js";
12
+ import { hoverTool, playTool } from "./tools.js";
13
+ export { NO_MODIFIERS, bindKeyboard, compose, createInteraction, createPlacement, createStore, defineCatalog, hoverTool, key, maxCount, minDistance, nodesIn, ownerOnly, pickNode, pickNodes, playTool, pointer, protectedZone, rateLimit, registerElementIntents, registerPlacementIntent, requireFree, runPipeline, sameTarget, selectionTool, snapToGrid, targetKey, targetOf, toKeyInput, unique as uniqueTargets, withinMap };
@@ -0,0 +1,26 @@
1
+ export interface Modifiers {
2
+ readonly shift: boolean;
3
+ readonly ctrl: boolean;
4
+ readonly alt: boolean;
5
+ readonly meta: boolean;
6
+ }
7
+ export declare const NO_MODIFIERS: Modifiers;
8
+ /** A pointer event, already converted to world coordinates (pixels of the map). */
9
+ export interface PointerInput {
10
+ readonly x: number;
11
+ readonly y: number;
12
+ /** 0 is the main button, 2 the secondary one. */
13
+ readonly button: number;
14
+ readonly modifiers: Modifiers;
15
+ }
16
+ export interface KeyInput {
17
+ /** `KeyboardEvent.key`: `"Escape"`, `"Delete"`, `"a"`… */
18
+ readonly key: string;
19
+ readonly modifiers: Modifiers;
20
+ }
21
+ /** Builds a pointer input: `pointer(10, 20, { shift: true })`. Handy in tests and in hosts that have no DOM events. */
22
+ export declare function pointer(x: number, y: number, options?: {
23
+ button?: number;
24
+ } & Partial<Modifiers>): PointerInput;
25
+ export declare function key(name: string, modifiers?: Partial<Modifiers>): KeyInput;
26
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/interaction/input.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,YAAY,EAAE,SAAiF,CAAC;AAE7G,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;CAC/B;AAED,uHAAuH;AACvH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,SAAS,CAAM,GAAG,YAAY,CAGlH;AAED,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,QAAQ,CAE9E"}
@@ -0,0 +1,33 @@
1
+ //#region src/interaction/input.ts
2
+ var NO_MODIFIERS = Object.freeze({
3
+ shift: false,
4
+ ctrl: false,
5
+ alt: false,
6
+ meta: false
7
+ });
8
+ /** Builds a pointer input: `pointer(10, 20, { shift: true })`. Handy in tests and in hosts that have no DOM events. */
9
+ function pointer(x, y, options = {}) {
10
+ const { button = 0, ...modifiers } = options;
11
+ return {
12
+ x,
13
+ y,
14
+ button,
15
+ modifiers: {
16
+ ...NO_MODIFIERS,
17
+ ...modifiers
18
+ }
19
+ };
20
+ }
21
+ function key(name, modifiers = {}) {
22
+ return {
23
+ key: name,
24
+ modifiers: {
25
+ ...NO_MODIFIERS,
26
+ ...modifiers
27
+ }
28
+ };
29
+ }
30
+ //#endregion
31
+ export { NO_MODIFIERS, key, pointer };
32
+
33
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","names":[],"sources":["../../src/interaction/input.ts"],"sourcesContent":["export interface Modifiers {\n readonly shift: boolean;\n readonly ctrl: boolean;\n readonly alt: boolean;\n readonly meta: boolean;\n}\n\nexport const NO_MODIFIERS: Modifiers = Object.freeze({ shift: false, ctrl: false, alt: false, meta: false });\n\n/** A pointer event, already converted to world coordinates (pixels of the map). */\nexport interface PointerInput {\n readonly x: number;\n readonly y: number;\n /** 0 is the main button, 2 the secondary one. */\n readonly button: number;\n readonly modifiers: Modifiers;\n}\n\nexport interface KeyInput {\n /** `KeyboardEvent.key`: `\"Escape\"`, `\"Delete\"`, `\"a\"`… */\n readonly key: string;\n readonly modifiers: Modifiers;\n}\n\n/** Builds a pointer input: `pointer(10, 20, { shift: true })`. Handy in tests and in hosts that have no DOM events. */\nexport function pointer(x: number, y: number, options: { button?: number } & Partial<Modifiers> = {}): PointerInput {\n const { button = 0, ...modifiers } = options;\n return { x, y, button, modifiers: { ...NO_MODIFIERS, ...modifiers } };\n}\n\nexport function key(name: string, modifiers: Partial<Modifiers> = {}): KeyInput {\n return { key: name, modifiers: { ...NO_MODIFIERS, ...modifiers } };\n}\n"],"mappings":";AAOA,IAAa,eAA0B,OAAO,OAAO;CAAE,OAAO;CAAO,MAAM;CAAO,KAAK;CAAO,MAAM;AAAM,CAAC;;AAkB3G,SAAgB,QAAQ,GAAW,GAAW,UAAoD,CAAC,GAAiB;CAClH,MAAM,EAAE,SAAS,GAAG,GAAG,cAAc;CACrC,OAAO;EAAE;EAAG;EAAG;EAAQ,WAAW;GAAE,GAAG;GAAc,GAAG;EAAU;CAAE;AACtE;AAEA,SAAgB,IAAI,MAAc,YAAgC,CAAC,GAAa;CAC9E,OAAO;EAAE,KAAK;EAAM,WAAW;GAAE,GAAG;GAAc,GAAG;EAAU;CAAE;AACnE"}
@@ -0,0 +1,80 @@
1
+ import { AssetResolver } from '../model/assets';
2
+ import { MapState } from '../model/definition';
3
+ import { MapPatch } from '../model/patches';
4
+ import { Rect } from '../renderer/geometry';
5
+ import { DescribeContext } from '../renderer/scene';
6
+ import { TargetRef } from './picking';
7
+ /**
8
+ * Everything a user can ask for, mapped to its payload. Open on purpose: a
9
+ * feature adds its intents by merging into this interface.
10
+ *
11
+ * ```ts
12
+ * declare module "@rpgjs/map-builder/interaction" {
13
+ * interface IntentRegistry {
14
+ * "tilling.till": { readonly x: number; readonly y: number };
15
+ * }
16
+ * }
17
+ * ```
18
+ *
19
+ * An intent is a wish, not a fact: it goes through the pipeline, then becomes
20
+ * patches, which the committer decides to apply.
21
+ */
22
+ export interface IntentRegistry {
23
+ "element.move": {
24
+ readonly id: string;
25
+ readonly x: number;
26
+ readonly y: number;
27
+ };
28
+ "element.remove": {
29
+ readonly id: string;
30
+ };
31
+ /** Put something of the catalog on the map. `id` is generated when omitted. */
32
+ "element.place": {
33
+ readonly catalogId: string;
34
+ readonly x: number;
35
+ readonly y: number;
36
+ readonly id?: string;
37
+ };
38
+ }
39
+ export type IntentType = keyof IntentRegistry & string;
40
+ export type IntentOf<K extends IntentType> = {
41
+ readonly type: K;
42
+ } & IntentRegistry[K];
43
+ export type MapIntent = {
44
+ [K in IntentType]: IntentOf<K>;
45
+ }[IntentType];
46
+ /** Intents that carry a position, the ones a grid can snap. */
47
+ export type PositionedIntentType = {
48
+ [K in IntentType]: IntentRegistry[K] extends {
49
+ readonly x: number;
50
+ readonly y: number;
51
+ } ? K : never;
52
+ }[IntentType];
53
+ /** Why an intent was refused. Structured, so the UI can explain and the server can log. */
54
+ export interface Rejection {
55
+ readonly ok: false;
56
+ /** Machine-readable, open: `"occupied"`, `"not-enough-wood"`… */
57
+ readonly reason: string;
58
+ readonly message?: string;
59
+ }
60
+ /** The space an intent would take, for checks like "is this spot free?". */
61
+ export interface Footprint {
62
+ readonly bounds: Rect;
63
+ readonly hitbox?: Rect;
64
+ /** Things that must not count as obstacles: the element being moved itself. */
65
+ readonly ignore?: readonly TargetRef[];
66
+ }
67
+ export interface IntentContext {
68
+ readonly state: MapState;
69
+ readonly assets: AssetResolver;
70
+ readonly describe: DescribeContext;
71
+ /** Who is acting. Whatever the game decides: a player, a user. */
72
+ readonly actor: unknown;
73
+ }
74
+ export interface IntentDefinition<K extends IntentType> {
75
+ /** Turns the (validated) intent into patches. */
76
+ resolve(intent: IntentOf<K>, context: IntentContext): MapPatch | readonly MapPatch[] | Rejection;
77
+ /** Where the intent would put things, when it makes sense. */
78
+ footprint?(intent: IntentOf<K>, context: IntentContext): Footprint | undefined;
79
+ }
80
+ //# sourceMappingURL=intents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intents.d.ts","sourceRoot":"","sources":["../../src/interaction/intents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChF,gBAAgB,EAAE;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,+EAA+E;IAC/E,eAAe,EAAE;QAAE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/G;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,cAAc,GAAG,MAAM,CAAC;AACvD,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,UAAU,IAAI;IAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;AACtF,MAAM,MAAM,SAAS,GAAG;KAAG,CAAC,IAAI,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC;CAAE,CAAC,UAAU,CAAC,CAAC;AAEvE,+DAA+D;AAC/D,MAAM,MAAM,oBAAoB,GAAG;KAChC,CAAC,IAAI,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,SAAS;QAAE,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK;CACpG,CAAC,UAAU,CAAC,CAAC;AAEd,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC;IACvB,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,UAAU;IACpD,iDAAiD;IACjD,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,QAAQ,GAAG,SAAS,QAAQ,EAAE,GAAG,SAAS,CAAC;IACjG,8DAA8D;IAC9D,SAAS,CAAC,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,GAAG,SAAS,GAAG,SAAS,CAAC;CAChF"}
@@ -0,0 +1,26 @@
1
+ import { Unsubscribe } from '../model/result';
2
+ import { InteractionController } from './controller';
3
+ import { KeyInput } from './input';
4
+ /** The part of a `KeyboardEvent` that is used. */
5
+ export interface KeyEventLike {
6
+ readonly key: string;
7
+ readonly shiftKey?: boolean;
8
+ readonly ctrlKey?: boolean;
9
+ readonly altKey?: boolean;
10
+ readonly metaKey?: boolean;
11
+ readonly target?: unknown;
12
+ preventDefault?(): void;
13
+ }
14
+ /** Anything that emits key events: `window`, `document`, an element. */
15
+ export interface KeyTarget {
16
+ addEventListener(type: "keydown", listener: (event: KeyEventLike) => void): void;
17
+ removeEventListener(type: "keydown", listener: (event: KeyEventLike) => void): void;
18
+ }
19
+ export declare const toKeyInput: (event: KeyEventLike) => KeyInput;
20
+ /**
21
+ * Gives key presses to the interaction layer: Escape cancels, Delete removes.
22
+ * Keys typed in a text field are left alone, and a key a tool took is not passed
23
+ * on to the browser. Returns the function that stops listening.
24
+ */
25
+ export declare function bindKeyboard(controller: Pick<InteractionController, "feedKey">, target: KeyTarget): Unsubscribe;
26
+ //# sourceMappingURL=keyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../src/interaction/keyboard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,IAAI,IAAI,CAAC;CACzB;AAED,wEAAwE;AACxE,MAAM,WAAW,SAAS;IACxB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IACjF,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;CACrF;AAQD,eAAO,MAAM,UAAU,GAAI,OAAO,YAAY,KAAG,QAG/C,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,GAAG,WAAW,CAO/G"}