aimeat 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/dist/build-stamp.json +4 -4
  2. package/dist/locales/en.json +9103 -9101
  3. package/dist/locales/es.json +9095 -0
  4. package/dist/locales/fi.json +9103 -9101
  5. package/dist/public/cortex-bundled/aimeat-input.js +611 -0
  6. package/dist/public/cortex-bundled/aimeat-input.yaml +149 -0
  7. package/dist/public/css/views/agents-detail.css +11 -0
  8. package/dist/public/js/utils.js +12 -7
  9. package/dist/public/spa.html +5 -2
  10. package/dist/public/views/admin/email-tab.js +459 -458
  11. package/dist/public/views/profile/agents/agent-card.js +25 -0
  12. package/dist/public/views/profile/agents/scope-model.js +296 -288
  13. package/dist/public/views/profile/agents-tab.js +26 -3
  14. package/dist/scripts/check-ai-disclosure.d.ts.map +1 -1
  15. package/dist/scripts/check-ai-disclosure.js +33 -18
  16. package/dist/scripts/check-ai-disclosure.js.map +1 -1
  17. package/dist/scripts/check-locales.d.ts +2 -0
  18. package/dist/scripts/check-locales.d.ts.map +1 -0
  19. package/dist/scripts/check-locales.js +46 -0
  20. package/dist/scripts/check-locales.js.map +1 -0
  21. package/dist/scripts/lib/locale-files.d.ts +40 -0
  22. package/dist/scripts/lib/locale-files.d.ts.map +1 -0
  23. package/dist/scripts/lib/locale-files.js +181 -0
  24. package/dist/scripts/lib/locale-files.js.map +1 -0
  25. package/dist/scripts/locale-extract.d.ts +2 -0
  26. package/dist/scripts/locale-extract.d.ts.map +1 -0
  27. package/dist/scripts/locale-extract.js +99 -0
  28. package/dist/scripts/locale-extract.js.map +1 -0
  29. package/dist/scripts/locale-merge.d.ts +2 -0
  30. package/dist/scripts/locale-merge.d.ts.map +1 -0
  31. package/dist/scripts/locale-merge.js +114 -0
  32. package/dist/scripts/locale-merge.js.map +1 -0
  33. package/dist/src/auth/middleware.d.ts +6 -0
  34. package/dist/src/auth/middleware.d.ts.map +1 -1
  35. package/dist/src/auth/middleware.js +24 -4
  36. package/dist/src/auth/middleware.js.map +1 -1
  37. package/dist/src/cli/connect/mcp/tools/agent-management.d.ts +1 -0
  38. package/dist/src/cli/connect/mcp/tools/agent-management.d.ts.map +1 -1
  39. package/dist/src/cli/connect/mcp/tools/agent-management.js +9 -0
  40. package/dist/src/cli/connect/mcp/tools/agent-management.js.map +1 -1
  41. package/dist/src/cli/connect/mcp/tools/schedules.d.ts +3 -0
  42. package/dist/src/cli/connect/mcp/tools/schedules.d.ts.map +1 -1
  43. package/dist/src/cli/connect/mcp/tools/schedules.js +5 -0
  44. package/dist/src/cli/connect/mcp/tools/schedules.js.map +1 -1
  45. package/dist/src/cli/connect/tool-call-defs-agent.d.ts +2 -0
  46. package/dist/src/cli/connect/tool-call-defs-agent.d.ts.map +1 -1
  47. package/dist/src/cli/connect/tool-call-defs-agent.js +16 -0
  48. package/dist/src/cli/connect/tool-call-defs-agent.js.map +1 -1
  49. package/dist/src/cli/connect/tool-call-defs-organism.d.ts.map +1 -1
  50. package/dist/src/cli/connect/tool-call-defs-organism.js +6 -0
  51. package/dist/src/cli/connect/tool-call-defs-organism.js.map +1 -1
  52. package/dist/src/config-operator.d.ts +4 -1
  53. package/dist/src/config-operator.d.ts.map +1 -1
  54. package/dist/src/config-operator.js +4 -4
  55. package/dist/src/config-operator.js.map +1 -1
  56. package/dist/src/data/library-packs/cortex.d.ts +3 -0
  57. package/dist/src/data/library-packs/cortex.d.ts.map +1 -1
  58. package/dist/src/data/library-packs/cortex.js +58 -0
  59. package/dist/src/data/library-packs/cortex.js.map +1 -1
  60. package/dist/src/generated/api-types.d.ts +90 -2
  61. package/dist/src/generated/api-types.d.ts.map +1 -1
  62. package/dist/src/i18n.d.ts +1 -1
  63. package/dist/src/i18n.d.ts.map +1 -1
  64. package/dist/src/i18n.js +13 -6
  65. package/dist/src/i18n.js.map +1 -1
  66. package/dist/src/mcp/agent-management.d.ts +6 -2
  67. package/dist/src/mcp/agent-management.d.ts.map +1 -1
  68. package/dist/src/mcp/agent-management.js +34 -3
  69. package/dist/src/mcp/agent-management.js.map +1 -1
  70. package/dist/src/mcp/agent-schedules.d.ts +6 -2
  71. package/dist/src/mcp/agent-schedules.d.ts.map +1 -1
  72. package/dist/src/mcp/agent-schedules.js +22 -1
  73. package/dist/src/mcp/agent-schedules.js.map +1 -1
  74. package/dist/src/mcp/annotations.d.ts +1 -0
  75. package/dist/src/mcp/annotations.d.ts.map +1 -1
  76. package/dist/src/mcp/annotations.js +5 -0
  77. package/dist/src/mcp/annotations.js.map +1 -1
  78. package/dist/src/mcp/catalog/definitions/agent-messaging.d.ts +2 -0
  79. package/dist/src/mcp/catalog/definitions/agent-messaging.d.ts.map +1 -1
  80. package/dist/src/mcp/catalog/definitions/agent-messaging.js +12 -0
  81. package/dist/src/mcp/catalog/definitions/agent-messaging.js.map +1 -1
  82. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.d.ts.map +1 -1
  83. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js +7 -0
  84. package/dist/src/mcp/catalog/definitions/schedules-tasks-memory.js.map +1 -1
  85. package/dist/src/mcp/catalog/scopes.d.ts.map +1 -1
  86. package/dist/src/mcp/catalog/scopes.js +5 -0
  87. package/dist/src/mcp/catalog/scopes.js.map +1 -1
  88. package/dist/src/mcp/catalog/surfaces.d.ts.map +1 -1
  89. package/dist/src/mcp/catalog/surfaces.js +4 -2
  90. package/dist/src/mcp/catalog/surfaces.js.map +1 -1
  91. package/dist/src/routes/admin-features.d.ts.map +1 -1
  92. package/dist/src/routes/admin-features.js +5 -4
  93. package/dist/src/routes/admin-features.js.map +1 -1
  94. package/dist/src/routes/agents/constants.d.ts +18 -2
  95. package/dist/src/routes/agents/constants.d.ts.map +1 -1
  96. package/dist/src/routes/agents/constants.js +18 -2
  97. package/dist/src/routes/agents/constants.js.map +1 -1
  98. package/dist/src/routes/agents/device-auth.d.ts +2 -0
  99. package/dist/src/routes/agents/device-auth.d.ts.map +1 -1
  100. package/dist/src/routes/agents/device-auth.js +22 -2
  101. package/dist/src/routes/agents/device-auth.js.map +1 -1
  102. package/dist/src/routes/agents/management.d.ts +7 -0
  103. package/dist/src/routes/agents/management.d.ts.map +1 -1
  104. package/dist/src/routes/agents/management.js +59 -4
  105. package/dist/src/routes/agents/management.js.map +1 -1
  106. package/dist/src/routes/agents/profile-metadata.d.ts +4 -0
  107. package/dist/src/routes/agents/profile-metadata.d.ts.map +1 -1
  108. package/dist/src/routes/agents/profile-metadata.js +31 -2
  109. package/dist/src/routes/agents/profile-metadata.js.map +1 -1
  110. package/dist/src/routes/agents/registration.d.ts +2 -0
  111. package/dist/src/routes/agents/registration.d.ts.map +1 -1
  112. package/dist/src/routes/agents/registration.js +3 -0
  113. package/dist/src/routes/agents/registration.js.map +1 -1
  114. package/dist/src/routes/ai-transparency.d.ts.map +1 -1
  115. package/dist/src/routes/ai-transparency.js +5 -1
  116. package/dist/src/routes/ai-transparency.js.map +1 -1
  117. package/dist/src/routes/auth.d.ts +3 -0
  118. package/dist/src/routes/auth.d.ts.map +1 -1
  119. package/dist/src/routes/auth.js +22 -5
  120. package/dist/src/routes/auth.js.map +1 -1
  121. package/dist/src/routes/portal.d.ts +3 -0
  122. package/dist/src/routes/portal.d.ts.map +1 -1
  123. package/dist/src/routes/portal.js +39 -17
  124. package/dist/src/routes/portal.js.map +1 -1
  125. package/dist/src/routes/schedules.d.ts +3 -0
  126. package/dist/src/routes/schedules.d.ts.map +1 -1
  127. package/dist/src/routes/schedules.js +16 -29
  128. package/dist/src/routes/schedules.js.map +1 -1
  129. package/dist/src/services/agent-profile-write.d.ts +13 -1
  130. package/dist/src/services/agent-profile-write.d.ts.map +1 -1
  131. package/dist/src/services/agent-profile-write.js +55 -0
  132. package/dist/src/services/agent-profile-write.js.map +1 -1
  133. package/dist/src/services/build-app-prompt.d.ts.map +1 -1
  134. package/dist/src/services/build-app-prompt.js +22 -0
  135. package/dist/src/services/build-app-prompt.js.map +1 -1
  136. package/dist/src/services/email-templates.d.ts.map +1 -1
  137. package/dist/src/services/email-templates.js +79 -3
  138. package/dist/src/services/email-templates.js.map +1 -1
  139. package/dist/src/services/notification-templates.d.ts +5 -3
  140. package/dist/src/services/notification-templates.d.ts.map +1 -1
  141. package/dist/src/services/notification-templates.js +23 -3
  142. package/dist/src/services/notification-templates.js.map +1 -1
  143. package/dist/src/services/schedule-write.d.ts +25 -2
  144. package/dist/src/services/schedule-write.d.ts.map +1 -1
  145. package/dist/src/services/schedule-write.js +44 -0
  146. package/dist/src/services/schedule-write.js.map +1 -1
  147. package/dist/src/storage/providers/postgres-kysely/db-types.d.ts +2 -0
  148. package/dist/src/storage/providers/postgres-kysely/db-types.d.ts.map +1 -1
  149. package/dist/src/storage/providers/postgres-kysely/methods/identity.d.ts.map +1 -1
  150. package/dist/src/storage/providers/postgres-kysely/methods/identity.js +5 -1
  151. package/dist/src/storage/providers/postgres-kysely/methods/identity.js.map +1 -1
  152. package/dist/src/storage/providers/postgres-kysely/methods/sessions.d.ts +1 -0
  153. package/dist/src/storage/providers/postgres-kysely/methods/sessions.d.ts.map +1 -1
  154. package/dist/src/storage/providers/postgres-kysely/methods/sessions.js +4 -0
  155. package/dist/src/storage/providers/postgres-kysely/methods/sessions.js.map +1 -1
  156. package/dist/src/storage/providers/postgres-kysely/migrations/0034_agent_console_url.sql +16 -0
  157. package/dist/src/storage/providers/postgres-kysely/migrations/0035_agent_registered_by.sql +19 -0
  158. package/dist/src/storage/providers/sqlite/methods/extensions-notify.d.ts +2 -0
  159. package/dist/src/storage/providers/sqlite/methods/extensions-notify.d.ts.map +1 -1
  160. package/dist/src/storage/providers/sqlite/methods/extensions-notify.js +4 -0
  161. package/dist/src/storage/providers/sqlite/methods/extensions-notify.js.map +1 -1
  162. package/dist/src/storage/providers/sqlite/methods/owner.d.ts +5 -0
  163. package/dist/src/storage/providers/sqlite/methods/owner.d.ts.map +1 -1
  164. package/dist/src/storage/providers/sqlite/methods/owner.js +13 -4
  165. package/dist/src/storage/providers/sqlite/methods/owner.js.map +1 -1
  166. package/dist/src/storage/providers/sqlite/repos/auth.d.ts +2 -0
  167. package/dist/src/storage/providers/sqlite/repos/auth.d.ts.map +1 -1
  168. package/dist/src/storage/providers/sqlite/repos/auth.js +4 -0
  169. package/dist/src/storage/providers/sqlite/repos/auth.js.map +1 -1
  170. package/dist/src/storage/providers/sqlite/schema-tables-1.d.ts +3 -0
  171. package/dist/src/storage/providers/sqlite/schema-tables-1.d.ts.map +1 -1
  172. package/dist/src/storage/providers/sqlite/schema-tables-1.js +4 -2
  173. package/dist/src/storage/providers/sqlite/schema-tables-1.js.map +1 -1
  174. package/dist/src/storage/providers/sqlite/schema-tables-2.d.ts +3 -0
  175. package/dist/src/storage/providers/sqlite/schema-tables-2.d.ts.map +1 -1
  176. package/dist/src/storage/providers/sqlite/schema-tables-2.js +4 -3
  177. package/dist/src/storage/providers/sqlite/schema-tables-2.js.map +1 -1
  178. package/dist/src/storage/providers/sqlite/schema-tables-3.d.ts +3 -0
  179. package/dist/src/storage/providers/sqlite/schema-tables-3.d.ts.map +1 -1
  180. package/dist/src/storage/providers/sqlite/schema-tables-3.js +6 -9
  181. package/dist/src/storage/providers/sqlite/schema-tables-3.js.map +1 -1
  182. package/dist/src/storage/providers/sqlite/schema.d.ts +6 -0
  183. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  184. package/dist/src/storage/providers/sqlite/schema.js +9 -0
  185. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  186. package/dist/src/storage/repositories/session.repository.d.ts +14 -0
  187. package/dist/src/storage/repositories/session.repository.d.ts.map +1 -1
  188. package/dist/src/storage/repositories/session.repository.js +2 -0
  189. package/dist/src/storage/repositories/session.repository.js.map +1 -1
  190. package/dist/src/storage/storage-factory.d.ts +6 -0
  191. package/dist/src/storage/storage-factory.d.ts.map +1 -1
  192. package/dist/src/storage/storage-factory.js +38 -19
  193. package/dist/src/storage/storage-factory.js.map +1 -1
  194. package/dist/src/storage/types/identity.d.ts +31 -0
  195. package/dist/src/storage/types/identity.d.ts.map +1 -1
  196. package/dist/src/utils/gaii.d.ts +11 -0
  197. package/dist/src/utils/gaii.d.ts.map +1 -1
  198. package/dist/src/utils/gaii.js +16 -0
  199. package/dist/src/utils/gaii.js.map +1 -1
  200. package/dist/static/sdk-libs/ai/strings.js +5 -2
  201. package/dist/static/sdk-libs/auth/i18n.js +9 -4
  202. package/dist/static/sdk-libs/auth/modal.js +11 -3
  203. package/dist/static/sdk-libs/auth/pill-strings.js +52 -0
  204. package/dist/static/sdk-libs/auth/pill.js +16 -2
  205. package/dist/static/sdk-libs/dist/aimeat-ai.js +30 -1
  206. package/dist/static/sdk-libs/dist/aimeat-auth.js +61 -5
  207. package/dist/static/sdk-libs/dist/aimeat-game.js +32 -2
  208. package/dist/static/sdk-libs/dist/aimeat-header.js +9 -4
  209. package/dist/static/sdk-libs/game/i18n.js +38 -7
  210. package/dist/static/sdk-libs/header/index.js +11 -5
  211. package/package.json +4 -1
