@scenography/overlay 0.2.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,240 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/overlay.ts
3
+ const STYLE_ID$1 = "scenography-overlay-styles";
4
+ const SLOT_LAYOUT = {
5
+ "top-left": {
6
+ row: 1,
7
+ col: 1
8
+ },
9
+ "top-center": {
10
+ row: 1,
11
+ col: 2
12
+ },
13
+ "top-right": {
14
+ row: 1,
15
+ col: 3
16
+ },
17
+ "middle-left": {
18
+ row: 2,
19
+ col: 1
20
+ },
21
+ center: {
22
+ row: 2,
23
+ col: 2
24
+ },
25
+ "middle-right": {
26
+ row: 2,
27
+ col: 3
28
+ },
29
+ "bottom-left": {
30
+ row: 3,
31
+ col: 1
32
+ },
33
+ "bottom-center": {
34
+ row: 3,
35
+ col: 2
36
+ },
37
+ "bottom-right": {
38
+ row: 3,
39
+ col: 3
40
+ }
41
+ };
42
+ const along = (n) => n === 1 ? "flex-start" : n === 3 ? "flex-end" : "center";
43
+ /** Inject the minimal layout CSS once. No daisyUI/Tailwind here — pure positioning. */
44
+ function ensureStyles$1() {
45
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID$1)) return;
46
+ const slotRules = Object.entries(SLOT_LAYOUT).map(([slot, { row, col }]) => `.sg-overlay__slot--${slot}{grid-row:${row};grid-column:${col};justify-content:${along(row)};align-items:${along(col)};}`).join("");
47
+ const style = document.createElement("style");
48
+ style.id = STYLE_ID$1;
49
+ style.textContent = ".sg-overlay{position:absolute;inset:0;pointer-events:none;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);}.sg-overlay__slot{display:flex;flex-direction:column;gap:8px;padding:12px;}.sg-overlay__item{pointer-events:auto;}" + slotRules;
50
+ document.head.appendChild(style);
51
+ }
52
+ /**
53
+ * Create a DOM overlay layered over a gallery's canvas. The overlay itself is
54
+ * framework- and style-agnostic: it positions a 3×3 grid of slots and mounts
55
+ * component elements into them. What a component looks like is up to the
56
+ * component (daisyUI widgets, your own markup, anything).
57
+ */
58
+ function createOverlay(options) {
59
+ ensureStyles$1();
60
+ const canvas = options.gallery.ctx.three.renderer.domElement;
61
+ const surface = options.container ?? canvas.parentElement ?? document.body;
62
+ if (getComputedStyle(surface).position === "static") surface.style.position = "relative";
63
+ const root = document.createElement("div");
64
+ root.className = "sg-overlay";
65
+ const slots = /* @__PURE__ */ new Map();
66
+ for (const slot of Object.keys(SLOT_LAYOUT)) {
67
+ const el = document.createElement("div");
68
+ el.className = `sg-overlay__slot sg-overlay__slot--${slot}`;
69
+ root.appendChild(el);
70
+ slots.set(slot, el);
71
+ }
72
+ surface.appendChild(root);
73
+ const ctx = {
74
+ gallery: options.gallery,
75
+ root,
76
+ surface,
77
+ locale: options.locale
78
+ };
79
+ const handles = /* @__PURE__ */ new Set();
80
+ return {
81
+ root,
82
+ add(slot, component) {
83
+ const result = component(ctx);
84
+ const handle = result instanceof HTMLElement ? { element: result } : result;
85
+ handle.element.classList.add("sg-overlay__item");
86
+ slots.get(slot)?.appendChild(handle.element);
87
+ handles.add(handle);
88
+ return () => {
89
+ handle.destroy?.();
90
+ handle.element.remove();
91
+ handles.delete(handle);
92
+ };
93
+ },
94
+ destroy() {
95
+ for (const handle of handles) {
96
+ handle.destroy?.();
97
+ handle.element.remove();
98
+ }
99
+ handles.clear();
100
+ root.remove();
101
+ }
102
+ };
103
+ }
104
+ //#endregion
105
+ //#region src/text.ts
106
+ /** Resolve a `Text` to its current string value. */
107
+ const resolveText = (text) => typeof text === "function" ? text() : text;
108
+ //#endregion
109
+ //#region src/components/touch-controls/touch-controls.ts
110
+ const STYLE_ID = "scenography-touch-joysticks-styles";
111
+ function ensureStyles() {
112
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
113
+ const style = document.createElement("style");
114
+ style.id = STYLE_ID;
115
+ style.textContent = ".sg-joysticks{position:fixed;inset:0;pointer-events:none;z-index:5;overflow:hidden;touch-action:none;}.sg-joy{position:absolute;border-radius:50%;background:rgba(0,0,0,0.25);border:2px solid rgba(255,255,255,0.35);backdrop-filter:blur(2px);display:none;}.sg-joy__knob{position:absolute;top:50%;left:50%;border-radius:50%;background:rgba(255,255,255,0.85);box-shadow:0 2px 8px rgba(0,0,0,0.4);}";
116
+ document.head.appendChild(style);
117
+ }
118
+ /**
119
+ * Dual on-screen joysticks for touch input — both hidden until a finger lands,
120
+ * then drawn at the touch point. One half of the screen moves the camera
121
+ * (forward/back + strafe, like WASD), the other aims it (like dragging the
122
+ * mouse). Multi-touch: both can be active at once. Which half is which follows
123
+ * `handedness`. Works on any touch-capable device (no media-query gating), so it
124
+ * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
125
+ */
126
+ function touchJoysticks(options = {}) {
127
+ const radius = options.radius ?? 60;
128
+ const resolveHand = () => typeof options.handedness === "function" ? options.handedness() : options.handedness ?? "right";
129
+ return (ctx) => {
130
+ ensureStyles();
131
+ const controls = ctx.gallery.ctx.controls;
132
+ const layer = document.createElement("div");
133
+ layer.className = "sg-joysticks";
134
+ layer.style.pointerEvents = "none";
135
+ const makeJoy = (role) => {
136
+ const base = document.createElement("div");
137
+ base.className = "sg-joy";
138
+ const size = radius * 2;
139
+ base.style.width = base.style.height = `${size}px`;
140
+ const knob = document.createElement("div");
141
+ knob.className = "sg-joy__knob";
142
+ const knobSize = radius;
143
+ knob.style.width = knob.style.height = `${knobSize}px`;
144
+ knob.style.margin = `${-knobSize / 2}px 0 0 ${-knobSize / 2}px`;
145
+ base.appendChild(knob);
146
+ layer.appendChild(base);
147
+ return {
148
+ role,
149
+ id: null,
150
+ ox: 0,
151
+ oy: 0,
152
+ base,
153
+ knob
154
+ };
155
+ };
156
+ const move = makeJoy("move");
157
+ const look = makeJoy("look");
158
+ const joys = [move, look];
159
+ const reset = (joy) => {
160
+ if (joy.role === "move") controls.setMoveInput(0, 0);
161
+ else controls.setLookInput(0, 0);
162
+ };
163
+ const apply = (joy, nx, ny) => {
164
+ if (joy.role === "move") controls.setMoveInput(-ny, nx);
165
+ else controls.setLookInput(nx, -ny);
166
+ };
167
+ const roleForX = (x) => {
168
+ return x < window.innerWidth / 2 === (resolveHand() === "right") ? "move" : "look";
169
+ };
170
+ const begin = (joy, x, y) => {
171
+ joy.ox = x;
172
+ joy.oy = y;
173
+ joy.base.style.left = `${x - radius}px`;
174
+ joy.base.style.top = `${y - radius}px`;
175
+ joy.base.style.display = "block";
176
+ joy.knob.style.transform = "translate(0px, 0px)";
177
+ apply(joy, 0, 0);
178
+ };
179
+ const drag = (joy, x, y) => {
180
+ let dx = x - joy.ox;
181
+ let dy = y - joy.oy;
182
+ const len = Math.hypot(dx, dy) || 1;
183
+ if (len > radius) {
184
+ dx = dx / len * radius;
185
+ dy = dy / len * radius;
186
+ }
187
+ joy.knob.style.transform = `translate(${dx}px, ${dy}px)`;
188
+ apply(joy, dx / radius, dy / radius);
189
+ };
190
+ const end = (joy) => {
191
+ joy.id = null;
192
+ joy.base.style.display = "none";
193
+ reset(joy);
194
+ };
195
+ const onStart = (e) => {
196
+ let claimed = false;
197
+ for (const t of Array.from(e.changedTouches)) {
198
+ if (document.elementFromPoint(t.clientX, t.clientY)?.closest(".sg-overlay__item")) continue;
199
+ const joy = roleForX(t.clientX) === "move" ? move : look;
200
+ if (joy.id !== null) continue;
201
+ joy.id = t.identifier;
202
+ begin(joy, t.clientX, t.clientY);
203
+ claimed = true;
204
+ }
205
+ if (claimed) e.preventDefault();
206
+ };
207
+ const onMove = (e) => {
208
+ let claimed = false;
209
+ for (const t of Array.from(e.changedTouches)) for (const joy of joys) if (joy.id === t.identifier) {
210
+ drag(joy, t.clientX, t.clientY);
211
+ claimed = true;
212
+ }
213
+ if (claimed) e.preventDefault();
214
+ };
215
+ const onEnd = (e) => {
216
+ for (const t of Array.from(e.changedTouches)) for (const joy of joys) if (joy.id === t.identifier) end(joy);
217
+ };
218
+ window.addEventListener("touchstart", onStart, { passive: false });
219
+ window.addEventListener("touchmove", onMove, { passive: false });
220
+ window.addEventListener("touchend", onEnd);
221
+ window.addEventListener("touchcancel", onEnd);
222
+ return {
223
+ element: layer,
224
+ destroy: () => {
225
+ window.removeEventListener("touchstart", onStart);
226
+ window.removeEventListener("touchmove", onMove);
227
+ window.removeEventListener("touchend", onEnd);
228
+ window.removeEventListener("touchcancel", onEnd);
229
+ controls.setMoveInput(0, 0);
230
+ controls.setLookInput(0, 0);
231
+ }
232
+ };
233
+ };
234
+ }
235
+ //#endregion
236
+ exports.createOverlay = createOverlay;
237
+ exports.resolveText = resolveText;
238
+ exports.touchJoysticks = touchJoysticks;
239
+
240
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["STYLE_ID","ensureStyles"],"sources":["../src/overlay.ts","../src/text.ts","../src/components/touch-controls/touch-controls.ts"],"sourcesContent":["import type { Gallery } from \"@scenography/engine\";\n\nimport type { OverlayComponent, OverlayContext, OverlayHandle, OverlaySlot } from \"./types\";\nimport type { LocaleSource } from \"./text\";\n\nconst STYLE_ID = \"scenography-overlay-styles\";\n\nconst SLOT_LAYOUT: Record<OverlaySlot, { row: number; col: number }> = {\n \"top-left\": { row: 1, col: 1 },\n \"top-center\": { row: 1, col: 2 },\n \"top-right\": { row: 1, col: 3 },\n \"middle-left\": { row: 2, col: 1 },\n center: { row: 2, col: 2 },\n \"middle-right\": { row: 2, col: 3 },\n \"bottom-left\": { row: 3, col: 1 },\n \"bottom-center\": { row: 3, col: 2 },\n \"bottom-right\": { row: 3, col: 3 },\n};\n\nconst along = (n: number): string => (n === 1 ? \"flex-start\" : n === 3 ? \"flex-end\" : \"center\");\n\n/** Inject the minimal layout CSS once. No daisyUI/Tailwind here — pure positioning. */\nfunction ensureStyles(): void {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const slotRules = (Object.entries(SLOT_LAYOUT) as [OverlaySlot, { row: number; col: number }][])\n .map(\n ([slot, { row, col }]) =>\n `.sg-overlay__slot--${slot}{grid-row:${row};grid-column:${col};justify-content:${along(row)};align-items:${along(col)};}`,\n )\n .join(\"\");\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent =\n `.sg-overlay{position:absolute;inset:0;pointer-events:none;display:grid;` +\n `grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);}` +\n `.sg-overlay__slot{display:flex;flex-direction:column;gap:8px;padding:12px;}` +\n `.sg-overlay__item{pointer-events:auto;}` +\n slotRules;\n document.head.appendChild(style);\n}\n\nexport interface OverlayOptions {\n gallery: Gallery;\n /** Element to mount into and target for fullscreen. Defaults to the canvas's parent. */\n container?: HTMLElement;\n /** Locale source so components can relocalize their text live. */\n locale?: LocaleSource;\n}\n\nexport interface Overlay {\n /** The overlay root element. */\n readonly root: HTMLElement;\n /** Add a component to a slot. Returns a function that removes it. */\n add(slot: OverlaySlot, component: OverlayComponent): () => void;\n /** Remove the overlay and tear down every component. */\n destroy(): void;\n}\n\n/**\n * Create a DOM overlay layered over a gallery's canvas. The overlay itself is\n * framework- and style-agnostic: it positions a 3×3 grid of slots and mounts\n * component elements into them. What a component looks like is up to the\n * component (daisyUI widgets, your own markup, anything).\n */\nexport function createOverlay(options: OverlayOptions): Overlay {\n ensureStyles();\n\n const canvas = options.gallery.ctx.three.renderer.domElement;\n const surface = options.container ?? canvas.parentElement ?? document.body;\n if (getComputedStyle(surface).position === \"static\") surface.style.position = \"relative\";\n\n const root = document.createElement(\"div\");\n root.className = \"sg-overlay\";\n\n const slots = new Map<OverlaySlot, HTMLElement>();\n for (const slot of Object.keys(SLOT_LAYOUT) as OverlaySlot[]) {\n const el = document.createElement(\"div\");\n el.className = `sg-overlay__slot sg-overlay__slot--${slot}`;\n root.appendChild(el);\n slots.set(slot, el);\n }\n surface.appendChild(root);\n\n const ctx: OverlayContext = { gallery: options.gallery, root, surface, locale: options.locale };\n const handles = new Set<OverlayHandle>();\n\n return {\n root,\n add(slot, component) {\n const result = component(ctx);\n const handle: OverlayHandle = result instanceof HTMLElement ? { element: result } : result;\n handle.element.classList.add(\"sg-overlay__item\");\n slots.get(slot)?.appendChild(handle.element);\n handles.add(handle);\n return () => {\n handle.destroy?.();\n handle.element.remove();\n handles.delete(handle);\n };\n },\n destroy() {\n for (const handle of handles) {\n handle.destroy?.();\n handle.element.remove();\n }\n handles.clear();\n root.remove();\n },\n };\n}\n","/** A text value that may change with locale: a literal string or a getter. */\nexport type Text = string | (() => string);\n\n/** Resolve a `Text` to its current string value. */\nexport const resolveText = (text: Text): string => (typeof text === \"function\" ? text() : text);\n\n/**\n * A source of locale changes the overlay can subscribe to (e.g. an app's i18n\n * store). Components re-apply their `Text` values whenever it fires, so text\n * relocalizes live without remounting.\n */\nexport interface LocaleSource {\n /** Subscribe to locale changes; returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n}\n","import type { OverlayComponent, OverlayContext } from \"../../types\";\n\n/** Which hand drives movement: `right` → move on the left, look on the right. */\nexport type Handedness = \"right\" | \"left\";\n\nexport interface TouchJoysticksOptions {\n /**\n * Whether the move joystick lives on the left (\"right\"-handed) or the right\n * (\"left\"-handed) half of the screen. A getter is read on every touch, so the\n * setting can change live. Defaults to \"right\".\n */\n handedness?: Handedness | (() => Handedness);\n /** Maximum knob travel from the touch origin, in px. Defaults to 60. */\n radius?: number;\n}\n\nconst STYLE_ID = \"scenography-touch-joysticks-styles\";\n\nfunction ensureStyles(): void {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent =\n // Full-viewport, click-through layer that just hosts the joystick visuals;\n // input is read from window touch events, so nothing here grabs pointers.\n `.sg-joysticks{position:fixed;inset:0;pointer-events:none;z-index:5;overflow:hidden;touch-action:none;}` +\n `.sg-joy{position:absolute;border-radius:50%;background:rgba(0,0,0,0.25);` +\n `border:2px solid rgba(255,255,255,0.35);backdrop-filter:blur(2px);display:none;}` +\n `.sg-joy__knob{position:absolute;top:50%;left:50%;border-radius:50%;` +\n `background:rgba(255,255,255,0.85);box-shadow:0 2px 8px rgba(0,0,0,0.4);}`;\n document.head.appendChild(style);\n}\n\ntype Role = \"move\" | \"look\";\n\ninterface Joystick {\n role: Role;\n id: number | null;\n ox: number;\n oy: number;\n base: HTMLElement;\n knob: HTMLElement;\n}\n\n/**\n * Dual on-screen joysticks for touch input — both hidden until a finger lands,\n * then drawn at the touch point. One half of the screen moves the camera\n * (forward/back + strafe, like WASD), the other aims it (like dragging the\n * mouse). Multi-touch: both can be active at once. Which half is which follows\n * `handedness`. Works on any touch-capable device (no media-query gating), so it\n * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.\n */\nexport function touchJoysticks(options: TouchJoysticksOptions = {}): OverlayComponent {\n const radius = options.radius ?? 60;\n const resolveHand = (): Handedness =>\n typeof options.handedness === \"function\"\n ? options.handedness()\n : (options.handedness ?? \"right\");\n\n return (ctx: OverlayContext) => {\n ensureStyles();\n const controls = ctx.gallery.ctx.controls;\n\n const layer = document.createElement(\"div\");\n layer.className = \"sg-joysticks\";\n // Inline pointer-events:none survives the overlay's `.sg-overlay__item` rule.\n layer.style.pointerEvents = \"none\";\n\n const makeJoy = (role: Role): Joystick => {\n const base = document.createElement(\"div\");\n base.className = \"sg-joy\";\n const size = radius * 2;\n base.style.width = base.style.height = `${size}px`;\n const knob = document.createElement(\"div\");\n knob.className = \"sg-joy__knob\";\n const knobSize = radius;\n knob.style.width = knob.style.height = `${knobSize}px`;\n knob.style.margin = `${-knobSize / 2}px 0 0 ${-knobSize / 2}px`;\n base.appendChild(knob);\n layer.appendChild(base);\n return { role, id: null, ox: 0, oy: 0, base, knob };\n };\n\n const move = makeJoy(\"move\");\n const look = makeJoy(\"look\");\n const joys = [move, look];\n\n const reset = (joy: Joystick): void => {\n if (joy.role === \"move\") controls.setMoveInput(0, 0);\n else controls.setLookInput(0, 0);\n };\n\n const apply = (joy: Joystick, nx: number, ny: number): void => {\n // Up (negative screen-Y) means forward / look up.\n if (joy.role === \"move\") controls.setMoveInput(-ny, nx);\n else controls.setLookInput(nx, -ny);\n };\n\n // Right-handed: move on the left half, look on the right. Mirror for lefties.\n const roleForX = (x: number): Role => {\n const onLeftHalf = x < window.innerWidth / 2;\n const moveOnLeft = resolveHand() === \"right\";\n return onLeftHalf === moveOnLeft ? \"move\" : \"look\";\n };\n\n const begin = (joy: Joystick, x: number, y: number): void => {\n joy.ox = x;\n joy.oy = y;\n joy.base.style.left = `${x - radius}px`;\n joy.base.style.top = `${y - radius}px`;\n joy.base.style.display = \"block\";\n joy.knob.style.transform = \"translate(0px, 0px)\";\n apply(joy, 0, 0);\n };\n\n const drag = (joy: Joystick, x: number, y: number): void => {\n let dx = x - joy.ox;\n let dy = y - joy.oy;\n const len = Math.hypot(dx, dy) || 1;\n if (len > radius) {\n dx = (dx / len) * radius;\n dy = (dy / len) * radius;\n }\n joy.knob.style.transform = `translate(${dx}px, ${dy}px)`;\n apply(joy, dx / radius, dy / radius);\n };\n\n const end = (joy: Joystick): void => {\n joy.id = null;\n joy.base.style.display = \"none\";\n reset(joy);\n };\n\n const onStart = (e: TouchEvent): void => {\n let claimed = false;\n for (const t of Array.from(e.changedTouches)) {\n // Don't steal touches that land on overlay controls (buttons, panels).\n const hit = document.elementFromPoint(t.clientX, t.clientY);\n if (hit?.closest(\".sg-overlay__item\")) continue;\n const joy = roleForX(t.clientX) === \"move\" ? move : look;\n if (joy.id !== null) continue; // that joystick is already in use\n joy.id = t.identifier;\n begin(joy, t.clientX, t.clientY);\n claimed = true;\n }\n if (claimed) e.preventDefault();\n };\n\n const onMove = (e: TouchEvent): void => {\n let claimed = false;\n for (const t of Array.from(e.changedTouches)) {\n for (const joy of joys) {\n if (joy.id === t.identifier) {\n drag(joy, t.clientX, t.clientY);\n claimed = true;\n }\n }\n }\n if (claimed) e.preventDefault();\n };\n\n const onEnd = (e: TouchEvent): void => {\n for (const t of Array.from(e.changedTouches)) {\n for (const joy of joys) if (joy.id === t.identifier) end(joy);\n }\n };\n\n window.addEventListener(\"touchstart\", onStart, { passive: false });\n window.addEventListener(\"touchmove\", onMove, { passive: false });\n window.addEventListener(\"touchend\", onEnd);\n window.addEventListener(\"touchcancel\", onEnd);\n\n return {\n element: layer,\n destroy: () => {\n window.removeEventListener(\"touchstart\", onStart);\n window.removeEventListener(\"touchmove\", onMove);\n window.removeEventListener(\"touchend\", onEnd);\n window.removeEventListener(\"touchcancel\", onEnd);\n controls.setMoveInput(0, 0);\n controls.setLookInput(0, 0);\n },\n };\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import { Gallery } from "@scenography/engine";
2
+
3
+ //#region src/text.d.ts
4
+ /** A text value that may change with locale: a literal string or a getter. */
5
+ type Text = string | (() => string);
6
+ /** Resolve a `Text` to its current string value. */
7
+ declare const resolveText: (text: Text) => string;
8
+ /**
9
+ * A source of locale changes the overlay can subscribe to (e.g. an app's i18n
10
+ * store). Components re-apply their `Text` values whenever it fires, so text
11
+ * relocalizes live without remounting.
12
+ */
13
+ interface LocaleSource {
14
+ /** Subscribe to locale changes; returns an unsubscribe function. */
15
+ subscribe(listener: () => void): () => void;
16
+ }
17
+ //#endregion
18
+ //#region src/types.d.ts
19
+ /** The nine regions a component can be placed in, on a 3×3 grid. */
20
+ type OverlaySlot = "top-left" | "top-center" | "top-right" | "middle-left" | "center" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right";
21
+ /** What a component receives when mounted. */
22
+ interface OverlayContext {
23
+ /** The running gallery (camera, entities, three escape hatch). */
24
+ gallery: Gallery;
25
+ /** The overlay root element, layered over the canvas. */
26
+ root: HTMLElement;
27
+ /** The element fullscreen targets (the gallery container). */
28
+ surface: HTMLElement;
29
+ /** Optional locale source for live-relocalizing component text. */
30
+ locale?: LocaleSource;
31
+ }
32
+ /** A mounted component: its element plus optional teardown. */
33
+ interface OverlayHandle {
34
+ element: HTMLElement;
35
+ destroy?(): void;
36
+ }
37
+ /**
38
+ * A component factory. Given the context, return a DOM element (and optionally a
39
+ * teardown). This is the whole contract — a custom component is just a function,
40
+ * no daisyUI or framework required.
41
+ */
42
+ type OverlayComponent = (ctx: OverlayContext) => HTMLElement | OverlayHandle;
43
+ //#endregion
44
+ //#region src/overlay.d.ts
45
+ interface OverlayOptions {
46
+ gallery: Gallery;
47
+ /** Element to mount into and target for fullscreen. Defaults to the canvas's parent. */
48
+ container?: HTMLElement;
49
+ /** Locale source so components can relocalize their text live. */
50
+ locale?: LocaleSource;
51
+ }
52
+ interface Overlay {
53
+ /** The overlay root element. */
54
+ readonly root: HTMLElement;
55
+ /** Add a component to a slot. Returns a function that removes it. */
56
+ add(slot: OverlaySlot, component: OverlayComponent): () => void;
57
+ /** Remove the overlay and tear down every component. */
58
+ destroy(): void;
59
+ }
60
+ /**
61
+ * Create a DOM overlay layered over a gallery's canvas. The overlay itself is
62
+ * framework- and style-agnostic: it positions a 3×3 grid of slots and mounts
63
+ * component elements into them. What a component looks like is up to the
64
+ * component (daisyUI widgets, your own markup, anything).
65
+ */
66
+ declare function createOverlay(options: OverlayOptions): Overlay;
67
+ //#endregion
68
+ //#region src/components/touch-controls/touch-controls.d.ts
69
+ /** Which hand drives movement: `right` → move on the left, look on the right. */
70
+ type Handedness = "right" | "left";
71
+ interface TouchJoysticksOptions {
72
+ /**
73
+ * Whether the move joystick lives on the left ("right"-handed) or the right
74
+ * ("left"-handed) half of the screen. A getter is read on every touch, so the
75
+ * setting can change live. Defaults to "right".
76
+ */
77
+ handedness?: Handedness | (() => Handedness);
78
+ /** Maximum knob travel from the touch origin, in px. Defaults to 60. */
79
+ radius?: number;
80
+ }
81
+ /**
82
+ * Dual on-screen joysticks for touch input — both hidden until a finger lands,
83
+ * then drawn at the touch point. One half of the screen moves the camera
84
+ * (forward/back + strafe, like WASD), the other aims it (like dragging the
85
+ * mouse). Multi-touch: both can be active at once. Which half is which follows
86
+ * `handedness`. Works on any touch-capable device (no media-query gating), so it
87
+ * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
88
+ */
89
+ declare function touchJoysticks(options?: TouchJoysticksOptions): OverlayComponent;
90
+ //#endregion
91
+ export { type Handedness, type LocaleSource, type Overlay, type OverlayComponent, type OverlayContext, type OverlayHandle, type OverlayOptions, type OverlaySlot, type Text, type TouchJoysticksOptions, createOverlay, resolveText, touchJoysticks };
92
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,92 @@
1
+ import { Gallery } from "@scenography/engine";
2
+
3
+ //#region src/text.d.ts
4
+ /** A text value that may change with locale: a literal string or a getter. */
5
+ type Text = string | (() => string);
6
+ /** Resolve a `Text` to its current string value. */
7
+ declare const resolveText: (text: Text) => string;
8
+ /**
9
+ * A source of locale changes the overlay can subscribe to (e.g. an app's i18n
10
+ * store). Components re-apply their `Text` values whenever it fires, so text
11
+ * relocalizes live without remounting.
12
+ */
13
+ interface LocaleSource {
14
+ /** Subscribe to locale changes; returns an unsubscribe function. */
15
+ subscribe(listener: () => void): () => void;
16
+ }
17
+ //#endregion
18
+ //#region src/types.d.ts
19
+ /** The nine regions a component can be placed in, on a 3×3 grid. */
20
+ type OverlaySlot = "top-left" | "top-center" | "top-right" | "middle-left" | "center" | "middle-right" | "bottom-left" | "bottom-center" | "bottom-right";
21
+ /** What a component receives when mounted. */
22
+ interface OverlayContext {
23
+ /** The running gallery (camera, entities, three escape hatch). */
24
+ gallery: Gallery;
25
+ /** The overlay root element, layered over the canvas. */
26
+ root: HTMLElement;
27
+ /** The element fullscreen targets (the gallery container). */
28
+ surface: HTMLElement;
29
+ /** Optional locale source for live-relocalizing component text. */
30
+ locale?: LocaleSource;
31
+ }
32
+ /** A mounted component: its element plus optional teardown. */
33
+ interface OverlayHandle {
34
+ element: HTMLElement;
35
+ destroy?(): void;
36
+ }
37
+ /**
38
+ * A component factory. Given the context, return a DOM element (and optionally a
39
+ * teardown). This is the whole contract — a custom component is just a function,
40
+ * no daisyUI or framework required.
41
+ */
42
+ type OverlayComponent = (ctx: OverlayContext) => HTMLElement | OverlayHandle;
43
+ //#endregion
44
+ //#region src/overlay.d.ts
45
+ interface OverlayOptions {
46
+ gallery: Gallery;
47
+ /** Element to mount into and target for fullscreen. Defaults to the canvas's parent. */
48
+ container?: HTMLElement;
49
+ /** Locale source so components can relocalize their text live. */
50
+ locale?: LocaleSource;
51
+ }
52
+ interface Overlay {
53
+ /** The overlay root element. */
54
+ readonly root: HTMLElement;
55
+ /** Add a component to a slot. Returns a function that removes it. */
56
+ add(slot: OverlaySlot, component: OverlayComponent): () => void;
57
+ /** Remove the overlay and tear down every component. */
58
+ destroy(): void;
59
+ }
60
+ /**
61
+ * Create a DOM overlay layered over a gallery's canvas. The overlay itself is
62
+ * framework- and style-agnostic: it positions a 3×3 grid of slots and mounts
63
+ * component elements into them. What a component looks like is up to the
64
+ * component (daisyUI widgets, your own markup, anything).
65
+ */
66
+ declare function createOverlay(options: OverlayOptions): Overlay;
67
+ //#endregion
68
+ //#region src/components/touch-controls/touch-controls.d.ts
69
+ /** Which hand drives movement: `right` → move on the left, look on the right. */
70
+ type Handedness = "right" | "left";
71
+ interface TouchJoysticksOptions {
72
+ /**
73
+ * Whether the move joystick lives on the left ("right"-handed) or the right
74
+ * ("left"-handed) half of the screen. A getter is read on every touch, so the
75
+ * setting can change live. Defaults to "right".
76
+ */
77
+ handedness?: Handedness | (() => Handedness);
78
+ /** Maximum knob travel from the touch origin, in px. Defaults to 60. */
79
+ radius?: number;
80
+ }
81
+ /**
82
+ * Dual on-screen joysticks for touch input — both hidden until a finger lands,
83
+ * then drawn at the touch point. One half of the screen moves the camera
84
+ * (forward/back + strafe, like WASD), the other aims it (like dragging the
85
+ * mouse). Multi-touch: both can be active at once. Which half is which follows
86
+ * `handedness`. Works on any touch-capable device (no media-query gating), so it
87
+ * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
88
+ */
89
+ declare function touchJoysticks(options?: TouchJoysticksOptions): OverlayComponent;
90
+ //#endregion
91
+ export { type Handedness, type LocaleSource, type Overlay, type OverlayComponent, type OverlayContext, type OverlayHandle, type OverlayOptions, type OverlaySlot, type Text, type TouchJoysticksOptions, createOverlay, resolveText, touchJoysticks };
92
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,237 @@
1
+ //#region src/overlay.ts
2
+ const STYLE_ID$1 = "scenography-overlay-styles";
3
+ const SLOT_LAYOUT = {
4
+ "top-left": {
5
+ row: 1,
6
+ col: 1
7
+ },
8
+ "top-center": {
9
+ row: 1,
10
+ col: 2
11
+ },
12
+ "top-right": {
13
+ row: 1,
14
+ col: 3
15
+ },
16
+ "middle-left": {
17
+ row: 2,
18
+ col: 1
19
+ },
20
+ center: {
21
+ row: 2,
22
+ col: 2
23
+ },
24
+ "middle-right": {
25
+ row: 2,
26
+ col: 3
27
+ },
28
+ "bottom-left": {
29
+ row: 3,
30
+ col: 1
31
+ },
32
+ "bottom-center": {
33
+ row: 3,
34
+ col: 2
35
+ },
36
+ "bottom-right": {
37
+ row: 3,
38
+ col: 3
39
+ }
40
+ };
41
+ const along = (n) => n === 1 ? "flex-start" : n === 3 ? "flex-end" : "center";
42
+ /** Inject the minimal layout CSS once. No daisyUI/Tailwind here — pure positioning. */
43
+ function ensureStyles$1() {
44
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID$1)) return;
45
+ const slotRules = Object.entries(SLOT_LAYOUT).map(([slot, { row, col }]) => `.sg-overlay__slot--${slot}{grid-row:${row};grid-column:${col};justify-content:${along(row)};align-items:${along(col)};}`).join("");
46
+ const style = document.createElement("style");
47
+ style.id = STYLE_ID$1;
48
+ style.textContent = ".sg-overlay{position:absolute;inset:0;pointer-events:none;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);}.sg-overlay__slot{display:flex;flex-direction:column;gap:8px;padding:12px;}.sg-overlay__item{pointer-events:auto;}" + slotRules;
49
+ document.head.appendChild(style);
50
+ }
51
+ /**
52
+ * Create a DOM overlay layered over a gallery's canvas. The overlay itself is
53
+ * framework- and style-agnostic: it positions a 3×3 grid of slots and mounts
54
+ * component elements into them. What a component looks like is up to the
55
+ * component (daisyUI widgets, your own markup, anything).
56
+ */
57
+ function createOverlay(options) {
58
+ ensureStyles$1();
59
+ const canvas = options.gallery.ctx.three.renderer.domElement;
60
+ const surface = options.container ?? canvas.parentElement ?? document.body;
61
+ if (getComputedStyle(surface).position === "static") surface.style.position = "relative";
62
+ const root = document.createElement("div");
63
+ root.className = "sg-overlay";
64
+ const slots = /* @__PURE__ */ new Map();
65
+ for (const slot of Object.keys(SLOT_LAYOUT)) {
66
+ const el = document.createElement("div");
67
+ el.className = `sg-overlay__slot sg-overlay__slot--${slot}`;
68
+ root.appendChild(el);
69
+ slots.set(slot, el);
70
+ }
71
+ surface.appendChild(root);
72
+ const ctx = {
73
+ gallery: options.gallery,
74
+ root,
75
+ surface,
76
+ locale: options.locale
77
+ };
78
+ const handles = /* @__PURE__ */ new Set();
79
+ return {
80
+ root,
81
+ add(slot, component) {
82
+ const result = component(ctx);
83
+ const handle = result instanceof HTMLElement ? { element: result } : result;
84
+ handle.element.classList.add("sg-overlay__item");
85
+ slots.get(slot)?.appendChild(handle.element);
86
+ handles.add(handle);
87
+ return () => {
88
+ handle.destroy?.();
89
+ handle.element.remove();
90
+ handles.delete(handle);
91
+ };
92
+ },
93
+ destroy() {
94
+ for (const handle of handles) {
95
+ handle.destroy?.();
96
+ handle.element.remove();
97
+ }
98
+ handles.clear();
99
+ root.remove();
100
+ }
101
+ };
102
+ }
103
+ //#endregion
104
+ //#region src/text.ts
105
+ /** Resolve a `Text` to its current string value. */
106
+ const resolveText = (text) => typeof text === "function" ? text() : text;
107
+ //#endregion
108
+ //#region src/components/touch-controls/touch-controls.ts
109
+ const STYLE_ID = "scenography-touch-joysticks-styles";
110
+ function ensureStyles() {
111
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
112
+ const style = document.createElement("style");
113
+ style.id = STYLE_ID;
114
+ style.textContent = ".sg-joysticks{position:fixed;inset:0;pointer-events:none;z-index:5;overflow:hidden;touch-action:none;}.sg-joy{position:absolute;border-radius:50%;background:rgba(0,0,0,0.25);border:2px solid rgba(255,255,255,0.35);backdrop-filter:blur(2px);display:none;}.sg-joy__knob{position:absolute;top:50%;left:50%;border-radius:50%;background:rgba(255,255,255,0.85);box-shadow:0 2px 8px rgba(0,0,0,0.4);}";
115
+ document.head.appendChild(style);
116
+ }
117
+ /**
118
+ * Dual on-screen joysticks for touch input — both hidden until a finger lands,
119
+ * then drawn at the touch point. One half of the screen moves the camera
120
+ * (forward/back + strafe, like WASD), the other aims it (like dragging the
121
+ * mouse). Multi-touch: both can be active at once. Which half is which follows
122
+ * `handedness`. Works on any touch-capable device (no media-query gating), so it
123
+ * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
124
+ */
125
+ function touchJoysticks(options = {}) {
126
+ const radius = options.radius ?? 60;
127
+ const resolveHand = () => typeof options.handedness === "function" ? options.handedness() : options.handedness ?? "right";
128
+ return (ctx) => {
129
+ ensureStyles();
130
+ const controls = ctx.gallery.ctx.controls;
131
+ const layer = document.createElement("div");
132
+ layer.className = "sg-joysticks";
133
+ layer.style.pointerEvents = "none";
134
+ const makeJoy = (role) => {
135
+ const base = document.createElement("div");
136
+ base.className = "sg-joy";
137
+ const size = radius * 2;
138
+ base.style.width = base.style.height = `${size}px`;
139
+ const knob = document.createElement("div");
140
+ knob.className = "sg-joy__knob";
141
+ const knobSize = radius;
142
+ knob.style.width = knob.style.height = `${knobSize}px`;
143
+ knob.style.margin = `${-knobSize / 2}px 0 0 ${-knobSize / 2}px`;
144
+ base.appendChild(knob);
145
+ layer.appendChild(base);
146
+ return {
147
+ role,
148
+ id: null,
149
+ ox: 0,
150
+ oy: 0,
151
+ base,
152
+ knob
153
+ };
154
+ };
155
+ const move = makeJoy("move");
156
+ const look = makeJoy("look");
157
+ const joys = [move, look];
158
+ const reset = (joy) => {
159
+ if (joy.role === "move") controls.setMoveInput(0, 0);
160
+ else controls.setLookInput(0, 0);
161
+ };
162
+ const apply = (joy, nx, ny) => {
163
+ if (joy.role === "move") controls.setMoveInput(-ny, nx);
164
+ else controls.setLookInput(nx, -ny);
165
+ };
166
+ const roleForX = (x) => {
167
+ return x < window.innerWidth / 2 === (resolveHand() === "right") ? "move" : "look";
168
+ };
169
+ const begin = (joy, x, y) => {
170
+ joy.ox = x;
171
+ joy.oy = y;
172
+ joy.base.style.left = `${x - radius}px`;
173
+ joy.base.style.top = `${y - radius}px`;
174
+ joy.base.style.display = "block";
175
+ joy.knob.style.transform = "translate(0px, 0px)";
176
+ apply(joy, 0, 0);
177
+ };
178
+ const drag = (joy, x, y) => {
179
+ let dx = x - joy.ox;
180
+ let dy = y - joy.oy;
181
+ const len = Math.hypot(dx, dy) || 1;
182
+ if (len > radius) {
183
+ dx = dx / len * radius;
184
+ dy = dy / len * radius;
185
+ }
186
+ joy.knob.style.transform = `translate(${dx}px, ${dy}px)`;
187
+ apply(joy, dx / radius, dy / radius);
188
+ };
189
+ const end = (joy) => {
190
+ joy.id = null;
191
+ joy.base.style.display = "none";
192
+ reset(joy);
193
+ };
194
+ const onStart = (e) => {
195
+ let claimed = false;
196
+ for (const t of Array.from(e.changedTouches)) {
197
+ if (document.elementFromPoint(t.clientX, t.clientY)?.closest(".sg-overlay__item")) continue;
198
+ const joy = roleForX(t.clientX) === "move" ? move : look;
199
+ if (joy.id !== null) continue;
200
+ joy.id = t.identifier;
201
+ begin(joy, t.clientX, t.clientY);
202
+ claimed = true;
203
+ }
204
+ if (claimed) e.preventDefault();
205
+ };
206
+ const onMove = (e) => {
207
+ let claimed = false;
208
+ for (const t of Array.from(e.changedTouches)) for (const joy of joys) if (joy.id === t.identifier) {
209
+ drag(joy, t.clientX, t.clientY);
210
+ claimed = true;
211
+ }
212
+ if (claimed) e.preventDefault();
213
+ };
214
+ const onEnd = (e) => {
215
+ for (const t of Array.from(e.changedTouches)) for (const joy of joys) if (joy.id === t.identifier) end(joy);
216
+ };
217
+ window.addEventListener("touchstart", onStart, { passive: false });
218
+ window.addEventListener("touchmove", onMove, { passive: false });
219
+ window.addEventListener("touchend", onEnd);
220
+ window.addEventListener("touchcancel", onEnd);
221
+ return {
222
+ element: layer,
223
+ destroy: () => {
224
+ window.removeEventListener("touchstart", onStart);
225
+ window.removeEventListener("touchmove", onMove);
226
+ window.removeEventListener("touchend", onEnd);
227
+ window.removeEventListener("touchcancel", onEnd);
228
+ controls.setMoveInput(0, 0);
229
+ controls.setLookInput(0, 0);
230
+ }
231
+ };
232
+ };
233
+ }
234
+ //#endregion
235
+ export { createOverlay, resolveText, touchJoysticks };
236
+
237
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["STYLE_ID","ensureStyles"],"sources":["../src/overlay.ts","../src/text.ts","../src/components/touch-controls/touch-controls.ts"],"sourcesContent":["import type { Gallery } from \"@scenography/engine\";\n\nimport type { OverlayComponent, OverlayContext, OverlayHandle, OverlaySlot } from \"./types\";\nimport type { LocaleSource } from \"./text\";\n\nconst STYLE_ID = \"scenography-overlay-styles\";\n\nconst SLOT_LAYOUT: Record<OverlaySlot, { row: number; col: number }> = {\n \"top-left\": { row: 1, col: 1 },\n \"top-center\": { row: 1, col: 2 },\n \"top-right\": { row: 1, col: 3 },\n \"middle-left\": { row: 2, col: 1 },\n center: { row: 2, col: 2 },\n \"middle-right\": { row: 2, col: 3 },\n \"bottom-left\": { row: 3, col: 1 },\n \"bottom-center\": { row: 3, col: 2 },\n \"bottom-right\": { row: 3, col: 3 },\n};\n\nconst along = (n: number): string => (n === 1 ? \"flex-start\" : n === 3 ? \"flex-end\" : \"center\");\n\n/** Inject the minimal layout CSS once. No daisyUI/Tailwind here — pure positioning. */\nfunction ensureStyles(): void {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const slotRules = (Object.entries(SLOT_LAYOUT) as [OverlaySlot, { row: number; col: number }][])\n .map(\n ([slot, { row, col }]) =>\n `.sg-overlay__slot--${slot}{grid-row:${row};grid-column:${col};justify-content:${along(row)};align-items:${along(col)};}`,\n )\n .join(\"\");\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent =\n `.sg-overlay{position:absolute;inset:0;pointer-events:none;display:grid;` +\n `grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);}` +\n `.sg-overlay__slot{display:flex;flex-direction:column;gap:8px;padding:12px;}` +\n `.sg-overlay__item{pointer-events:auto;}` +\n slotRules;\n document.head.appendChild(style);\n}\n\nexport interface OverlayOptions {\n gallery: Gallery;\n /** Element to mount into and target for fullscreen. Defaults to the canvas's parent. */\n container?: HTMLElement;\n /** Locale source so components can relocalize their text live. */\n locale?: LocaleSource;\n}\n\nexport interface Overlay {\n /** The overlay root element. */\n readonly root: HTMLElement;\n /** Add a component to a slot. Returns a function that removes it. */\n add(slot: OverlaySlot, component: OverlayComponent): () => void;\n /** Remove the overlay and tear down every component. */\n destroy(): void;\n}\n\n/**\n * Create a DOM overlay layered over a gallery's canvas. The overlay itself is\n * framework- and style-agnostic: it positions a 3×3 grid of slots and mounts\n * component elements into them. What a component looks like is up to the\n * component (daisyUI widgets, your own markup, anything).\n */\nexport function createOverlay(options: OverlayOptions): Overlay {\n ensureStyles();\n\n const canvas = options.gallery.ctx.three.renderer.domElement;\n const surface = options.container ?? canvas.parentElement ?? document.body;\n if (getComputedStyle(surface).position === \"static\") surface.style.position = \"relative\";\n\n const root = document.createElement(\"div\");\n root.className = \"sg-overlay\";\n\n const slots = new Map<OverlaySlot, HTMLElement>();\n for (const slot of Object.keys(SLOT_LAYOUT) as OverlaySlot[]) {\n const el = document.createElement(\"div\");\n el.className = `sg-overlay__slot sg-overlay__slot--${slot}`;\n root.appendChild(el);\n slots.set(slot, el);\n }\n surface.appendChild(root);\n\n const ctx: OverlayContext = { gallery: options.gallery, root, surface, locale: options.locale };\n const handles = new Set<OverlayHandle>();\n\n return {\n root,\n add(slot, component) {\n const result = component(ctx);\n const handle: OverlayHandle = result instanceof HTMLElement ? { element: result } : result;\n handle.element.classList.add(\"sg-overlay__item\");\n slots.get(slot)?.appendChild(handle.element);\n handles.add(handle);\n return () => {\n handle.destroy?.();\n handle.element.remove();\n handles.delete(handle);\n };\n },\n destroy() {\n for (const handle of handles) {\n handle.destroy?.();\n handle.element.remove();\n }\n handles.clear();\n root.remove();\n },\n };\n}\n","/** A text value that may change with locale: a literal string or a getter. */\nexport type Text = string | (() => string);\n\n/** Resolve a `Text` to its current string value. */\nexport const resolveText = (text: Text): string => (typeof text === \"function\" ? text() : text);\n\n/**\n * A source of locale changes the overlay can subscribe to (e.g. an app's i18n\n * store). Components re-apply their `Text` values whenever it fires, so text\n * relocalizes live without remounting.\n */\nexport interface LocaleSource {\n /** Subscribe to locale changes; returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n}\n","import type { OverlayComponent, OverlayContext } from \"../../types\";\n\n/** Which hand drives movement: `right` → move on the left, look on the right. */\nexport type Handedness = \"right\" | \"left\";\n\nexport interface TouchJoysticksOptions {\n /**\n * Whether the move joystick lives on the left (\"right\"-handed) or the right\n * (\"left\"-handed) half of the screen. A getter is read on every touch, so the\n * setting can change live. Defaults to \"right\".\n */\n handedness?: Handedness | (() => Handedness);\n /** Maximum knob travel from the touch origin, in px. Defaults to 60. */\n radius?: number;\n}\n\nconst STYLE_ID = \"scenography-touch-joysticks-styles\";\n\nfunction ensureStyles(): void {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent =\n // Full-viewport, click-through layer that just hosts the joystick visuals;\n // input is read from window touch events, so nothing here grabs pointers.\n `.sg-joysticks{position:fixed;inset:0;pointer-events:none;z-index:5;overflow:hidden;touch-action:none;}` +\n `.sg-joy{position:absolute;border-radius:50%;background:rgba(0,0,0,0.25);` +\n `border:2px solid rgba(255,255,255,0.35);backdrop-filter:blur(2px);display:none;}` +\n `.sg-joy__knob{position:absolute;top:50%;left:50%;border-radius:50%;` +\n `background:rgba(255,255,255,0.85);box-shadow:0 2px 8px rgba(0,0,0,0.4);}`;\n document.head.appendChild(style);\n}\n\ntype Role = \"move\" | \"look\";\n\ninterface Joystick {\n role: Role;\n id: number | null;\n ox: number;\n oy: number;\n base: HTMLElement;\n knob: HTMLElement;\n}\n\n/**\n * Dual on-screen joysticks for touch input — both hidden until a finger lands,\n * then drawn at the touch point. One half of the screen moves the camera\n * (forward/back + strafe, like WASD), the other aims it (like dragging the\n * mouse). Multi-touch: both can be active at once. Which half is which follows\n * `handedness`. Works on any touch-capable device (no media-query gating), so it\n * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.\n */\nexport function touchJoysticks(options: TouchJoysticksOptions = {}): OverlayComponent {\n const radius = options.radius ?? 60;\n const resolveHand = (): Handedness =>\n typeof options.handedness === \"function\"\n ? options.handedness()\n : (options.handedness ?? \"right\");\n\n return (ctx: OverlayContext) => {\n ensureStyles();\n const controls = ctx.gallery.ctx.controls;\n\n const layer = document.createElement(\"div\");\n layer.className = \"sg-joysticks\";\n // Inline pointer-events:none survives the overlay's `.sg-overlay__item` rule.\n layer.style.pointerEvents = \"none\";\n\n const makeJoy = (role: Role): Joystick => {\n const base = document.createElement(\"div\");\n base.className = \"sg-joy\";\n const size = radius * 2;\n base.style.width = base.style.height = `${size}px`;\n const knob = document.createElement(\"div\");\n knob.className = \"sg-joy__knob\";\n const knobSize = radius;\n knob.style.width = knob.style.height = `${knobSize}px`;\n knob.style.margin = `${-knobSize / 2}px 0 0 ${-knobSize / 2}px`;\n base.appendChild(knob);\n layer.appendChild(base);\n return { role, id: null, ox: 0, oy: 0, base, knob };\n };\n\n const move = makeJoy(\"move\");\n const look = makeJoy(\"look\");\n const joys = [move, look];\n\n const reset = (joy: Joystick): void => {\n if (joy.role === \"move\") controls.setMoveInput(0, 0);\n else controls.setLookInput(0, 0);\n };\n\n const apply = (joy: Joystick, nx: number, ny: number): void => {\n // Up (negative screen-Y) means forward / look up.\n if (joy.role === \"move\") controls.setMoveInput(-ny, nx);\n else controls.setLookInput(nx, -ny);\n };\n\n // Right-handed: move on the left half, look on the right. Mirror for lefties.\n const roleForX = (x: number): Role => {\n const onLeftHalf = x < window.innerWidth / 2;\n const moveOnLeft = resolveHand() === \"right\";\n return onLeftHalf === moveOnLeft ? \"move\" : \"look\";\n };\n\n const begin = (joy: Joystick, x: number, y: number): void => {\n joy.ox = x;\n joy.oy = y;\n joy.base.style.left = `${x - radius}px`;\n joy.base.style.top = `${y - radius}px`;\n joy.base.style.display = \"block\";\n joy.knob.style.transform = \"translate(0px, 0px)\";\n apply(joy, 0, 0);\n };\n\n const drag = (joy: Joystick, x: number, y: number): void => {\n let dx = x - joy.ox;\n let dy = y - joy.oy;\n const len = Math.hypot(dx, dy) || 1;\n if (len > radius) {\n dx = (dx / len) * radius;\n dy = (dy / len) * radius;\n }\n joy.knob.style.transform = `translate(${dx}px, ${dy}px)`;\n apply(joy, dx / radius, dy / radius);\n };\n\n const end = (joy: Joystick): void => {\n joy.id = null;\n joy.base.style.display = \"none\";\n reset(joy);\n };\n\n const onStart = (e: TouchEvent): void => {\n let claimed = false;\n for (const t of Array.from(e.changedTouches)) {\n // Don't steal touches that land on overlay controls (buttons, panels).\n const hit = document.elementFromPoint(t.clientX, t.clientY);\n if (hit?.closest(\".sg-overlay__item\")) continue;\n const joy = roleForX(t.clientX) === \"move\" ? move : look;\n if (joy.id !== null) continue; // that joystick is already in use\n joy.id = t.identifier;\n begin(joy, t.clientX, t.clientY);\n claimed = true;\n }\n if (claimed) e.preventDefault();\n };\n\n const onMove = (e: TouchEvent): void => {\n let claimed = false;\n for (const t of Array.from(e.changedTouches)) {\n for (const joy of joys) {\n if (joy.id === t.identifier) {\n drag(joy, t.clientX, t.clientY);\n claimed = true;\n }\n }\n }\n if (claimed) e.preventDefault();\n };\n\n const onEnd = (e: TouchEvent): void => {\n for (const t of Array.from(e.changedTouches)) {\n for (const joy of joys) if (joy.id === t.identifier) end(joy);\n }\n };\n\n window.addEventListener(\"touchstart\", onStart, { passive: false });\n window.addEventListener(\"touchmove\", onMove, { passive: false });\n window.addEventListener(\"touchend\", onEnd);\n window.addEventListener(\"touchcancel\", onEnd);\n\n return {\n element: layer,\n destroy: () => {\n window.removeEventListener(\"touchstart\", onStart);\n window.removeEventListener(\"touchmove\", onMove);\n window.removeEventListener(\"touchend\", onEnd);\n window.removeEventListener(\"touchcancel\", onEnd);\n controls.setMoveInput(0, 0);\n controls.setLookInput(0, 0);\n },\n };\n };\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@scenography/overlay",
3
+ "version": "0.2.0",
4
+ "description": "Framework-agnostic DOM overlay for scenography galleries: slot positioning, a component API, and built-in touch controls. No UI-library coupling.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.cts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.mts",
17
+ "default": "./dist/index.mjs"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.cts",
21
+ "default": "./dist/index.cjs"
22
+ }
23
+ }
24
+ },
25
+ "devDependencies": {
26
+ "@scenography/engine": "0.2.0"
27
+ },
28
+ "peerDependencies": {
29
+ "@scenography/engine": "0.2.0"
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown",
33
+ "dev": "tsdown --watch",
34
+ "typecheck": "tsc --noEmit"
35
+ }
36
+ }