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 | |
11 |
|
12 |
|
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'
|
45 | import type { Interaction, ActionProps } from '@interactjs/core/Interaction'
|
46 | import type { Scope, SignalArgs, ActionName, Plugin } from '@interactjs/core/scope'
|
47 | import type { CursorChecker, PointerType, PointerEventType, Element } from '@interactjs/types/index'
|
48 | import * as domUtils from '@interactjs/utils/domUtils'
|
49 | import extend from '@interactjs/utils/extend'
|
50 | import is from '@interactjs/utils/is'
|
51 | import { copyAction } from '@interactjs/utils/misc'
|
52 |
|
53 | import InteractableMethods from './InteractableMethods'
|
54 |
|
55 | declare module '@interactjs/core/InteractStatic' {
|
56 | export interface InteractStatic {
|
57 | maxInteractions: (newValue: any) => any
|
58 | }
|
59 | }
|
60 |
|
61 | declare module '@interactjs/core/scope' {
|
62 | interface Scope {
|
63 | autoStart: AutoStart
|
64 | }
|
65 |
|
66 | interface SignalArgs {
|
67 | 'autoStart:before-start': Omit<SignalArgs['interactions:move'], 'interaction'> & {
|
68 | interaction: Interaction<ActionName>
|
69 | }
|
70 | 'autoStart:prepared': { interaction: Interaction }
|
71 | 'auto-start:check': CheckSignalArg
|
72 | }
|
73 | }
|
74 |
|
75 | declare 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 |
|
97 | interface CheckSignalArg {
|
98 | interactable: Interactable
|
99 | interaction: Interaction
|
100 | element: Element
|
101 | action: ActionProps<ActionName>
|
102 | buttons: number
|
103 | }
|
104 |
|
105 | export interface AutoStart {
|
106 | // Allow this many interactions to happen simultaneously
|
107 | maxInteractions: number
|
108 | withinInteractionLimit: typeof withinInteractionLimit
|
109 | cursorElement: Element
|
110 | }
|
111 |
|
112 | function 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 <= 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 |
|
153 | function 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 |
|
163 | function 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 |
|
173 | function 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<ActionName>).prepared.name
|
189 |
|
190 | if (actionName && 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 |
|
204 | function clearCursorOnStop ({ interaction }: { interaction: Interaction }, scope: Scope) {
|
205 | const { interactable } = interaction
|
206 |
|
207 | if (interactable && 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
|
214 | function validateAction<T extends ActionName> (
|
215 | action: ActionProps<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) &&
|
223 | interactable.options[action.name].enabled &&
|
224 | withinInteractionLimit(interactable, element, action, scope)
|
225 | ) {
|
226 | return action
|
227 | }
|
228 |
|
229 | return null
|
230 | }
|
231 |
|
232 | function 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 < 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<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 |
|
264 | function 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 && !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 |
|
307 | function prepare (
|
308 | interaction: Interaction,
|
309 | {
|
310 | action,
|
311 | interactable,
|
312 | element,
|
313 | }: {
|
314 | action: ActionProps<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 && action.name ? interactable.getRect(element) : null
|
327 |
|
328 | setInteractionCursor(interaction, scope)
|
329 |
|
330 | scope.fire('autoStart:prepared', { interaction })
|
331 | }
|
332 |
|
333 | function withinInteractionLimit<T extends ActionName> (
|
334 | interactable: Interactable,
|
335 | element: Element,
|
336 | action: ActionProps<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 && maxPerElement && 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 && elementCount >= maxPerElement) {
|
379 | return false
|
380 | }
|
381 | }
|
382 | }
|
383 |
|
384 | return autoStartMax > 0
|
385 | }
|
386 |
|
387 | function 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 |
|
397 | function setCursor (element: Element, cursor: string, scope: Scope) {
|
398 | const { cursorElement: prevCursorElement } = scope.autoStart
|
399 |
|
400 | if (prevCursorElement && 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 |
|
409 | function setInteractionCursor<T extends ActionName> (interaction: Interaction<T>, scope: Scope) {
|
410 | const { interactable, element, prepared } = interaction
|
411 |
|
412 | if (!(interaction.pointerType === 'mouse' && interactable && 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 |
|
436 | const 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 |
|
453 | export 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>
|