purgetss 7.4.0 → 7.5.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.
@@ -3,14 +3,26 @@
3
3
 
4
4
  function Animation(args = {}) {
5
5
  const params = {
6
+ // Global
6
7
  id: args.id,
8
+ debug: args.debug ?? false,
9
+ isIOS: Ti.Platform.osname === 'iphone' || Ti.Platform.osname === 'ipad',
10
+
11
+ // Play / Toggle / Sequence
7
12
  open: false,
8
- draggables: [],
9
13
  playing: false,
10
14
  delay: args.delay ?? 0,
11
- debug: args.debug ?? false,
12
- isIOS: Ti.Platform.osname === 'iphone' || Ti.Platform.osname === 'ipad',
13
- hasTransformation: (args.scale !== undefined || args.rotate !== undefined)
15
+ hasTransformation: (args.scale !== undefined || args.rotate !== undefined),
16
+
17
+ // Draggable
18
+ draggables: [],
19
+
20
+ // Collision Detection
21
+ collisionViews: [],
22
+ dragCB: null,
23
+ dropCB: null,
24
+ lastHoverTarget: null,
25
+ lastKnownTarget: null
14
26
  }
15
27
 
16
28
  logger('Create Animation View: ' + params.id)
@@ -41,6 +53,242 @@ function Animation(args = {}) {
41
53
  makeViewsDraggable(_views)
42
54
  }
43
55
 
56
+ animationView.detectCollisions = (views, _dragCB, _dropCB) => {
57
+ if (params.debug) { console.log('') }
58
+ logger('`detectCollisions` method called on: ' + params.id)
59
+ if (_dragCB) params.dragCB = _dragCB
60
+ if (_dropCB) params.dropCB = _dropCB
61
+ const arr = Array.isArray(views) ? views : [views]
62
+ arr.forEach(view => {
63
+ view._collisionEnabled = true
64
+ if (!params.collisionViews.includes(view)) {
65
+ params.collisionViews.push(view)
66
+ }
67
+ })
68
+ }
69
+
70
+ function resolvePosition(view) {
71
+ if (view._originTop !== undefined) return { top: view._originTop, left: view._originLeft }
72
+ if (view.top !== undefined && view.top !== null && view.left !== undefined && view.left !== null) return { top: view.top, left: view.left }
73
+ const r = view.rect
74
+ return { top: r.y, left: r.x }
75
+ }
76
+
77
+ function normalizePosition(view) {
78
+ const pos = resolvePosition(view)
79
+ view.applyProperties({ top: pos.top, left: pos.left, right: null, bottom: null })
80
+ view._originTop = pos.top
81
+ view._originLeft = pos.left
82
+ }
83
+
84
+ animationView.swap = (view1, view2) => {
85
+ if (params.debug) { console.log('') }
86
+ logger('`swap` method called on: ' + params.id)
87
+
88
+ // Cancel any pending bounce-back animations
89
+ ;[view1, view2].forEach(v => {
90
+ if (v._bouncingBack) {
91
+ logger(` -> cancelling bounce-back for ${v.id} before swap`)
92
+ v.applyProperties({ top: v._originTop, left: v._originLeft, transform: Ti.UI.createMatrix2D() })
93
+ v._bouncingBack = false
94
+ }
95
+ })
96
+
97
+ // Read REAL positions — rect gives actual rendered position, no stale variables
98
+ const targetRect = view2.rect
99
+ const sourceHome = { top: view1._originTop ?? view1.top, left: view1._originLeft ?? view1.left }
100
+
101
+ // Source goes to target's real position, target goes to source's home
102
+ const dest1Top = targetRect.y
103
+ const dest1Left = targetRect.x
104
+ const dest2Top = sourceHome.top
105
+ const dest2Left = sourceHome.left
106
+
107
+ logger(` -> ${view1.id}: home(${sourceHome.top}, ${sourceHome.left}) → dest(${dest1Top}, ${dest1Left})`)
108
+ logger(` -> ${view2.id}: rect(${targetRect.y}, ${targetRect.x}) → dest(${dest2Top}, ${dest2Left})`)
109
+
110
+ const maxZ = params.draggables.length
111
+ view1.zIndex = maxZ + 1
112
+ view2.zIndex = maxZ
113
+
114
+ view1.animate({
115
+ ...args, left: dest1Left, top: dest1Top, transform: Ti.UI.createMatrix2D()
116
+ }, () => {
117
+ view1.applyProperties({ left: dest1Left, top: dest1Top, transform: Ti.UI.createMatrix2D() })
118
+ params.draggables.forEach((d, k) => { d.zIndex = k })
119
+ })
120
+
121
+ view2.animate({
122
+ ...args, left: dest2Left, top: dest2Top
123
+ }, () => {
124
+ view2.applyProperties({ left: dest2Left, top: dest2Top })
125
+ })
126
+
127
+ view1._originTop = dest1Top
128
+ view1._originLeft = dest1Left
129
+ view2._originTop = dest2Top
130
+ view2._originLeft = dest2Left
131
+ }
132
+
133
+ animationView.sequence = (_views, _cb) => {
134
+ if (params.debug) { console.log('') }
135
+ logger('`sequence` method called on: ' + params.id)
136
+ const views = Array.isArray(_views) ? _views : [_views]
137
+ const total = views.length
138
+
139
+ params.open = !params.open
140
+ chooseAnimationBasedOnState('play')
141
+
142
+ function animateNext(index) {
143
+ if (index >= total) { return }
144
+ const isLast = index === total - 1
145
+ playView(views[index], (e) => {
146
+ if (isLast && typeof _cb === 'function') {
147
+ _cb(e)
148
+ } else {
149
+ animateNext(index + 1)
150
+ }
151
+ }, 'play', index, total)
152
+ }
153
+
154
+ animateNext(0)
155
+ }
156
+
157
+ animationView.shake = (view, intensity = 10) => {
158
+ if (params.debug) { console.log('') }
159
+ logger('`shake` method called on: ' + params.id)
160
+ if (!view) { return notFound() }
161
+
162
+ view.applyProperties({ transform: Ti.UI.createMatrix2D().translate(-intensity, 0) })
163
+
164
+ view.animate({
165
+ ...args,
166
+ transform: Ti.UI.createMatrix2D().translate(intensity, 0),
167
+ duration: Math.round((args.duration ?? 400) / 6),
168
+ autoreverse: true,
169
+ repeat: 3,
170
+ curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
171
+ }, () => {
172
+ view.applyProperties({ transform: Ti.UI.createMatrix2D() })
173
+ })
174
+ }
175
+
176
+ animationView.pulse = (view, count = 1) => {
177
+ if (params.debug) { console.log('') }
178
+ logger('`pulse` method called on: ' + params.id)
179
+ if (!view) { return notFound() }
180
+
181
+ view.animate({
182
+ ...args,
183
+ transform: args.transform ?? Ti.UI.createMatrix2D().scale(1.2),
184
+ autoreverse: true,
185
+ repeat: count,
186
+ curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
187
+ }, () => {
188
+ view.applyProperties({ transform: Ti.UI.createMatrix2D() })
189
+ })
190
+ }
191
+
192
+ animationView.snapTo = (view, targets) => {
193
+ if (params.debug) { console.log('') }
194
+ logger('`snapTo` method called on: ' + params.id)
195
+ if (!view) { return notFound() }
196
+
197
+ const arr = Array.isArray(targets) ? targets : [targets]
198
+ const vr = view.rect
199
+ const vcx = (view._visualLeft ?? view.left) + vr.width / 2
200
+ const vcy = (view._visualTop ?? view.top) + vr.height / 2
201
+
202
+ let closest = null
203
+ let minDist = Infinity
204
+
205
+ arr.forEach(target => {
206
+ if (target === view) { return }
207
+ const tr = target.rect
208
+ const tcx = tr.x + tr.width / 2
209
+ const tcy = tr.y + tr.height / 2
210
+ const dist = Math.sqrt((vcx - tcx) ** 2 + (vcy - tcy) ** 2)
211
+ if (dist < minDist) {
212
+ minDist = dist
213
+ closest = target
214
+ }
215
+ })
216
+
217
+ if (!closest) { return null }
218
+
219
+ const cr = closest.rect
220
+ normalizePosition(closest)
221
+ const destLeft = closest._originLeft + (cr.width - vr.width) / 2
222
+ const destTop = closest._originTop + (cr.height - vr.height) / 2
223
+
224
+ view.animate({
225
+ ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
226
+ }, () => {
227
+ view.applyProperties({ left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
228
+ })
229
+
230
+ view._originTop = destTop
231
+ view._originLeft = destLeft
232
+
233
+ return closest
234
+ }
235
+
236
+ animationView.reorder = (views, newOrder) => {
237
+ if (params.debug) { console.log('') }
238
+ logger('`reorder` method called on: ' + params.id)
239
+ const arr = Array.isArray(views) ? views : [views]
240
+ if (newOrder.length !== arr.length) { return }
241
+
242
+ arr.forEach(v => normalizePosition(v))
243
+
244
+ const positions = arr.map(v => ({
245
+ left: v._originLeft,
246
+ top: v._originTop
247
+ }))
248
+
249
+ arr.forEach((view, i) => {
250
+ const dest = positions[newOrder[i]]
251
+
252
+ view.animate({
253
+ ...args, left: dest.left, top: dest.top, transform: Ti.UI.createMatrix2D()
254
+ }, () => {
255
+ view.applyProperties({ left: dest.left, top: dest.top, transform: Ti.UI.createMatrix2D() })
256
+ })
257
+
258
+ view._originTop = dest.top
259
+ view._originLeft = dest.left
260
+ })
261
+ }
262
+
263
+ animationView.undraggable = (_views) => {
264
+ if (params.debug) { console.log('') }
265
+ logger('`undraggable` method called on: ' + params.id)
266
+ const arr = Array.isArray(_views) ? _views : [_views]
267
+
268
+ arr.forEach(view => {
269
+ if (view._dragListeners) {
270
+ view.removeEventListener('touchstart', view._dragListeners.onTouchStart)
271
+ view.removeEventListener('touchend', view._dragListeners.onTouchEnd)
272
+ view.removeEventListener('touchmove', view._dragListeners.onTouchMove)
273
+ Ti.Gesture.removeEventListener('orientationchange', view._dragListeners.onOrientationChange)
274
+ delete view._dragListeners
275
+ }
276
+
277
+ const idx = params.draggables.indexOf(view)
278
+ if (idx !== -1) { params.draggables.splice(idx, 1) }
279
+
280
+ const cIdx = params.collisionViews.indexOf(view)
281
+ if (cIdx !== -1) { params.collisionViews.splice(cIdx, 1) }
282
+
283
+ delete view._originTop
284
+ delete view._originLeft
285
+ delete view._visualTop
286
+ delete view._visualLeft
287
+ delete view._collisionEnabled
288
+ delete view._bouncingBack
289
+ })
290
+ }
291
+
44
292
  animationView.open = (_views, _cb) => {
45
293
  if (params.debug) { console.log('') }
46
294
  logger('`open` method called on: ' + params.id)
@@ -53,6 +301,54 @@ function Animation(args = {}) {
53
301
  mainPlayApplyFn(_views, _cb, 'play', 'close')
54
302
  }
55
303
 
304
+ // Mac Catalyst note: Parent containers of transitioned views should use fixed
305
+ // dimensions (not Ti.UI.FILL). Resizable containers trigger a UIKit re-layout
306
+ // that distorts views with rotated Matrix2D transforms.
307
+ animationView.transition = (views, layouts) => {
308
+ if (params.debug) { console.log('') }
309
+ logger('`transition` method called on: ' + params.id)
310
+
311
+ const arr = Array.isArray(views) ? views : [views]
312
+
313
+ arr.forEach((view, i) => {
314
+ const layout = layouts[i]
315
+
316
+ if (!layout) {
317
+ view.animate(Ti.UI.createAnimation({ ...args, zIndex: 0, opacity: 0 }), () => {
318
+ view.applyProperties({ zIndex: 0, opacity: 0, transform: Ti.UI.createMatrix2D(), translation: { x: 0, y: 0 }, rotate: 0, scale: 1 })
319
+ })
320
+ return
321
+ }
322
+
323
+ const tx = layout.translation?.x ?? 0
324
+ const ty = layout.translation?.y ?? 0
325
+ const rotate = layout.rotate ?? 0
326
+ const scale = layout.scale ?? 1
327
+
328
+ const transform = Ti.UI.createMatrix2D().translate(tx, ty).rotate(rotate).scale(scale)
329
+
330
+ const needsFadeIn = view.opacity === 0
331
+
332
+ if (layout.zIndex !== undefined) view.zIndex = layout.zIndex
333
+
334
+ const animation = Ti.UI.createAnimation({
335
+ ...args,
336
+ transform
337
+ })
338
+
339
+ if (needsFadeIn) animation.opacity = 1
340
+ if (layout.width !== undefined) animation.width = layout.width
341
+ if (layout.height !== undefined) animation.height = layout.height
342
+ if (layout.opacity !== undefined) animation.opacity = layout.opacity
343
+
344
+ view.animate(animation, () => {
345
+ const props = { transform, translation: { x: tx, y: ty }, rotate, scale, zIndex: layout.zIndex }
346
+ if (needsFadeIn) props.opacity = 1
347
+ view.applyProperties(props)
348
+ })
349
+ })
350
+ }
351
+
56
352
  function handleTransformations(args) {
57
353
  if ('anchorPoint' in args || 'rotate' in args || 'scale' in args) {
58
354
  logger(' -> Creating transform')
@@ -124,19 +420,113 @@ function Animation(args = {}) {
124
420
  let offsetX, offsetY
125
421
  setBounds(draggableView, args.bounds)
126
422
 
127
- draggableView.addEventListener('touchstart', (event) => {
423
+ const onTouchStart = (event) => {
128
424
  offsetX = event.x
129
425
  offsetY = event.y
426
+ draggableView._wasDragged = false
427
+
428
+ // If a bounce-back animation is still running, complete it immediately
429
+ if (draggableView._bouncingBack) {
430
+ logger(` -> completing pending bounce-back for ${draggableView.id}`)
431
+ draggableView.applyProperties({
432
+ top: draggableView._originTop,
433
+ left: draggableView._originLeft,
434
+ transform: Ti.UI.createMatrix2D()
435
+ })
436
+ draggableView._bouncingBack = false
437
+ }
130
438
 
131
439
  updateDraggableZIndex(event)
132
440
  checkDraggable(draggableView, 'drag')
133
- })
134
441
 
135
- draggableView.addEventListener('touchend', () => checkDraggable(draggableView, 'drop'))
442
+ if (draggableView._collisionEnabled) {
443
+ if (draggableView._originTop === undefined) {
444
+ draggableView._originTop = draggableView.top
445
+ draggableView._originLeft = draggableView.left
446
+ logger(` -> touchStart origin capture (first): ${draggableView.id} top=${draggableView.top} left=${draggableView.left}`)
447
+ } else {
448
+ logger(` -> touchStart origin kept: ${draggableView.id} origin=(${draggableView._originTop}, ${draggableView._originLeft}) vs top/left=(${draggableView.top}, ${draggableView.left})`)
449
+ }
450
+ }
451
+ }
136
452
 
137
- draggableView.addEventListener('touchmove', (event) => handleTouchMove(event, draggableView, offsetX, offsetY))
453
+ const onTouchEnd = () => {
454
+ checkDraggable(draggableView, 'drop')
138
455
 
139
- Ti.Gesture.addEventListener('orientationchange', () => checkBoundaries(draggableView))
456
+ // Android: consolidate transform after drag so transition reads correct state
457
+ if (!params.isIOS && draggableView.translation) {
458
+ const { translation, rotate, scale } = draggableView
459
+ draggableView.applyProperties({
460
+ translation, rotate, scale,
461
+ transform: Ti.UI.createMatrix2D().translate(translation.x, translation.y).rotate(rotate ?? 0).scale(scale ?? 1)
462
+ })
463
+ }
464
+
465
+ if (draggableView._collisionEnabled) {
466
+ const directTarget = checkCollision(draggableView)
467
+ const target = directTarget ?? params.lastKnownTarget
468
+ logger(` -> collision check: ${draggableView.id} | direct: ${directTarget?.id ?? 'null'} | lastKnown: ${params.lastKnownTarget?.id ?? 'null'} | final: ${target?.id ?? 'null'}`)
469
+ if (target) {
470
+ // On Android, consolidate drag position before snap to avoid animation conflict
471
+ if (!params.isIOS) {
472
+ draggableView.applyProperties({
473
+ top: draggableView._visualTop ?? draggableView.top,
474
+ left: draggableView._visualLeft ?? draggableView.left
475
+ })
476
+ }
477
+ if (args.animationProperties?.snap?.center) {
478
+ logger(` -> snap-center: ${draggableView.id} to ${target.id}`)
479
+ animationView.snapTo(draggableView, [target])
480
+ }
481
+ if (params.dropCB) {
482
+ logger(` -> dropCB: ${draggableView.id} on ${target.id}`)
483
+ params.dropCB(draggableView, target)
484
+ }
485
+ } else if (!target && args.animationProperties?.snap?.back) {
486
+ logger(` -> bounce-back: ${draggableView.id} to (${draggableView._originTop}, ${draggableView._originLeft})`)
487
+
488
+ // On Android, consolidate drag position before bounce-back to avoid animation conflict
489
+ if (!params.isIOS) {
490
+ draggableView.applyProperties({
491
+ top: draggableView._visualTop ?? draggableView.top,
492
+ left: draggableView._visualLeft ?? draggableView.left
493
+ })
494
+ }
495
+
496
+ draggableView._bouncingBack = true
497
+
498
+ draggableView.animate({
499
+ ...args,
500
+ top: draggableView._originTop,
501
+ left: draggableView._originLeft,
502
+ transform: Ti.UI.createMatrix2D()
503
+ }, () => {
504
+ draggableView._bouncingBack = false
505
+ draggableView.applyProperties({
506
+ top: draggableView._originTop,
507
+ left: draggableView._originLeft,
508
+ transform: Ti.UI.createMatrix2D()
509
+ })
510
+ logger(` -> bounce-back complete: ${draggableView.id}`)
511
+ })
512
+ }
513
+ params.lastHoverTarget = null
514
+ params.lastKnownTarget = null
515
+ if (params.dragCB) {
516
+ params.dragCB(draggableView, null)
517
+ }
518
+ }
519
+ }
520
+
521
+ const onTouchMove = (event) => handleTouchMove(event, draggableView, offsetX, offsetY)
522
+ const onOrientationChange = () => checkBoundaries(draggableView)
523
+
524
+ draggableView._dragListeners = { onTouchStart, onTouchEnd, onTouchMove, onOrientationChange }
525
+
526
+ draggableView.addEventListener('touchend', onTouchEnd)
527
+ draggableView.addEventListener('touchmove', onTouchMove)
528
+ draggableView.addEventListener('touchstart', onTouchStart)
529
+ Ti.Gesture.addEventListener('orientationchange', onOrientationChange)
140
530
 
141
531
  params.draggables.push(draggableView)
142
532
  } else {
@@ -145,6 +535,7 @@ function Animation(args = {}) {
145
535
  }
146
536
 
147
537
  function handleTouchMove(event, draggableView, offsetX, offsetY) {
538
+ draggableView._wasDragged = true
148
539
  const convertedPoint = draggableView.convertPointToView({ x: event.x, y: event.y }, draggableView.parent)
149
540
  let top = Math.round(convertedPoint.y - offsetY)
150
541
  let left = Math.round(convertedPoint.x - offsetX)
@@ -166,11 +557,63 @@ function Animation(args = {}) {
166
557
  }
167
558
  }
168
559
 
560
+ draggableView._visualTop = top
561
+ draggableView._visualLeft = left
562
+
169
563
  if (params.isIOS) {
170
- const { x, y } = calculateTranslation(draggableView, draggableView.parent.rect, left, top)
171
- draggableView.applyProperties({ duration: 0, transform: Ti.UI.createMatrix2D().translate(x, y) })
564
+ const r = draggableView.rotate ?? 0
565
+ const s = draggableView.scale ?? 1
566
+
567
+ if (r !== 0 || s !== 1) {
568
+ // Delta-based drag for transformed views (preserves rotate/scale)
569
+ const translation = draggableView.translation ?? { x: 0, y: 0 }
570
+ const deltaX = Math.round((event.x - offsetX) * s)
571
+ const deltaY = Math.round((event.y - offsetY) * s)
572
+ translation.x += deltaX
573
+ translation.y += deltaY
574
+ draggableView.applyProperties({
575
+ translation, rotate: r, scale: s,
576
+ transform: Ti.UI.createMatrix2D().translate(translation.x, translation.y).rotate(r).scale(s)
577
+ })
578
+ } else {
579
+ // Standard drag for non-transformed views
580
+ const { x, y } = calculateTranslation(draggableView, draggableView.parent.rect, left, top)
581
+ draggableView.applyProperties({ duration: 0, transform: Ti.UI.createMatrix2D().translate(x, y) })
582
+ }
172
583
  } else {
173
- draggableView.animate(Ti.UI.createAnimation({ top, left, duration: 0 }))
584
+ const r = draggableView.rotate ?? 0
585
+ const s = draggableView.scale ?? 1
586
+
587
+ if (r !== 0 || s !== 1) {
588
+ // Delta-based drag for transformed views on Android
589
+ const translation = draggableView.translation ?? { x: 0, y: 0 }
590
+ const deltaX = Math.round((event.x - offsetX) * s)
591
+ const deltaY = Math.round((event.y - offsetY) * s)
592
+ translation.x += deltaX
593
+ translation.y += deltaY
594
+ draggableView.animate(Ti.UI.createAnimation({
595
+ transform: Ti.UI.createMatrix2D().translate(translation.x, translation.y).rotate(r).scale(s),
596
+ duration: 0
597
+ }))
598
+ draggableView.translation = translation
599
+ draggableView.rotate = r
600
+ draggableView.scale = s
601
+ } else {
602
+ draggableView.animate(Ti.UI.createAnimation({ top, left, duration: 0 }))
603
+ }
604
+ }
605
+
606
+ if (draggableView._collisionEnabled && params.dragCB) {
607
+ try {
608
+ const target = checkCollision(draggableView)
609
+ if (target) { params.lastKnownTarget = target }
610
+ if (target !== params.lastHoverTarget) {
611
+ params.lastHoverTarget = target
612
+ params.dragCB(draggableView, target)
613
+ }
614
+ } catch (e) {
615
+ logger(` !! collision error in touchmove: ${e.message}`)
616
+ }
174
617
  }
175
618
  }
176
619
 
@@ -178,15 +621,15 @@ function Animation(args = {}) {
178
621
  let x = left - parentViewRect.width / 2 + draggableView.rect.width / 2
179
622
  let y = top - parentViewRect.height / 2 + draggableView.rect.height / 2
180
623
 
181
- if (draggableView.left) {
624
+ if (draggableView.left != null) {
182
625
  x = left - draggableView.left
183
- } else if (draggableView.right) {
626
+ } else if (draggableView.right != null) {
184
627
  x = left - (parentViewRect.width - draggableView.right - draggableView.rect.width)
185
628
  }
186
629
 
187
- if (draggableView.top) {
630
+ if (draggableView.top != null) {
188
631
  y = top - draggableView.top
189
- } else if (draggableView.bottom) {
632
+ } else if (draggableView.bottom != null) {
190
633
  y = top - (parentViewRect.height - draggableView.bottom - draggableView.rect.height)
191
634
  }
192
635
 
@@ -206,8 +649,14 @@ function Animation(args = {}) {
206
649
  }
207
650
 
208
651
  function updateDraggableZIndex(event) {
209
- params.draggables.push(params.draggables.splice(realSourceView(event.source).zIndex, 1)[0])
210
- params.draggables.forEach((draggable, key) => { draggable.zIndex = key })
652
+ if (args.animationProperties?.keepZIndex) return
653
+
654
+ const source = realSourceView(event.source)
655
+ const idx = params.draggables.indexOf(source)
656
+ if (idx !== -1) {
657
+ params.draggables.push(params.draggables.splice(idx, 1)[0])
658
+ params.draggables.forEach((draggable, key) => { draggable.zIndex = key })
659
+ }
211
660
  }
212
661
 
213
662
  function realSourceView(_source) {
@@ -269,6 +718,26 @@ function Animation(args = {}) {
269
718
  })
270
719
  }
271
720
 
721
+ function checkCollision(draggableView) {
722
+ const top = draggableView._visualTop
723
+ const left = draggableView._visualLeft
724
+ if (left === null || left === undefined || top === null || top === undefined) {
725
+ return null
726
+ }
727
+
728
+ const cy = top + draggableView.rect.height / 2
729
+ const cx = left + draggableView.rect.width / 2
730
+
731
+ for (const other of params.collisionViews) {
732
+ if (other === draggableView) { continue }
733
+ const r = other.rect
734
+ if (cx > r.x && cx < r.x + r.width && cy > r.y && cy < r.y + r.height) {
735
+ return other
736
+ }
737
+ }
738
+ return null
739
+ }
740
+
272
741
  function checkDraggable(_view, _action) {
273
742
  logger('Check Draggable')
274
743
  logger(' -> `' + _action + '`')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "purgetss",
4
- "version": "7.4.0",
4
+ "version": "7.5.0",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "purgetss": "bin/purgetss"
@@ -87,7 +87,7 @@
87
87
  },
88
88
  "devDependencies": {
89
89
  "@eslint/js": "^9.39.1",
90
- "@fortawesome/fontawesome-free": "^7.1.0",
90
+ "@fortawesome/fontawesome-free": "^7.2.0",
91
91
  "eslint": "^9.39.1",
92
92
  "eslint-plugin-import": "^2.32.0",
93
93
  "eslint-plugin-n": "^17.23.1",
@@ -324,6 +324,7 @@ export function helperToBuildTailwindClasses(key, value) {
324
324
  case 'showUndoRedoActions':
325
325
  case 'showVerticalScrollIndicator':
326
326
  case 'size':
327
+ case 'snap':
327
328
  case 'softKeyboardOnFocus':
328
329
  case 'sound':
329
330
  case 'splitActionBar':
@@ -103,17 +103,27 @@ export function combineAllValues(base, defaultTheme) {
103
103
  const allValues = {}
104
104
 
105
105
  // ! Custom Window, View and ImageView
106
- allValues.Window = (configFile.theme.Window && configFile.theme.Window.apply)
107
- ? _.merge({ apply: configFile.theme.Window.apply }, configFile.theme.Window)
108
- : _.merge({ default: { backgroundColor: '#FFFFFF' } }, configFile.theme.Window)
106
+ // Merge extend values into theme (same as colors, spacing, etc.)
107
+ _.each(['Window', 'View', 'ImageView'], comp => {
108
+ if (configFile.theme.extend[comp]) {
109
+ configFile.theme[comp] = _.merge({}, configFile.theme[comp], configFile.theme.extend[comp])
110
+ delete configFile.theme.extend[comp]
111
+ }
112
+ // Normalize shorthand: { apply: '...' } → { default: { apply: '...' } }
113
+ if (configFile.theme[comp] && configFile.theme[comp].apply && !configFile.theme[comp].default) {
114
+ configFile.theme[comp] = { default: configFile.theme[comp] }
115
+ }
116
+ })
109
117
 
110
- allValues.ImageView = (configFile.theme.ImageView && configFile.theme.ImageView.apply)
111
- ? _.merge({ apply: configFile.theme.ImageView.apply }, { ios: { hires: true } }, configFile.theme.ImageView)
112
- : _.merge({ ios: { hires: true } }, configFile.theme.ImageView)
118
+ // Merge user config WITH defaults, then write back to configFile.theme
119
+ // so that downstream functions pick up the full merged object
120
+ configFile.theme.Window = _.merge({ default: { backgroundColor: '#FFFFFF' } }, configFile.theme.Window)
121
+ configFile.theme.ImageView = _.merge({ ios: { hires: true } }, configFile.theme.ImageView)
122
+ configFile.theme.View = _.merge({ default: { width: 'Ti.UI.SIZE', height: 'Ti.UI.SIZE' } }, configFile.theme.View)
113
123
 
114
- allValues.View = (configFile.theme.View && configFile.theme.View.apply)
115
- ? _.merge({ apply: configFile.theme.View.apply }, configFile.theme.View)
116
- : _.merge({ default: { width: 'Ti.UI.SIZE', height: 'Ti.UI.SIZE' } }, configFile.theme.View)
124
+ allValues.Window = configFile.theme.Window
125
+ allValues.ImageView = configFile.theme.ImageView
126
+ allValues.View = configFile.theme.View
117
127
 
118
128
  // ! Width, height and margin properties
119
129
  // INFO: sizingProperties: For glossary generator only... Do not move from this position.
@@ -243,6 +243,28 @@ export function toggle() {
243
243
  return convertedStyles
244
244
  }
245
245
 
246
+ /**
247
+ * Snap properties for the Animation module
248
+ * @returns {string} Generated styles
249
+ */
250
+ export function snap() {
251
+ let convertedStyles = processComments({
252
+ prop: 'snap - For the Animation module',
253
+ modules: 'Animation'
254
+ })
255
+
256
+ convertedStyles += '\'.snap-back\': { animationProperties: { snap: { back: true } } }\n'
257
+ convertedStyles += '\'.snap-back-false\': { animationProperties: { snap: { back: false } } }\n'
258
+ convertedStyles += '\'.snap-center\': { animationProperties: { snap: { center: true } } }\n'
259
+ convertedStyles += '\'.snap-center-false\': { animationProperties: { snap: { center: false } } }\n'
260
+ convertedStyles += '\'.snap-magnet\': { animationProperties: { snap: { magnet: true } } }\n'
261
+ convertedStyles += '\'.snap-magnet-false\': { animationProperties: { snap: { magnet: false } } }\n'
262
+ convertedStyles += '\'.keep-z-index\': { animationProperties: { keepZIndex: true } }\n'
263
+ convertedStyles += '\'.keep-z-index-false\': { animationProperties: { keepZIndex: false } }\n'
264
+
265
+ return convertedStyles
266
+ }
267
+
246
268
  /**
247
269
  * Dragging type property
248
270
  * @returns {string} Generated styles