UNPKG

17.9 kBHTMLView Raw
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width,initial-scale=1">
6 <title>packages/@interactjs/auto-start/base.ts - Documentation</title>
7
8 <script src="scripts/prettify/prettify.js"></script>
9 <script src="scripts/prettify/lang-css.js"></script>
10 <!--[if lt IE 9]>
11 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
12 <![endif]-->
13 <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
14 <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15 <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
16</head>
17<body>
18
19<input type="checkbox" id="nav-trigger" class="nav-trigger" />
20<label for="nav-trigger" class="navicon-button x">
21 <div class="navicon"></div>
22</label>
23
24<label for="nav-trigger" class="overlay"></label>
25
26<nav>
27 <li class="nav-link nav-home-link"><a href="index.html">Home</a></li><li class="nav-heading">Classes</li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="DropEvent_DropEvent.html">DropEvent</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Interactable.html">Interactable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#context">context</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#deltaSource">deltaSource</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#draggable">draggable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#dropCheck">dropCheck</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#dropzone">dropzone</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#fire">fire</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#gesturable">gesturable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#getRect">getRect</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#off">off</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#on">on</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#origin">origin</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#rectChecker">rectChecker</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#reflow">reflow</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#resizable">resizable</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#set">set</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="Interactable.html#unset">unset</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="InteractEvent_InteractEvent.html">InteractEvent</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="Interaction_Interaction.html">Interaction</a></span></li><li class="nav-heading"><span class="nav-item-type type-class">C</span><span class="nav-item-name"><a href="module.exports_module.exports.html">exports</a></span></li><li class="nav-heading">Modules</li><li class="nav-heading"><span class="nav-item-type type-module">M</span><span class="nav-item-name"><a href="module-interact.html">interact</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.isSet">isSet</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.maxInteractions">maxInteractions</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.pointerMoveTolerance">pointerMoveTolerance</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.stop">stop</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.supportsPointerEvent">supportsPointerEvent</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.supportsTouch">supportsTouch</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="module-interact.html#.use">use</a></span></li><li class="nav-heading"><span class="nav-item-type type-module">M</span><span class="nav-item-name"><a href="module-modifiers_aspectRatio.html">modifiers/aspectRatio</a></span></li><li class="nav-heading"><span class="nav-item-type type-module">M</span><span class="nav-item-name"><a href="module-modifiers_snapEdges.html">modifiers/snapEdges</a></span></li><li class="nav-heading"><a href="global.html">Globals</a></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#interact">interact</a></span></li><li class="nav-item"><span class="nav-item-type type-function">F</span><span class="nav-item-name"><a href="global.html#preventDefault">preventDefault</a></span></li>
28</nav>
29
30<div class="code-col-bg"></div>
31
32<div id="main">
33
34 <h1 class="page-title">packages/@interactjs/auto-start/base.ts</h1>
35
36
37
38
39
40
41
42 <section>
43 <article>
44 <pre class="prettyprint source linenums"><code>import type { Interactable } from '@interactjs/core/Interactable'
45import type { Interaction, ActionProps } from '@interactjs/core/Interaction'
46import type { Scope, SignalArgs, ActionName, Plugin } from '@interactjs/core/scope'
47import type { CursorChecker, PointerType, PointerEventType, Element } from '@interactjs/types/index'
48import * as domUtils from '@interactjs/utils/domUtils'
49import extend from '@interactjs/utils/extend'
50import is from '@interactjs/utils/is'
51import { copyAction } from '@interactjs/utils/misc'
52
53import InteractableMethods from './InteractableMethods'
54
55declare module '@interactjs/core/InteractStatic' {
56 export interface InteractStatic {
57 maxInteractions: (newValue: any) => any
58 }
59}
60
61declare module '@interactjs/core/scope' {
62 interface Scope {
63 autoStart: AutoStart
64 }
65
66 interface SignalArgs {
67 'autoStart:before-start': Omit&lt;SignalArgs['interactions:move'], 'interaction'> &amp; {
68 interaction: Interaction&lt;ActionName>
69 }
70 'autoStart:prepared': { interaction: Interaction }
71 'auto-start:check': CheckSignalArg
72 }
73}
74
75declare module '@interactjs/core/options' {
76 interface BaseDefaults {
77 actionChecker?: any
78 cursorChecker?: any
79 styleCursor?: any
80 }
81
82 interface PerActionDefaults {
83 manualStart?: boolean
84 max?: number
85 maxPerElement?: number
86 allowFrom?: string | Element
87 ignoreFrom?: string | Element
88 cursorChecker?: CursorChecker
89
90 // only allow left button by default
91 // see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons#Return_value
92 // TODO: docst
93 mouseButtons?: 0 | 1 | 2 | 4 | 8 | 16
94 }
95}
96
97interface CheckSignalArg {
98 interactable: Interactable
99 interaction: Interaction
100 element: Element
101 action: ActionProps&lt;ActionName>
102 buttons: number
103}
104
105export interface AutoStart {
106 // Allow this many interactions to happen simultaneously
107 maxInteractions: number
108 withinInteractionLimit: typeof withinInteractionLimit
109 cursorElement: Element
110}
111
112function install (scope: Scope) {
113 const { interactStatic: interact, defaults } = scope
114
115 scope.usePlugin(InteractableMethods)
116
117 defaults.base.actionChecker = null
118 defaults.base.styleCursor = true
119
120 extend(defaults.perAction, {
121 manualStart: false,
122 max: Infinity,
123 maxPerElement: 1,
124 allowFrom: null,
125 ignoreFrom: null,
126
127 // only allow left button by default
128 // see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons#Return_value
129 mouseButtons: 1,
130 })
131
132 /**
133 * Returns or sets the maximum number of concurrent interactions allowed. By
134 * default only 1 interaction is allowed at a time (for backwards
135 * compatibility). To allow multiple interactions on the same Interactables and
136 * elements, you need to enable it in the draggable, resizable and gesturable
137 * `'max'` and `'maxPerElement'` options.
138 *
139 * @alias module:interact.maxInteractions
140 *
141 * @param {number} [newValue] Any number. newValue &lt;= 0 means no interactions.
142 */
143 interact.maxInteractions = (newValue: number) => maxInteractions(newValue, scope)
144
145 scope.autoStart = {
146 // Allow this many interactions to happen simultaneously
147 maxInteractions: Infinity,
148 withinInteractionLimit,
149 cursorElement: null,
150 }
151}
152
153function prepareOnDown (
154 { interaction, pointer, event, eventTarget }: SignalArgs['interactions:down'],
155 scope: Scope,
156) {
157 if (interaction.interacting()) return
158
159 const actionInfo = getActionInfo(interaction, pointer, event, eventTarget, scope)
160 prepare(interaction, actionInfo, scope)
161}
162
163function prepareOnMove (
164 { interaction, pointer, event, eventTarget }: SignalArgs['interactions:move'],
165 scope: Scope,
166) {
167 if (interaction.pointerType !== 'mouse' || interaction.pointerIsDown || interaction.interacting()) return
168
169 const actionInfo = getActionInfo(interaction, pointer, event, eventTarget as Element, scope)
170 prepare(interaction, actionInfo, scope)
171}
172
173function startOnMove (arg: SignalArgs['interactions:move'], scope: Scope) {
174 const { interaction } = arg
175
176 if (
177 !interaction.pointerIsDown ||
178 interaction.interacting() ||
179 !interaction.pointerWasMoved ||
180 !interaction.prepared.name
181 ) {
182 return
183 }
184
185 scope.fire('autoStart:before-start', arg)
186
187 const { interactable } = interaction
188 const actionName = (interaction as Interaction&lt;ActionName>).prepared.name
189
190 if (actionName &amp;&amp; interactable) {
191 // check manualStart and interaction limit
192 if (
193 interactable.options[actionName].manualStart ||
194 !withinInteractionLimit(interactable, interaction.element, interaction.prepared, scope)
195 ) {
196 interaction.stop()
197 } else {
198 interaction.start(interaction.prepared, interactable, interaction.element)
199 setInteractionCursor(interaction, scope)
200 }
201 }
202}
203
204function clearCursorOnStop ({ interaction }: { interaction: Interaction }, scope: Scope) {
205 const { interactable } = interaction
206
207 if (interactable &amp;&amp; interactable.options.styleCursor) {
208 setCursor(interaction.element, '', scope)
209 }
210}
211
212// Check if the current interactable supports the action.
213// If so, return the validated action. Otherwise, return null
214function validateAction&lt;T extends ActionName> (
215 action: ActionProps&lt;T>,
216 interactable: Interactable,
217 element: Element,
218 eventTarget: Node,
219 scope: Scope,
220) {
221 if (
222 interactable.testIgnoreAllow(interactable.options[action.name], element, eventTarget) &amp;&amp;
223 interactable.options[action.name].enabled &amp;&amp;
224 withinInteractionLimit(interactable, element, action, scope)
225 ) {
226 return action
227 }
228
229 return null
230}
231
232function validateMatches (
233 interaction: Interaction,
234 pointer: PointerType,
235 event: PointerEventType,
236 matches: Interactable[],
237 matchElements: Element[],
238 eventTarget: Node,
239 scope: Scope,
240) {
241 for (let i = 0, len = matches.length; i &lt; len; i++) {
242 const match = matches[i]
243 const matchElement = matchElements[i]
244 const matchAction = match.getAction(pointer, event, interaction, matchElement)
245
246 if (!matchAction) {
247 continue
248 }
249
250 const action = validateAction&lt;ActionName>(matchAction, match, matchElement, eventTarget, scope)
251
252 if (action) {
253 return {
254 action,
255 interactable: match,
256 element: matchElement,
257 }
258 }
259 }
260
261 return { action: null, interactable: null, element: null }
262}
263
264function getActionInfo (
265 interaction: Interaction,
266 pointer: PointerType,
267 event: PointerEventType,
268 eventTarget: Node,
269 scope: Scope,
270) {
271 let matches: Interactable[] = []
272 let matchElements: Element[] = []
273
274 let element = eventTarget as Element
275
276 function pushMatches (interactable: Interactable) {
277 matches.push(interactable)
278 matchElements.push(element)
279 }
280
281 while (is.element(element)) {
282 matches = []
283 matchElements = []
284
285 scope.interactables.forEachMatch(element, pushMatches)
286
287 const actionInfo = validateMatches(
288 interaction,
289 pointer,
290 event,
291 matches,
292 matchElements,
293 eventTarget,
294 scope,
295 )
296
297 if (actionInfo.action &amp;&amp; !actionInfo.interactable.options[actionInfo.action.name].manualStart) {
298 return actionInfo
299 }
300
301 element = domUtils.parentNode(element) as Element
302 }
303
304 return { action: null, interactable: null, element: null }
305}
306
307function prepare (
308 interaction: Interaction,
309 {
310 action,
311 interactable,
312 element,
313 }: {
314 action: ActionProps&lt;any>
315 interactable: Interactable
316 element: Element
317 },
318 scope: Scope,
319) {
320 action = action || { name: null }
321
322 interaction.interactable = interactable
323 interaction.element = element
324 copyAction(interaction.prepared, action)
325
326 interaction.rect = interactable &amp;&amp; action.name ? interactable.getRect(element) : null
327
328 setInteractionCursor(interaction, scope)
329
330 scope.fire('autoStart:prepared', { interaction })
331}
332
333function withinInteractionLimit&lt;T extends ActionName> (
334 interactable: Interactable,
335 element: Element,
336 action: ActionProps&lt;T>,
337 scope: Scope,
338) {
339 const options = interactable.options
340 const maxActions = options[action.name].max
341 const maxPerElement = options[action.name].maxPerElement
342 const autoStartMax = scope.autoStart.maxInteractions
343 let activeInteractions = 0
344 let interactableCount = 0
345 let elementCount = 0
346
347 // no actions if any of these values == 0
348 if (!(maxActions &amp;&amp; maxPerElement &amp;&amp; autoStartMax)) {
349 return false
350 }
351
352 for (const interaction of scope.interactions.list) {
353 const otherAction = interaction.prepared.name
354
355 if (!interaction.interacting()) {
356 continue
357 }
358
359 activeInteractions++
360
361 if (activeInteractions >= autoStartMax) {
362 return false
363 }
364
365 if (interaction.interactable !== interactable) {
366 continue
367 }
368
369 interactableCount += otherAction === action.name ? 1 : 0
370
371 if (interactableCount >= maxActions) {
372 return false
373 }
374
375 if (interaction.element === element) {
376 elementCount++
377
378 if (otherAction === action.name &amp;&amp; elementCount >= maxPerElement) {
379 return false
380 }
381 }
382 }
383
384 return autoStartMax > 0
385}
386
387function maxInteractions (newValue: any, scope: Scope) {
388 if (is.number(newValue)) {
389 scope.autoStart.maxInteractions = newValue
390
391 return this
392 }
393
394 return scope.autoStart.maxInteractions
395}
396
397function setCursor (element: Element, cursor: string, scope: Scope) {
398 const { cursorElement: prevCursorElement } = scope.autoStart
399
400 if (prevCursorElement &amp;&amp; prevCursorElement !== element) {
401 prevCursorElement.style.cursor = ''
402 }
403
404 element.ownerDocument.documentElement.style.cursor = cursor
405 element.style.cursor = cursor
406 scope.autoStart.cursorElement = cursor ? element : null
407}
408
409function setInteractionCursor&lt;T extends ActionName> (interaction: Interaction&lt;T>, scope: Scope) {
410 const { interactable, element, prepared } = interaction
411
412 if (!(interaction.pointerType === 'mouse' &amp;&amp; interactable &amp;&amp; interactable.options.styleCursor)) {
413 // clear previous target element cursor
414 if (scope.autoStart.cursorElement) {
415 setCursor(scope.autoStart.cursorElement, '', scope)
416 }
417
418 return
419 }
420
421 let cursor = ''
422
423 if (prepared.name) {
424 const cursorChecker = interactable.options[prepared.name].cursorChecker
425
426 if (is.func(cursorChecker)) {
427 cursor = cursorChecker(prepared, interactable, element, interaction._interacting)
428 } else {
429 cursor = scope.actions.map[prepared.name].getCursor(prepared)
430 }
431 }
432
433 setCursor(interaction.element, cursor || '', scope)
434}
435
436const autoStart: Plugin = {
437 id: 'auto-start/base',
438 before: ['actions'],
439 install,
440 listeners: {
441 'interactions:down': prepareOnDown,
442 'interactions:move': (arg, scope) => {
443 prepareOnMove(arg, scope)
444 startOnMove(arg, scope)
445 },
446 'interactions:stop': clearCursorOnStop,
447 },
448 maxInteractions,
449 withinInteractionLimit,
450 validateAction,
451}
452
453export default autoStart
454</code></pre>
455 </article>
456 </section>
457
458
459
460
461</div>
462
463<script>prettyPrint();</script>
464<script src="scripts/linenumber.js"></script>
465</body>
466</html>