@scenography/overlay 0.2.0 → 0.3.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 CHANGED
@@ -122,6 +122,11 @@ function ensureStyles() {
122
122
  * mouse). Multi-touch: both can be active at once. Which half is which follows
123
123
  * `handedness`. Works on any touch-capable device (no media-query gating), so it
124
124
  * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
125
+ *
126
+ * Mode-aware via `ctx.gallery.ctx.viewControls`: in **VR** (gyro aims, no split)
127
+ * a finger anywhere is the *single move* joystick — the gyro handles look, so the
128
+ * look stick is dropped; when the `joysticks` flag is off (**Cardboard**) no stick
129
+ * is drawn at all. Switches live on `controls:change`.
125
130
  */
126
131
  function touchJoysticks(options = {}) {
127
132
  const radius = options.radius ?? 60;
@@ -129,6 +134,11 @@ function touchJoysticks(options = {}) {
129
134
  return (ctx) => {
130
135
  ensureStyles();
131
136
  const controls = ctx.gallery.ctx.controls;
137
+ const viewControls = ctx.gallery.ctx.viewControls;
138
+ const isVr = () => {
139
+ const flags = viewControls.get();
140
+ return flags.gyro && !flags.splitScreen;
141
+ };
132
142
  const layer = document.createElement("div");
133
143
  layer.className = "sg-joysticks";
134
144
  layer.style.pointerEvents = "none";
@@ -165,6 +175,7 @@ function touchJoysticks(options = {}) {
165
175
  else controls.setLookInput(nx, -ny);
166
176
  };
167
177
  const roleForX = (x) => {
178
+ if (isVr()) return "move";
168
179
  return x < window.innerWidth / 2 === (resolveHand() === "right") ? "move" : "look";
169
180
  };
170
181
  const begin = (joy, x, y) => {
@@ -193,6 +204,7 @@ function touchJoysticks(options = {}) {
193
204
  reset(joy);
194
205
  };
195
206
  const onStart = (e) => {
207
+ if (!viewControls.get().joysticks) return;
196
208
  let claimed = false;
197
209
  for (const t of Array.from(e.changedTouches)) {
198
210
  if (document.elementFromPoint(t.clientX, t.clientY)?.closest(".sg-overlay__item")) continue;
@@ -219,6 +231,11 @@ function touchJoysticks(options = {}) {
219
231
  window.addEventListener("touchmove", onMove, { passive: false });
220
232
  window.addEventListener("touchend", onEnd);
221
233
  window.addEventListener("touchcancel", onEnd);
234
+ const offChange = ctx.gallery.on("controls:change", () => {
235
+ for (const joy of joys) if (joy.id !== null) end(joy);
236
+ controls.setMoveInput(0, 0);
237
+ controls.setLookInput(0, 0);
238
+ });
222
239
  return {
223
240
  element: layer,
224
241
  destroy: () => {
@@ -226,6 +243,7 @@ function touchJoysticks(options = {}) {
226
243
  window.removeEventListener("touchmove", onMove);
227
244
  window.removeEventListener("touchend", onEnd);
228
245
  window.removeEventListener("touchcancel", onEnd);
246
+ offChange();
229
247
  controls.setMoveInput(0, 0);
230
248
  controls.setLookInput(0, 0);
231
249
  }
@@ -1 +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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"}
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 *\n * Mode-aware via `ctx.gallery.ctx.viewControls`: in **VR** (gyro aims, no split)\n * a finger anywhere is the *single move* joystick — the gyro handles look, so the\n * look stick is dropped; when the `joysticks` flag is off (**Cardboard**) no stick\n * is drawn at all. Switches live on `controls:change`.\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 const viewControls = ctx.gallery.ctx.viewControls;\n // VR: the gyro aims, so a touch anywhere is the single *move* stick, no look.\n const isVr = (): boolean => {\n const flags = viewControls.get();\n return flags.gyro && !flags.splitScreen;\n };\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 // In VR the gyro owns look, so every touch is the single move stick.\n const roleForX = (x: number): Role => {\n if (isVr()) return \"move\";\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 // Joysticks off (e.g. Cardboard, where selection is by gaze): ignore touches.\n if (!viewControls.get().joysticks) return;\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 // On a mode switch, release any active stick and clear both channels so a\n // lingering look value (from touch mode) doesn't stick when VR/Cardboard take\n // over, and the look stick's visual doesn't linger.\n const offChange = ctx.gallery.on(\"controls:change\", () => {\n for (const joy of joys) if (joy.id !== null) end(joy);\n controls.setMoveInput(0, 0);\n controls.setLookInput(0, 0);\n });\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 offChange();\n controls.setMoveInput(0, 0);\n controls.setLookInput(0, 0);\n },\n };\n };\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -85,6 +85,11 @@ interface TouchJoysticksOptions {
85
85
  * mouse). Multi-touch: both can be active at once. Which half is which follows
86
86
  * `handedness`. Works on any touch-capable device (no media-query gating), so it
87
87
  * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
88
+ *
89
+ * Mode-aware via `ctx.gallery.ctx.viewControls`: in **VR** (gyro aims, no split)
90
+ * a finger anywhere is the *single move* joystick — the gyro handles look, so the
91
+ * look stick is dropped; when the `joysticks` flag is off (**Cardboard**) no stick
92
+ * is drawn at all. Switches live on `controls:change`.
88
93
  */
89
94
  declare function touchJoysticks(options?: TouchJoysticksOptions): OverlayComponent;
90
95
  //#endregion
package/dist/index.d.mts CHANGED
@@ -85,6 +85,11 @@ interface TouchJoysticksOptions {
85
85
  * mouse). Multi-touch: both can be active at once. Which half is which follows
86
86
  * `handedness`. Works on any touch-capable device (no media-query gating), so it
87
87
  * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
88
+ *
89
+ * Mode-aware via `ctx.gallery.ctx.viewControls`: in **VR** (gyro aims, no split)
90
+ * a finger anywhere is the *single move* joystick — the gyro handles look, so the
91
+ * look stick is dropped; when the `joysticks` flag is off (**Cardboard**) no stick
92
+ * is drawn at all. Switches live on `controls:change`.
88
93
  */
89
94
  declare function touchJoysticks(options?: TouchJoysticksOptions): OverlayComponent;
90
95
  //#endregion
package/dist/index.mjs CHANGED
@@ -121,6 +121,11 @@ function ensureStyles() {
121
121
  * mouse). Multi-touch: both can be active at once. Which half is which follows
122
122
  * `handedness`. Works on any touch-capable device (no media-query gating), so it
123
123
  * also responds to a laptop touchscreen. Drives `ctx.gallery.ctx.controls`.
124
+ *
125
+ * Mode-aware via `ctx.gallery.ctx.viewControls`: in **VR** (gyro aims, no split)
126
+ * a finger anywhere is the *single move* joystick — the gyro handles look, so the
127
+ * look stick is dropped; when the `joysticks` flag is off (**Cardboard**) no stick
128
+ * is drawn at all. Switches live on `controls:change`.
124
129
  */
125
130
  function touchJoysticks(options = {}) {
126
131
  const radius = options.radius ?? 60;
@@ -128,6 +133,11 @@ function touchJoysticks(options = {}) {
128
133
  return (ctx) => {
129
134
  ensureStyles();
130
135
  const controls = ctx.gallery.ctx.controls;
136
+ const viewControls = ctx.gallery.ctx.viewControls;
137
+ const isVr = () => {
138
+ const flags = viewControls.get();
139
+ return flags.gyro && !flags.splitScreen;
140
+ };
131
141
  const layer = document.createElement("div");
132
142
  layer.className = "sg-joysticks";
133
143
  layer.style.pointerEvents = "none";
@@ -164,6 +174,7 @@ function touchJoysticks(options = {}) {
164
174
  else controls.setLookInput(nx, -ny);
165
175
  };
166
176
  const roleForX = (x) => {
177
+ if (isVr()) return "move";
167
178
  return x < window.innerWidth / 2 === (resolveHand() === "right") ? "move" : "look";
168
179
  };
169
180
  const begin = (joy, x, y) => {
@@ -192,6 +203,7 @@ function touchJoysticks(options = {}) {
192
203
  reset(joy);
193
204
  };
194
205
  const onStart = (e) => {
206
+ if (!viewControls.get().joysticks) return;
195
207
  let claimed = false;
196
208
  for (const t of Array.from(e.changedTouches)) {
197
209
  if (document.elementFromPoint(t.clientX, t.clientY)?.closest(".sg-overlay__item")) continue;
@@ -218,6 +230,11 @@ function touchJoysticks(options = {}) {
218
230
  window.addEventListener("touchmove", onMove, { passive: false });
219
231
  window.addEventListener("touchend", onEnd);
220
232
  window.addEventListener("touchcancel", onEnd);
233
+ const offChange = ctx.gallery.on("controls:change", () => {
234
+ for (const joy of joys) if (joy.id !== null) end(joy);
235
+ controls.setMoveInput(0, 0);
236
+ controls.setLookInput(0, 0);
237
+ });
221
238
  return {
222
239
  element: layer,
223
240
  destroy: () => {
@@ -225,6 +242,7 @@ function touchJoysticks(options = {}) {
225
242
  window.removeEventListener("touchmove", onMove);
226
243
  window.removeEventListener("touchend", onEnd);
227
244
  window.removeEventListener("touchcancel", onEnd);
245
+ offChange();
228
246
  controls.setMoveInput(0, 0);
229
247
  controls.setLookInput(0, 0);
230
248
  }
@@ -1 +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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"}
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 *\n * Mode-aware via `ctx.gallery.ctx.viewControls`: in **VR** (gyro aims, no split)\n * a finger anywhere is the *single move* joystick — the gyro handles look, so the\n * look stick is dropped; when the `joysticks` flag is off (**Cardboard**) no stick\n * is drawn at all. Switches live on `controls:change`.\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 const viewControls = ctx.gallery.ctx.viewControls;\n // VR: the gyro aims, so a touch anywhere is the single *move* stick, no look.\n const isVr = (): boolean => {\n const flags = viewControls.get();\n return flags.gyro && !flags.splitScreen;\n };\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 // In VR the gyro owns look, so every touch is the single move stick.\n const roleForX = (x: number): Role => {\n if (isVr()) return \"move\";\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 // Joysticks off (e.g. Cardboard, where selection is by gaze): ignore touches.\n if (!viewControls.get().joysticks) return;\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 // On a mode switch, release any active stick and clear both channels so a\n // lingering look value (from touch mode) doesn't stick when VR/Cardboard take\n // over, and the look stick's visual doesn't linger.\n const offChange = ctx.gallery.on(\"controls:change\", () => {\n for (const joy of joys) if (joy.id !== null) end(joy);\n controls.setMoveInput(0, 0);\n controls.setLookInput(0, 0);\n });\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 offChange();\n controls.setMoveInput(0, 0);\n controls.setLookInput(0, 0);\n },\n };\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scenography/overlay",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Framework-agnostic DOM overlay for scenography galleries: slot positioning, a component API, and built-in touch controls. No UI-library coupling.",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -23,10 +23,10 @@
23
23
  }
24
24
  },
25
25
  "devDependencies": {
26
- "@scenography/engine": "0.2.0"
26
+ "@scenography/engine": "0.3.0"
27
27
  },
28
28
  "peerDependencies": {
29
- "@scenography/engine": "0.2.0"
29
+ "@scenography/engine": "0.3.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsdown",