@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 +18 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +18 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -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.
|
|
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.
|
|
26
|
+
"@scenography/engine": "0.3.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@scenography/engine": "0.
|
|
29
|
+
"@scenography/engine": "0.3.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsdown",
|