@umicat/three-sdk 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/Input3D.js +51 -1
  2. package/package.json +1 -1
package/dist/Input3D.js CHANGED
@@ -6,6 +6,21 @@
6
6
  * and makes a quick tap always count. */
7
7
  const MIN_PRESS_MS = 80;
8
8
  const now = () => (typeof performance !== 'undefined' ? performance.now() : Date.now());
9
+ /**
10
+ * Everything needed to stop a long press from being read as "select this text".
11
+ *
12
+ * A thumbstick REQUIRES holding still, which is exactly the gesture iOS uses to
13
+ * start a selection — so on a phone the Copy / Look Up / Translate callout came
14
+ * up over the game the first time anyone tried to walk. `user-select` alone is
15
+ * not enough: `-webkit-touch-callout` is a separate switch, and neither is
16
+ * implied by `touch-action: none`.
17
+ */
18
+ const NO_SELECTION = {
19
+ userSelect: 'none',
20
+ WebkitUserSelect: 'none',
21
+ WebkitTouchCallout: 'none',
22
+ WebkitTapHighlightColor: 'transparent',
23
+ };
9
24
  const isTouchDevice = () => typeof matchMedia === 'function' &&
10
25
  matchMedia('(pointer: coarse)').matches &&
11
26
  !matchMedia('(pointer: fine)').matches;
@@ -155,8 +170,9 @@ export class Input3D {
155
170
  // some ancestor happens to be positioned, and "happens to be" is not a
156
171
  // layout strategy.
157
172
  position: 'fixed', inset: '0', pointerEvents: 'none',
158
- touchAction: 'none', userSelect: 'none', zIndex: '10',
173
+ touchAction: 'none', zIndex: '10',
159
174
  });
175
+ Object.assign(root.style, NO_SELECTION);
160
176
  const floating = this.stickMode === 'floating';
161
177
  // The zone a thumb may land on to summon the stick. It is the LEFT HALF,
162
178
  // not the pad: the whole point is that the player does not have to find
@@ -166,6 +182,7 @@ export class Input3D {
166
182
  position: 'absolute', left: '0', top: '0', width: '50%', height: '100%',
167
183
  pointerEvents: floating ? 'auto' : 'none',
168
184
  });
185
+ Object.assign(zone.style, NO_SELECTION);
169
186
  const pad = document.createElement('div');
170
187
  Object.assign(pad.style, floating ? {
171
188
  position: 'absolute', width: '30vmin', height: '30vmin',
@@ -213,6 +230,11 @@ export class Input3D {
213
230
  color: 'rgba(255,255,255,0.85)', font: '600 4vmin/1 system-ui, sans-serif',
214
231
  });
215
232
  el.textContent = label;
233
+ // Explicitly, not by inheritance. The label IS text -- a glyph in a div
234
+ // -- and long-pressing the attack button is how the iOS Copy / Look Up /
235
+ // Translate callout came up mid-fight, with the selection handles
236
+ // clamped around the little crossed swords.
237
+ Object.assign(el.style, NO_SELECTION);
216
238
  return el;
217
239
  };
218
240
  const btn = makeButton('▲');
@@ -225,6 +247,30 @@ export class Input3D {
225
247
  root.append(zone, pad, cluster);
226
248
  container.appendChild(root);
227
249
  this.root = root;
250
+ // And the page underneath. The overlay covers the controls, but a press
251
+ // that lands on the HUD or the canvas would still summon the callout — and
252
+ // a game is not a document; there is nothing here to select.
253
+ //
254
+ // A stylesheet rather than inline styles, so form fields can opt back IN:
255
+ // blanket `user-select: none` on <body> can stop a player selecting text
256
+ // inside their own name field, and a game that takes typed input would
257
+ // have inherited a bug from its movement controls.
258
+ const doc = container.ownerDocument;
259
+ const style = doc.createElement('style');
260
+ style.dataset.umicatTouch = '';
261
+ style.textContent = `
262
+ html, body { -webkit-user-select: none; user-select: none;
263
+ -webkit-touch-callout: none;
264
+ -webkit-tap-highlight-color: transparent; }
265
+ input, textarea, select, [contenteditable] {
266
+ -webkit-user-select: text; user-select: text;
267
+ -webkit-touch-callout: default; }
268
+ `;
269
+ doc.head.appendChild(style);
270
+ this.cleanups.push(() => style.remove());
271
+ const noMenu = (e) => e.preventDefault();
272
+ doc.addEventListener('contextmenu', noMenu);
273
+ this.cleanups.push(() => doc.removeEventListener('contextmenu', noMenu));
228
274
  // Track by pointerId so a thumb on the stick and a thumb on the button do
229
275
  // not fight over one piece of state.
230
276
  let stickId = null;
@@ -281,6 +327,9 @@ export class Input3D {
281
327
  // not — which is the point: `direction()` reads the same either way.
282
328
  const grip = floating ? zone : pad;
283
329
  on(grip, 'pointerdown', (e) => {
330
+ // Cancel the default gesture: on iOS, holding still on the screen is how
331
+ // a text selection begins, and holding still is the entire thumbstick.
332
+ e.preventDefault();
284
333
  // One thumb drives the stick. A second finger landing in the zone must
285
334
  // not move the origin out from under the first.
286
335
  if (stickId !== null)
@@ -315,6 +364,7 @@ export class Input3D {
315
364
  this.cleanups.push(() => el.removeEventListener(ev, h));
316
365
  };
317
366
  on('pointerdown', (e) => {
367
+ // Cancels the selection gesture as well as the default press behaviour.
318
368
  e.preventDefault();
319
369
  el.setPointerCapture(e.pointerId);
320
370
  this.touchHeld.add(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umicat/three-sdk",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Three.js runtime for Umicat games: the scene3d design format, its loader with physics, a kinematic character controller, and the Umicat platform via @umicat/platform-sdk.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",