@@ -0,0 +1,611 @@
1
+ /**
2
+ * @file aimeat-input.js
3
+ * @description The shared INPUT primitive for AIMEAT apps: tap, double-tap, long-press, swipe and
4
+ * drag as one Pointer Events implementation, with a keyboard equivalent for every gesture so the
5
+ * same code answers a finger and a keyboard. It owns gesture recognition and NOTHING about what a
6
+ * gesture means. Written because mobile input was the one part of the mobile story the platform
7
+ * had no answer for: the layout rules (overflow, viewport, the chrome strip) are covered in the
8
+ * build-app prompt and in fifteen appdev pitfalls, while touch handling was hand-rolled per app
9
+ * and per pack. `aimeat-viewport` remains the CAMERA for a movable surface; this pack is gestures
10
+ * on an element, and the two compose (see `claimFor`).
11
+ * @structure
12
+ * - injectCss() → structural CSS (touch-action, tap highlight, pad geometry)
13
+ * - tappable(el, onActivate, opts) → an element that behaves as a button on both inputs
14
+ * - on(el, handlers, opts) → gesture recognizer, returns a handle
15
+ * - haptic(kind) → navigator.vibrate where it exists, no-op elsewhere
16
+ * - pad(host, opts) → virtual thumbstick / dpad, WASD + arrows on desktop
17
+ * - isCoarse(), hasHover(), claimFor()
18
+ * @usage
19
+ * <script src="/v1/cortex/aimeat-input/libs/aimeat-input.js"></script>
20
+ * AIMEAT.input.tappable(btn, () => open(card)); // buttons and cards
21
+ * AIMEAT.input.on(list, { // gesture surfaces
22
+ * swipe: (dir) => dir === 'left' && archive(),
23
+ * longPress: () => menu(),
24
+ * }, { axis: 'x' }); // vertical scrolling survives
25
+ * @version-history
26
+ * v1.0.0 — 2026-08-12 — Initial release. Pointer Events only (never touch+mouse together),
27
+ * one gesture state machine per element, keyboard equivalence on by default, touch-action
28
+ * derived from the handlers you register, and the trailing synthetic click suppressed once a
29
+ * gesture has consumed the interaction. The pad contains its own absolutely positioned parts
30
+ * (overflow: clip) and re-places them on resize: browser verification at 390px caught this pack
31
+ * inflating the page by 444px, which is the very failure the mobile checklist warns about.
32
+ */
33
+ (function (global) {
34
+ 'use strict';
35
+
36
+ var AIMEAT = global.AIMEAT = global.AIMEAT || {};
37
+
38
+ // Idempotent, in case a pack embeds a copy the way aimeat-dag embeds the viewport.
39
+ if (AIMEAT.input) return;
40
+
41
+ var VERSION = '1.0.0';
42
+
43
+ // Defaults chosen from the platform conventions rather than invented: 44px is the touch target
44
+ // the mobile checklist already asks for, 500ms is the long-press users bring from the OS, and a
45
+ // 10px slop is what separates a tap from the start of a scroll on a real thumb.
46
+ var TAP_SLOP = 10; // client px a tap may drift and still count as a tap
47
+ var LONG_PRESS_MS = 500;
48
+ var DOUBLE_TAP_MS = 300;
49
+ var SWIPE_MIN_PX = 40;
50
+ var SWIPE_MAX_MS = 600;
51
+ var CLICK_SUPPRESS_MS = 400;
52
+
53
+ function nowMs() {
54
+ return (global.performance && global.performance.now) ? global.performance.now() : Date.now();
55
+ }
56
+
57
+ function media(q) {
58
+ try { return !!(global.matchMedia && global.matchMedia(q).matches); } catch (e) { /* noop */ }
59
+ return false;
60
+ }
61
+
62
+ /** True on a finger/stylus-first device. Use it to CHOOSE a layout, never to skip mouse support. */
63
+ function isCoarse() { return media('(pointer: coarse)'); }
64
+
65
+ /** True where a real hover exists. A control whose meaning only appears on hover is unreachable without it. */
66
+ function hasHover() { return media('(hover: hover)'); }
67
+
68
+ function resolveEl(elOrSelector) {
69
+ return typeof elOrSelector === 'string' ? document.querySelector(elOrSelector) : elOrSelector;
70
+ }
71
+
72
+ // Structural CSS only — no colour, no spacing, no font. The app owns every cosmetic, so injection
73
+ // order never matters and nothing here can fight an app stylesheet.
74
+ var cssInjected = false;
75
+ function injectCss() {
76
+ if (cssInjected) return;
77
+ var css = [
78
+ /* touch-action: manipulation removes the legacy 300ms tap delay without disabling scroll or
79
+ pinch-zoom; -webkit-tap-highlight-color kills the grey iOS flash so the app's own pressed
80
+ state is the only feedback. min-height/width come from a variable the app can raise but
81
+ should not lower: 44px is the smallest target a thumb hits reliably. */
82
+ '.ai-tap { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }',
83
+ '.ai-tap-size { min-height: var(--ai-touch, 44px); min-width: var(--ai-touch, 44px); }',
84
+ '.ai-grab { touch-action: none; user-select: none; -webkit-user-select: none; }',
85
+ '.ai-pan-y { touch-action: pan-y; }', /* horizontal gesture, vertical scrolling survives */
86
+ '.ai-pan-x { touch-action: pan-x; }',
87
+ /* The pad is positioned and sized here; everything visible about it is the app's own CSS on
88
+ .ai-pad-base / .ai-pad-stick, which start with no paint at all. */
89
+ /* overflow containment is load-bearing, not cosmetic: the base and the stick are absolutely
90
+ positioned, so before the first touch (and after a viewport change that leaves a stale
91
+ placement) they sit partly outside the host. Uncontained, they widen the PAGE, and one
92
+ element wider than the screen makes a phone shrink-to-fit the whole document — the exact
93
+ bug the mobile checklist exists to prevent. `clip` rather than `hidden` so the host does
94
+ not become a scroll container. Measured at 390px: 444px of horizontal overflow without it. */
95
+ '.ai-pad { position: relative; touch-action: none; user-select: none; -webkit-user-select: none; overflow: hidden; overflow: clip; }',
96
+ '.ai-pad-base { position: absolute; width: var(--ai-pad-size, 128px); height: var(--ai-pad-size, 128px); margin-left: calc(var(--ai-pad-size, 128px) / -2); margin-top: calc(var(--ai-pad-size, 128px) / -2); border-radius: 50%; pointer-events: none; }',
97
+ '.ai-pad-stick { position: absolute; width: var(--ai-pad-stick, 56px); height: var(--ai-pad-stick, 56px); margin-left: calc(var(--ai-pad-stick, 56px) / -2); margin-top: calc(var(--ai-pad-stick, 56px) / -2); border-radius: 50%; pointer-events: none; will-change: transform; }',
98
+ '.ai-pad-idle .ai-pad-base, .ai-pad-idle .ai-pad-stick { opacity: var(--ai-pad-idle-opacity, 0.35); }',
99
+ '@media (prefers-reduced-motion: reduce) { .ai-pad-stick { transition: none; } }',
100
+ ].join('\n');
101
+ var style = document.createElement('style');
102
+ style.setAttribute('data-aimeat-input', '1');
103
+ style.textContent = css;
104
+ document.head.appendChild(style);
105
+ cssInjected = true;
106
+ }
107
+
108
+ /**
109
+ * Vibrate, where the device and the browser both allow it. Silent everywhere else, including
110
+ * every desktop browser and iOS Safari, so it is safe to call unconditionally — it is never the
111
+ * only feedback a user gets, only a reinforcement of feedback that is already on screen.
112
+ *
113
+ * @param {'light'|'medium'|'heavy'|'success'|'warning'|number|number[]} [kind='light']
114
+ * @returns {boolean} whether the device actually buzzed
115
+ */
116
+ var HAPTICS = { light: 10, medium: 20, heavy: 35, success: [12, 40, 12], warning: [24, 60, 24] };
117
+ function haptic(kind) {
118
+ var pattern = typeof kind === 'number' || Array.isArray(kind) ? kind : HAPTICS[kind || 'light'];
119
+ if (!pattern || !global.navigator || !global.navigator.vibrate) return false;
120
+ try { return !!global.navigator.vibrate(pattern); } catch (e) { /* noop: blocked by policy */ }
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Make an element behave as a button for a finger, a mouse and a keyboard alike.
126
+ *
127
+ * This listens for `click`, NOT for `touchstart` or `pointerdown`, and that is the whole point:
128
+ * the browser already synthesizes a correct click from a tap, routes Enter and Space to it, and
129
+ * announces it to a screen reader. Hand-rolling touchstart is what produces the double-fire, the
130
+ * gesture that triggers mid-scroll, and the control a keyboard can never reach. What the browser
131
+ * does NOT do on its own is remove the 300ms delay and the grey flash, so the class does that.
132
+ *
133
+ * Reach for `on()` instead only when a click is genuinely not enough: a canvas, a map, a surface
134
+ * where the same pointer can also drag or swipe.
135
+ *
136
+ * @param {Element|string} elOrSelector
137
+ * @param {Function} onActivate (ev) → void
138
+ * @param {Object} [opts]
139
+ * @param {boolean} [opts.size=true] enforce the 44px minimum target (--ai-touch)
140
+ * @param {string} [opts.role='button'] set only when the element has no implicit one
141
+ * @param {string} [opts.label] accessible name, when the element has no text of its own
142
+ * @param {string|false} [opts.haptic='light'] buzz on activation
143
+ * @returns {{destroy: Function}}
144
+ */
145
+ function tappable(elOrSelector, onActivate, opts) {
146
+ opts = opts || {};
147
+ var el = resolveEl(elOrSelector);
148
+ if (!el) throw new Error('aimeat-input: element not found');
149
+ injectCss();
150
+
151
+ el.classList.add('ai-tap');
152
+ if (opts.size !== false) el.classList.add('ai-tap-size');
153
+
154
+ // A real <button> or <a href> already carries role, focus and keyboard activation. Adding them
155
+ // to something else is what makes a <div> reachable; adding them to a button is noise.
156
+ var tag = (el.tagName || '').toLowerCase();
157
+ var native = tag === 'button' || (tag === 'a' && el.hasAttribute('href')) || tag === 'input';
158
+ if (!native) {
159
+ if (!el.hasAttribute('role')) el.setAttribute('role', opts.role || 'button');
160
+ if (!el.hasAttribute('tabindex')) el.setAttribute('tabindex', '0');
161
+ }
162
+ if (opts.label && !el.hasAttribute('aria-label')) el.setAttribute('aria-label', opts.label);
163
+
164
+ function fire(ev) {
165
+ if (el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true') return;
166
+ if (opts.haptic !== false) haptic(opts.haptic || 'light');
167
+ onActivate(ev);
168
+ }
169
+ function onClick(ev) { fire(ev); }
170
+ function onKey(ev) {
171
+ // Space scrolls the page and Enter submits a form; both are wrong here, and only for the
172
+ // element we made focusable ourselves — a native control routes its own keys.
173
+ if (native) return;
174
+ if (ev.key !== 'Enter' && ev.key !== ' ' && ev.key !== 'Spacebar') return;
175
+ ev.preventDefault();
176
+ fire(ev);
177
+ }
178
+
179
+ el.addEventListener('click', onClick);
180
+ el.addEventListener('keydown', onKey);
181
+
182
+ return {
183
+ destroy: function () {
184
+ el.removeEventListener('click', onClick);
185
+ el.removeEventListener('keydown', onKey);
186
+ el.classList.remove('ai-tap', 'ai-tap-size');
187
+ },
188
+ };
189
+ }
190
+
191
+ var DIR_KEYS = { ArrowLeft: 'left', ArrowRight: 'right', ArrowUp: 'up', ArrowDown: 'down' };
192
+
193
+ function directionOf(dx, dy, axis) {
194
+ var ax = Math.abs(dx), ay = Math.abs(dy);
195
+ if (axis === 'x') return dx < 0 ? 'left' : 'right';
196
+ if (axis === 'y') return dy < 0 ? 'up' : 'down';
197
+ if (ax >= ay) return dx < 0 ? 'left' : 'right';
198
+ return dy < 0 ? 'up' : 'down';
199
+ }
200
+
201
+ // Which scrolling the browser must keep doing while this element is under a finger. Derived from
202
+ // the handlers actually registered, because the wrong value here is the single most damaging
203
+ // mistake in touch code: `touch-action: none` on a list kills scrolling for the whole list, and
204
+ // no value at all makes a horizontal swipe fight the page.
205
+ function deriveTouchAction(h, axis) {
206
+ if (h.dragMove || h.dragStart || h.dragEnd) return 'none';
207
+ if (h.swipe) {
208
+ if (axis === 'x') return 'pan-y';
209
+ if (axis === 'y') return 'pan-x';
210
+ return 'none';
211
+ }
212
+ return 'manipulation';
213
+ }
214
+
215
+ /**
216
+ * Recognize gestures on one element. Pointer Events only, one gesture at a time, and every
217
+ * gesture that can be expressed with a keyboard is.
218
+ *
219
+ * @param {Element|string} elOrSelector
220
+ * @param {Object} handlers
221
+ * @param {Function} [handlers.tap] (ev, info) after a press that did not move or linger
222
+ * @param {Function} [handlers.doubleTap] (ev, info) two taps inside `doubleTapMs`
223
+ * @param {Function} [handlers.longPress] (ev, info) press held past `longPressMs` without moving
224
+ * @param {Function} [handlers.swipe] (dir, info) 'left'|'right'|'up'|'down'
225
+ * @param {Function} [handlers.dragStart] (ev, info) once the press passes the slop
226
+ * @param {Function} [handlers.dragMove] (ev, info) info carries dx, dy, and the per-event ddx/ddy
227
+ * @param {Function} [handlers.dragEnd] (ev, info) info carries the final dx, dy and velocity
228
+ * @param {Object} [opts]
229
+ * @param {'x'|'y'|'both'} [opts.axis='both'] which way swipes and drags are read. DECLARE THIS on
230
+ * anything that also scrolls: 'x' keeps vertical scrolling alive, 'both' takes the element out
231
+ * of scrolling entirely.
232
+ * @param {boolean} [opts.keyboard=true] arrows fire `swipe`, Enter/Space fire `tap`, and the
233
+ * context-menu key or a right click fires `longPress`
234
+ * @param {number} [opts.slop=10] px of drift a tap tolerates
235
+ * @param {number} [opts.longPressMs=500] @param {number} [opts.doubleTapMs=300]
236
+ * @param {number} [opts.swipeMinPx=40] @param {number} [opts.swipeMaxMs=600]
237
+ * @param {string|false} [opts.touchAction] override the derived value; false leaves CSS untouched
238
+ * @param {string|false} [opts.haptic='light'] buzz when longPress fires (the one gesture with no
239
+ * visual start of its own)
240
+ * @returns {{destroy: Function, update: Function, el: Element}}
241
+ */
242
+ function on(elOrSelector, handlers, opts) {
243
+ opts = opts || {};
244
+ var el = resolveEl(elOrSelector);
245
+ if (!el) throw new Error('aimeat-input: element not found');
246
+ var h = handlers || {};
247
+ injectCss();
248
+
249
+ var axis = opts.axis || 'both';
250
+ var slop = opts.slop != null ? opts.slop : TAP_SLOP;
251
+ var longMs = opts.longPressMs != null ? opts.longPressMs : LONG_PRESS_MS;
252
+ var dblMs = opts.doubleTapMs != null ? opts.doubleTapMs : DOUBLE_TAP_MS;
253
+ var swipeMin = opts.swipeMinPx != null ? opts.swipeMinPx : SWIPE_MIN_PX;
254
+ var swipeMax = opts.swipeMaxMs != null ? opts.swipeMaxMs : SWIPE_MAX_MS;
255
+ var keyboard = opts.keyboard !== false;
256
+
257
+ if (opts.touchAction !== false) {
258
+ el.style.touchAction = opts.touchAction || deriveTouchAction(h, axis);
259
+ if (h.dragMove || h.dragStart) el.classList.add('ai-grab');
260
+ }
261
+ // A gesture surface that reports taps has to be reachable without a pointer at all.
262
+ if (keyboard && (h.tap || h.swipe) && !el.hasAttribute('tabindex') && el.tabIndex < 0) {
263
+ el.setAttribute('tabindex', '0');
264
+ }
265
+
266
+ var g = null; // the live gesture, or null
267
+ var lastTapAt = 0;
268
+ var suppressClickUntil = 0;
269
+ var longTimer = null;
270
+
271
+ function clearLongTimer() {
272
+ if (longTimer) { clearTimeout(longTimer); longTimer = null; }
273
+ }
274
+
275
+ function info(extra) {
276
+ var base = {
277
+ dx: g ? g.x - g.sx : 0,
278
+ dy: g ? g.y - g.sy : 0,
279
+ duration: g ? nowMs() - g.t0 : 0,
280
+ pointerType: g ? g.pointerType : 'keyboard',
281
+ source: g ? 'pointer' : 'keyboard',
282
+ };
283
+ if (extra) for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k)) base[k] = extra[k];
284
+ return base;
285
+ }
286
+
287
+ // Once a gesture has consumed the interaction, the browser still delivers the synthetic click
288
+ // that follows a tap. Without this the consumer gets the gesture AND a click, which is the
289
+ // double-fire everyone hits when hand-rolling touch.
290
+ function consumeClick() { suppressClickUntil = nowMs() + CLICK_SUPPRESS_MS; }
291
+ function onClickCapture(ev) {
292
+ if (nowMs() > suppressClickUntil) return;
293
+ suppressClickUntil = 0;
294
+ ev.stopPropagation();
295
+ ev.preventDefault();
296
+ }
297
+
298
+ function onPointerDown(ev) {
299
+ if (g) return; // a second finger never corrupts a gesture in flight
300
+ if (ev.pointerType === 'mouse' && ev.button !== 0) return;
301
+ // Best-effort, for the same reason aimeat-viewport does it: a synthetic PointerEvent has no
302
+ // active pointer and setPointerCapture throws NotFoundError, swallowing the whole pointerdown.
303
+ try { el.setPointerCapture && el.setPointerCapture(ev.pointerId); } catch (e) { /* noop */ }
304
+
305
+ g = {
306
+ id: ev.pointerId, sx: ev.clientX, sy: ev.clientY, x: ev.clientX, y: ev.clientY,
307
+ px: ev.clientX, py: ev.clientY, t0: nowMs(), moved: false, dragging: false,
308
+ longFired: false, pointerType: ev.pointerType || 'mouse',
309
+ };
310
+
311
+ if (h.longPress) {
312
+ longTimer = setTimeout(function () {
313
+ if (!g || g.moved) return;
314
+ g.longFired = true;
315
+ if (opts.haptic !== false) haptic(opts.haptic || 'light');
316
+ consumeClick();
317
+ h.longPress(ev, info());
318
+ }, longMs);
319
+ }
320
+ }
321
+
322
+ function onPointerMove(ev) {
323
+ if (!g || ev.pointerId !== g.id) return;
324
+ g.px = g.x; g.py = g.y;
325
+ g.x = ev.clientX; g.y = ev.clientY;
326
+
327
+ if (!g.moved && Math.abs(g.x - g.sx) + Math.abs(g.y - g.sy) > slop) {
328
+ g.moved = true;
329
+ clearLongTimer();
330
+ if (h.dragStart || h.dragMove || h.dragEnd) {
331
+ g.dragging = true;
332
+ if (h.dragStart) h.dragStart(ev, info());
333
+ }
334
+ }
335
+ if (g.dragging && h.dragMove) h.dragMove(ev, info({ ddx: g.x - g.px, ddy: g.y - g.py }));
336
+ }
337
+
338
+ function onPointerUp(ev) {
339
+ if (!g || ev.pointerId !== g.id) return;
340
+ clearLongTimer();
341
+ var cur = g;
342
+ var dx = cur.x - cur.sx, dy = cur.y - cur.sy;
343
+ var dt = nowMs() - cur.t0;
344
+ var dist = Math.hypot(dx, dy);
345
+
346
+ if (cur.dragging && h.dragEnd) {
347
+ h.dragEnd(ev, info({ velocity: dt > 0 ? dist / dt : 0 }));
348
+ consumeClick();
349
+ } else if (h.swipe && cur.moved && dist >= swipeMin && dt <= swipeMax) {
350
+ var dir = directionOf(dx, dy, axis);
351
+ // An 'x' surface must not report a vertical flick as a horizontal swipe.
352
+ var wrongWay = (axis === 'x' && Math.abs(dy) > Math.abs(dx)) || (axis === 'y' && Math.abs(dx) > Math.abs(dy));
353
+ if (!wrongWay) {
354
+ consumeClick();
355
+ h.swipe(dir, info({ velocity: dt > 0 ? dist / dt : 0 }));
356
+ }
357
+ } else if (!cur.moved && !cur.longFired) {
358
+ var at = nowMs();
359
+ if (h.doubleTap && at - lastTapAt < dblMs) {
360
+ lastTapAt = 0;
361
+ consumeClick();
362
+ h.doubleTap(ev, info());
363
+ } else {
364
+ lastTapAt = at;
365
+ if (h.tap) { consumeClick(); h.tap(ev, info()); }
366
+ }
367
+ }
368
+ g = null;
369
+ }
370
+
371
+ function onPointerCancel(ev) {
372
+ if (!g || ev.pointerId !== g.id) return;
373
+ clearLongTimer();
374
+ if (g.dragging && h.dragEnd) h.dragEnd(ev, info({ cancelled: true }));
375
+ g = null;
376
+ }
377
+
378
+ // Desktop equivalents. A right click is the mouse's long-press, and it is what a user with no
379
+ // touchscreen will reach for; without this the context menu appears instead.
380
+ function onContextMenu(ev) {
381
+ if (!h.longPress || !keyboard) return;
382
+ ev.preventDefault();
383
+ h.longPress(ev, { dx: 0, dy: 0, duration: 0, pointerType: 'mouse', source: 'contextmenu' });
384
+ }
385
+ function onKeyDown(ev) {
386
+ if (!keyboard) return;
387
+ if (h.swipe && DIR_KEYS[ev.key]) {
388
+ var dir = DIR_KEYS[ev.key];
389
+ if (axis === 'x' && (dir === 'up' || dir === 'down')) return;
390
+ if (axis === 'y' && (dir === 'left' || dir === 'right')) return;
391
+ ev.preventDefault();
392
+ h.swipe(dir, { dx: 0, dy: 0, duration: 0, velocity: 0, pointerType: 'keyboard', source: 'keyboard' });
393
+ return;
394
+ }
395
+ if (h.tap && (ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar')) {
396
+ ev.preventDefault();
397
+ h.tap(ev, { dx: 0, dy: 0, duration: 0, pointerType: 'keyboard', source: 'keyboard' });
398
+ }
399
+ }
400
+
401
+ el.addEventListener('pointerdown', onPointerDown);
402
+ el.addEventListener('pointermove', onPointerMove);
403
+ el.addEventListener('pointerup', onPointerUp);
404
+ el.addEventListener('pointercancel', onPointerCancel);
405
+ el.addEventListener('click', onClickCapture, true);
406
+ el.addEventListener('contextmenu', onContextMenu);
407
+ el.addEventListener('keydown', onKeyDown);
408
+
409
+ return {
410
+ el: el,
411
+ /** Swap the handlers without rebinding listeners or losing a gesture in flight. */
412
+ update: function (next) { h = next || {}; },
413
+ destroy: function () {
414
+ clearLongTimer();
415
+ el.removeEventListener('pointerdown', onPointerDown);
416
+ el.removeEventListener('pointermove', onPointerMove);
417
+ el.removeEventListener('pointerup', onPointerUp);
418
+ el.removeEventListener('pointercancel', onPointerCancel);
419
+ el.removeEventListener('click', onClickCapture, true);
420
+ el.removeEventListener('contextmenu', onContextMenu);
421
+ el.removeEventListener('keydown', onKeyDown);
422
+ el.classList.remove('ai-grab');
423
+ if (opts.touchAction !== false) el.style.touchAction = '';
424
+ },
425
+ };
426
+ }
427
+
428
+ /**
429
+ * Bridge to aimeat-viewport: turn a drag handler into the drag handle its `onClaimPointer`
430
+ * delegate expects, so an app can use one vocabulary for both packs. The viewport owns pan, zoom
431
+ * and pinch on the surface; this pack owns what a gesture on a CHILD means. Neither reimplements
432
+ * the other, and an app that loads both never has two recognizers fighting for one pointer.
433
+ *
434
+ * const vp = AIMEAT.viewport.create('#board', {
435
+ * onClaimPointer: (ev) => {
436
+ * const card = ev.target.closest('[data-card]');
437
+ * return card ? AIMEAT.input.claimFor({ onMove: (dx, dy) => nudge(card, dx, dy) }) : null;
438
+ * },
439
+ * });
440
+ *
441
+ * @param {Object} spec { onMove(worldDx, worldDy, ev, cam), onEnd(moved, ev) }
442
+ * @returns {Object} the same shape, safe to hand straight to the viewport
443
+ */
444
+ function claimFor(spec) {
445
+ spec = spec || {};
446
+ return {
447
+ onMove: function (dx, dy, ev, cam) { if (spec.onMove) spec.onMove(dx, dy, ev, cam); },
448
+ onEnd: function (moved, ev) { if (spec.onEnd) spec.onEnd(moved, ev); },
449
+ };
450
+ }
451
+
452
+ /**
453
+ * A virtual thumbstick for a game, and the keyboard that stands in for it on a desktop. Reports a
454
+ * normalized vector on every change; the game reads it in its own loop or on the callback, and
455
+ * nothing about how it LOOKS is decided here (the two elements start with no paint at all, so the
456
+ * app skins them the way it skins aimeat-game, through its own CSS).
457
+ *
458
+ * @param {Element|string} hostOrSelector the area a thumb may land in (give it a size)
459
+ * @param {Object} [opts]
460
+ * @param {Function} [opts.onChange] ({x, y, angle, distance, active, source}) on every change
461
+ * @param {'stick'|'dpad'} [opts.mode='stick'] dpad snaps the vector to 8 directions
462
+ * @param {boolean} [opts.keyboard=true] WASD and the arrow keys drive the same vector
463
+ * @param {boolean} [opts.floating=true] the base appears where the thumb lands, rather than fixed
464
+ * @param {number} [opts.deadZone=0.12] fraction of the radius that still reads as centre
465
+ * @returns {{value: Function, destroy: Function, host: Element}}
466
+ */
467
+ function pad(hostOrSelector, opts) {
468
+ opts = opts || {};
469
+ var host = resolveEl(hostOrSelector);
470
+ if (!host) throw new Error('aimeat-input: pad host not found');
471
+ injectCss();
472
+
473
+ var mode = opts.mode || 'stick';
474
+ var floating = opts.floating !== false;
475
+ var deadZone = opts.deadZone != null ? opts.deadZone : 0.12;
476
+ var useKeys = opts.keyboard !== false;
477
+
478
+ host.classList.add('ai-pad', 'ai-pad-idle');
479
+ var base = document.createElement('div');
480
+ base.className = 'ai-pad-base';
481
+ var stick = document.createElement('div');
482
+ stick.className = 'ai-pad-stick';
483
+ host.appendChild(base);
484
+ host.appendChild(stick);
485
+
486
+ var radius = 0, originX = 0, originY = 0, pointerId = null;
487
+ var vec = { x: 0, y: 0, angle: 0, distance: 0, active: false, source: 'none' };
488
+ var keys = {};
489
+
490
+ function place(cx, cy) {
491
+ var r = host.getBoundingClientRect();
492
+ originX = cx - r.left; originY = cy - r.top;
493
+ base.style.left = originX + 'px'; base.style.top = originY + 'px';
494
+ stick.style.left = originX + 'px'; stick.style.top = originY + 'px';
495
+ radius = (base.offsetWidth || 128) / 2;
496
+ }
497
+
498
+ function emit(x, y, source) {
499
+ var d = Math.min(1, Math.hypot(x, y));
500
+ if (d < deadZone) { x = 0; y = 0; d = 0; }
501
+ if (mode === 'dpad' && d > 0) {
502
+ var step = Math.PI / 4;
503
+ var a = Math.round(Math.atan2(y, x) / step) * step;
504
+ x = Math.cos(a); y = Math.sin(a); d = 1;
505
+ }
506
+ vec = { x: x, y: y, angle: d ? Math.atan2(y, x) : 0, distance: d, active: d > 0, source: source };
507
+ stick.style.transform = 'translate(' + (x * radius) + 'px,' + (y * radius) + 'px)';
508
+ host.classList.toggle('ai-pad-idle', !vec.active);
509
+ if (opts.onChange) opts.onChange(vec);
510
+ }
511
+
512
+ // Geometry, kept correct without touching the vector. A placement measured at one viewport is
513
+ // wrong after a rotation or a resize, and a pad that has never been touched has no placement at
514
+ // all — both leave the parts sitting outside the host.
515
+ function recentre() {
516
+ if (pointerId !== null) return; // never move the base out from under a live thumb
517
+ var r = host.getBoundingClientRect();
518
+ if (!r.width) return;
519
+ place(r.left + r.width / 2, r.top + r.height / 2);
520
+ stick.style.transform = 'translate(' + (vec.x * radius) + 'px,' + (vec.y * radius) + 'px)';
521
+ }
522
+
523
+ function onDown(ev) {
524
+ if (pointerId !== null) return;
525
+ pointerId = ev.pointerId;
526
+ try { host.setPointerCapture && host.setPointerCapture(ev.pointerId); } catch (e) { /* noop */ }
527
+ if (floating) place(ev.clientX, ev.clientY);
528
+ else if (!radius) { var r = host.getBoundingClientRect(); place(r.left + r.width / 2, r.top + r.height / 2); }
529
+ emit(0, 0, 'pointer');
530
+ }
531
+ function onMove(ev) {
532
+ if (ev.pointerId !== pointerId) return;
533
+ var r = host.getBoundingClientRect();
534
+ emit((ev.clientX - r.left - originX) / radius, (ev.clientY - r.top - originY) / radius, 'pointer');
535
+ }
536
+ function onUp(ev) {
537
+ if (ev.pointerId !== pointerId) return;
538
+ pointerId = null;
539
+ emit(0, 0, 'pointer');
540
+ }
541
+
542
+ var KEY_VEC = {
543
+ ArrowLeft: [-1, 0], a: [-1, 0], A: [-1, 0],
544
+ ArrowRight: [1, 0], d: [1, 0], D: [1, 0],
545
+ ArrowUp: [0, -1], w: [0, -1], W: [0, -1],
546
+ ArrowDown: [0, 1], s: [0, 1], S: [0, 1],
547
+ };
548
+ function fromKeys() {
549
+ var x = 0, y = 0;
550
+ for (var k in keys) {
551
+ if (!keys[k] || !KEY_VEC[k]) continue;
552
+ x += KEY_VEC[k][0]; y += KEY_VEC[k][1];
553
+ }
554
+ var len = Math.hypot(x, y);
555
+ if (len > 1) { x /= len; y /= len; }
556
+ if (!radius) { var r = host.getBoundingClientRect(); place(r.left + r.width / 2, r.top + r.height / 2); }
557
+ emit(x, y, 'keyboard');
558
+ }
559
+ function onKeyDown(ev) { if (KEY_VEC[ev.key]) { keys[ev.key] = true; ev.preventDefault(); fromKeys(); } }
560
+ function onKeyUp(ev) { if (KEY_VEC[ev.key]) { keys[ev.key] = false; fromKeys(); } }
561
+ // A game runs full-screen and the pad is rarely the focused element, so the keyboard listens on
562
+ // the window. It is removed again by destroy().
563
+ function onBlur() { keys = {}; fromKeys(); }
564
+
565
+ host.addEventListener('pointerdown', onDown);
566
+ host.addEventListener('pointermove', onMove);
567
+ host.addEventListener('pointerup', onUp);
568
+ host.addEventListener('pointercancel', onUp);
569
+ if (useKeys) {
570
+ global.addEventListener('keydown', onKeyDown);
571
+ global.addEventListener('keyup', onKeyUp);
572
+ global.addEventListener('blur', onBlur);
573
+ }
574
+ var ro = null;
575
+ if (global.ResizeObserver) { ro = new global.ResizeObserver(recentre); ro.observe(host); }
576
+ recentre();
577
+
578
+ return {
579
+ host: host,
580
+ /** The current vector. Read it from a game loop rather than accumulating onChange calls. */
581
+ value: function () { return { x: vec.x, y: vec.y, angle: vec.angle, distance: vec.distance, active: vec.active, source: vec.source }; },
582
+ destroy: function () {
583
+ if (ro) ro.disconnect();
584
+ host.removeEventListener('pointerdown', onDown);
585
+ host.removeEventListener('pointermove', onMove);
586
+ host.removeEventListener('pointerup', onUp);
587
+ host.removeEventListener('pointercancel', onUp);
588
+ if (useKeys) {
589
+ global.removeEventListener('keydown', onKeyDown);
590
+ global.removeEventListener('keyup', onKeyUp);
591
+ global.removeEventListener('blur', onBlur);
592
+ }
593
+ if (base.parentNode) base.parentNode.removeChild(base);
594
+ if (stick.parentNode) stick.parentNode.removeChild(stick);
595
+ host.classList.remove('ai-pad', 'ai-pad-idle');
596
+ },
597
+ };
598
+ }
599
+
600
+ AIMEAT.input = {
601
+ VERSION: VERSION,
602
+ on: on,
603
+ tappable: tappable,
604
+ haptic: haptic,
605
+ pad: pad,
606
+ claimFor: claimFor,
607
+ isCoarse: isCoarse,
608
+ hasHover: hasHover,
609
+ injectCss: injectCss,
610
+ };
611
+ })(typeof window !== 'undefined' ? window : this);