@maccesar/titools 2.2.11 → 2.3.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 (99) hide show
  1. package/README.md +255 -713
  2. package/bin/titools.js +1 -1
  3. package/lib/commands/update.js +5 -6
  4. package/lib/config.js +1 -0
  5. package/lib/downloader.js +10 -0
  6. package/package.json +1 -1
  7. package/skills/alloy-guides/SKILL.md +46 -48
  8. package/skills/alloy-guides/references/CONTROLLERS.md +4 -6
  9. package/skills/alloy-guides/references/MODELS.md +340 -184
  10. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +3 -4
  11. package/skills/alloy-guides/references/VIEWS_STYLES.md +44 -46
  12. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +4 -5
  13. package/skills/alloy-guides/references/VIEWS_XML.md +51 -17
  14. package/skills/alloy-guides/references/WIDGETS.md +1 -1
  15. package/skills/alloy-howtos/SKILL.md +12 -13
  16. package/skills/alloy-howtos/references/cli_reference.md +40 -41
  17. package/skills/alloy-howtos/references/samples.md +3 -4
  18. package/skills/purgetss/SKILL.md +369 -356
  19. package/skills/purgetss/references/EXAMPLES.md +24 -25
  20. package/skills/purgetss/references/animation-advanced.md +555 -0
  21. package/skills/purgetss/references/animation-system.md +395 -995
  22. package/skills/purgetss/references/apply-directive.md +111 -141
  23. package/skills/purgetss/references/arbitrary-values.md +206 -480
  24. package/skills/purgetss/references/class-categories.md +277 -0
  25. package/skills/purgetss/references/class-index.md +1 -420
  26. package/skills/purgetss/references/cli-commands.md +446 -556
  27. package/skills/purgetss/references/configurable-properties.md +163 -599
  28. package/skills/purgetss/references/custom-rules.md +33 -76
  29. package/skills/purgetss/references/customization-deep-dive.md +319 -518
  30. package/skills/purgetss/references/dynamic-component-creation.md +33 -37
  31. package/skills/purgetss/references/grid-layout.md +42 -371
  32. package/skills/purgetss/references/icon-fonts.md +82 -475
  33. package/skills/purgetss/references/installation-setup.md +159 -331
  34. package/skills/purgetss/references/migration-guide.md +54 -109
  35. package/skills/purgetss/references/opacity-modifier.md +25 -222
  36. package/skills/purgetss/references/performance-tips.md +2 -2
  37. package/skills/purgetss/references/platform-modifiers.md +21 -407
  38. package/skills/purgetss/references/tikit-components.md +237 -104
  39. package/skills/purgetss/references/titanium-resets.md +20 -21
  40. package/skills/purgetss/references/ui-ux-design.md +171 -1702
  41. package/skills/ti-api/SKILL.md +109 -0
  42. package/skills/ti-api/references/api-android.md +675 -0
  43. package/skills/ti-api/references/api-app-platform.md +636 -0
  44. package/skills/ti-api/references/api-core.md +764 -0
  45. package/skills/ti-api/references/api-data-network.md +641 -0
  46. package/skills/ti-api/references/api-media.md +655 -0
  47. package/skills/ti-api/references/api-modules-ble-bluetooth.md +657 -0
  48. package/skills/ti-api/references/api-modules-coremotion-urlsession.md +411 -0
  49. package/skills/ti-api/references/api-modules-map.md +632 -0
  50. package/skills/ti-api/references/api-modules-nfc.md +725 -0
  51. package/skills/ti-api/references/api-modules-social-misc.md +526 -0
  52. package/skills/ti-api/references/api-services.md +700 -0
  53. package/skills/ti-api/references/api-ui-android.md +499 -0
  54. package/skills/ti-api/references/api-ui-extras.md +702 -0
  55. package/skills/ti-api/references/api-ui-ios-animator.md +378 -0
  56. package/skills/ti-api/references/api-ui-ios.md +756 -0
  57. package/skills/ti-api/references/api-ui-lists.md +581 -0
  58. package/skills/ti-api/references/api-ui-text-input.md +607 -0
  59. package/skills/ti-api/references/api-ui-views.md +572 -0
  60. package/skills/ti-api/references/api-ui-windows-navigation.md +676 -0
  61. package/skills/ti-api/references/api-xml-global.md +743 -0
  62. package/skills/ti-expert/SKILL.md +106 -138
  63. package/skills/ti-expert/references/adaptive-layouts.md +414 -0
  64. package/skills/ti-expert/references/alloy-builtins.md +16 -20
  65. package/skills/ti-expert/references/alloy-structure.md +40 -42
  66. package/skills/ti-expert/references/anti-patterns.md +34 -0
  67. package/skills/ti-expert/references/architecture-tiers.md +248 -0
  68. package/skills/ti-expert/references/code-conventions.md +5 -3
  69. package/skills/ti-expert/references/error-handling.md +14 -7
  70. package/skills/ti-expert/references/examples.md +4 -2
  71. package/skills/ti-expert/references/performance-optimization.md +26 -24
  72. package/skills/ti-expert/references/security-device.md +17 -14
  73. package/skills/ti-expert/references/security-fundamentals.md +60 -101
  74. package/skills/ti-expert/references/state-management.md +15 -14
  75. package/skills/ti-expert/references/testing-e2e-ci.md +30 -21
  76. package/skills/ti-expert/references/theming.md +12 -20
  77. package/skills/ti-guides/SKILL.md +13 -17
  78. package/skills/ti-guides/references/advanced-data-and-images.md +30 -10
  79. package/skills/ti-guides/references/application-frameworks.md +3 -3
  80. package/skills/ti-guides/references/coding-best-practices.md +31 -2
  81. package/skills/ti-guides/references/commonjs-advanced.md +46 -4
  82. package/skills/ti-guides/references/hello-world.md +9 -13
  83. package/skills/ti-guides/references/hyperloop-native-access.md +4 -1
  84. package/skills/ti-guides/references/javascript-primer.md +13 -5
  85. package/skills/ti-guides/references/resources.md +0 -2
  86. package/skills/ti-guides/references/style-and-conventions.md +1 -0
  87. package/skills/ti-guides/references/tiapp-config.md +0 -32
  88. package/skills/ti-howtos/SKILL.md +43 -45
  89. package/skills/ti-howtos/references/buffer-codec-streams.md +25 -3
  90. package/skills/ti-howtos/references/debugging-profiling.md +14 -25
  91. package/skills/ti-howtos/references/google-maps-v2.md +3 -3
  92. package/skills/ti-howtos/references/ios-map-kit.md +8 -1
  93. package/skills/ti-howtos/references/notification-services.md +0 -1
  94. package/skills/ti-howtos/references/webpack-build-pipeline.md +3 -0
  95. package/skills/ti-ui/SKILL.md +47 -49
  96. package/skills/ti-ui/references/application-structures.md +3 -2
  97. package/skills/ti-ui/references/orientation.md +17 -9
  98. package/skills/ti-ui/references/platform-ui-ios.md +63 -0
  99. package/skills/ti-ui/references/scrolling-views.md +39 -0
@@ -4,9 +4,8 @@ Complete examples of common patterns with anti-patterns and correct implementati
4
4
 
5
5
  ## Titanium Layout Patterns
6
6
 
7
- :::danger NO FLEXBOX IN TITANIUM
8
- Titanium does NOT support CSS Flexbox. All examples use `horizontal`, `vertical`, or `composite` layouts.
9
- :::
7
+ > **🚨 NO FLEXBOX IN TITANIUM**
8
+ > Titanium does NOT support CSS Flexbox. All examples use `horizontal`, `vertical`, or `composite` layouts.
10
9
 
11
10
  ### Horizontal Row with Space Between
12
11
 
@@ -99,13 +98,13 @@ Titanium does NOT support CSS Flexbox. All examples use `horizontal`, `vertical`
99
98
 
100
99
  ### Available Layout Classes
101
100
 
102
- | Class | Description | Use Case |
103
- | ------------ | ------------------------------------ | ------------------------------ |
104
- | `horizontal` | Children arranged left to right | Rows of buttons, form fields |
105
- | `vertical` | Children arranged top to bottom | Stacked content, lists |
106
- | *(no class)* | Defaults to `composite` | Absolute positioning, overlays |
107
- | `flow-col` | Horizontal grid flow with 100% width | Special grid layouts |
108
- | `flow-row` | Vertical grid flow with 100% height | Special grid layouts |
101
+ | Class | Description | Use Case |
102
+ | --------------- | ------------------------------------ | ------------------------------ |
103
+ | `horizontal` | Children arranged left to right | Rows of buttons, form fields |
104
+ | `vertical` | Children arranged top to bottom | Stacked content, lists |
105
+ | *(no class)* | Defaults to `composite` | Absolute positioning, overlays |
106
+ | `grid-flow-col` | Horizontal grid flow with 100% width | Special grid layouts |
107
+ | `grid-flow-row` | Vertical grid flow with 100% height | Special grid layouts |
109
108
 
110
109
  ---
111
110
 
@@ -213,11 +212,11 @@ alloy compile
213
212
  **✅ CORRECT (Use margins on children instead):**
214
213
  ```xml
215
214
  <View>
216
- <Label class="m-4" text="Hello" />
215
+ <Label class="m-4 wh-auto" text="Hello" />
217
216
  </View>
218
217
  ```
219
218
 
220
- **Why:** Titanium Views don't support padding properties. Use margins on child elements.
219
+ **Why:** Titanium Views don't support padding properties. Use margins on child elements, and add `wh-auto` on `Label`/`Button`/`Switch` (or any `SIZE`-default component) when opposite margins would otherwise trigger Titanium edge pinning.
221
220
 
222
221
  ---
223
222
 
@@ -321,16 +320,16 @@ alloy compile
321
320
 
322
321
  ## Quick Reference Table
323
322
 
324
- | Anti-Pattern | Why It Fails | Correct Approach |
325
- | --------------------- | ------------------------ | ----------------------- |
326
- | `flex-row` | Flexbox not supported | `horizontal` |
327
- | `flex-col` | Flexbox not supported | `vertical` |
328
- | `justify-*` | Flexbox not supported | Use margins/positioning |
329
- | `items-center` | Flexbox not supported | Use `center` class |
330
- | `p-4` on View | No padding on containers | `m-4` on children |
331
- | `w-full` | Percentage-based | `w-screen` (Ti.UI.FILL) |
332
- | `rounded-full` | Needs size suffix | `rounded-full-12` |
333
- | `composite` class | Already default | Omit it |
334
- | `w-[100px]` | Wrong syntax | `w-(100px)` |
335
- | Manual `.tss` | Overwritten by PurgeTSS | Use utility classes |
336
- | `gap` with `%` widths | Total exceeds 100% | Use explicit margins |
323
+ | Anti-Pattern | Why It Fails | Correct Approach |
324
+ | --------------------- | ------------------------ | ------------------------ |
325
+ | `flex-row` | Flexbox not supported | `horizontal` |
326
+ | `flex-col` | Flexbox not supported | `vertical` |
327
+ | `justify-*` | Flexbox not supported | Use margins/positioning |
328
+ | `items-center` | Flexbox mental model | Use layout + positioning |
329
+ | `p-4` on View | No padding on containers | `m-4` on children |
330
+ | `w-full` | Percentage-based | `w-screen` (Ti.UI.FILL) |
331
+ | `rounded-full` | Needs size suffix | `rounded-full-12` |
332
+ | `composite` class | Already default | Omit it |
333
+ | `w-[100px]` | Wrong syntax | `w-(100px)` |
334
+ | Manual `.tss` | Overwritten by PurgeTSS | Use utility classes |
335
+ | `gap` with `%` widths | Total exceeds 100% | Use explicit margins |
@@ -0,0 +1,555 @@
1
+ # PurgeTSS Animation -- Advanced Reference
2
+
3
+ Property inheritance, timing/utility classes, utility functions, implementation rules, and complex UI examples.
4
+
5
+ For core animation methods (play, toggle, apply, open, close, draggable, undraggable, detectCollisions, sequence, swap, pulse, shake, snapTo, reorder, transition), see [animation-system.md](./animation-system.md).
6
+
7
+ ---
8
+
9
+ ## Property Inheritance from the Animation Object
10
+
11
+ All methods in the Animation module inherit properties from the `<Animation>` object's classes. You configure animation behavior in XML and it applies to every method call.
12
+
13
+ When you declare an Animation object with utility classes:
14
+
15
+ ```xml
16
+ <Animation id="myAnim" module="purgetss.ui" class="duration-150 delay-100 curve-animation-ease-out" />
17
+ ```
18
+
19
+ The parsed properties (`duration: 150`, `delay: 100`, `curve: EASE_OUT`) are stored in the internal `args` object. Each method reads from `args` as its default values.
20
+
21
+ ### Inheritance Matrix
22
+
23
+ | Property | play/toggle | open/close | apply | sequence | swap | reorder | snapTo | shake | pulse | transition |
24
+ | --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
25
+ | `duration` | Y | Y | -- | Y | Y | Y | Y | Y (div6) | Y | Y |
26
+ | `delay` | Y | Y | -- | Y | Y | Y | Y | Y | Y | Y |
27
+ | `curve` | Y | Y | -- | Y | Y | Y | Y | fixed | fixed | Y |
28
+ | `autoreverse` | Y | Y | -- | Y | -- | -- | -- | fixed | fixed | -- |
29
+ | `repeat` | Y | Y | -- | Y | -- | -- | -- | fixed | param | -- |
30
+
31
+ **Legend:**
32
+ - **Y** = inherited from Animation object
33
+ - **--** = not applicable to this method
34
+ - **fixed** = uses internal fixed values (`shake`: `autoreverse: true`, `repeat: 3`, `curve: EASE_IN_OUT`; `pulse`: `autoreverse: true`, `curve: EASE_IN_OUT`)
35
+ - **param** = controlled by method parameter (`pulse` `count` sets the repeat value)
36
+ - **(div6)** = `shake` divides the inherited duration by 6 for each oscillation cycle
37
+
38
+ ### Fallback Defaults
39
+
40
+ When a property is not set on the Animation object and no explicit parameter is passed:
41
+
42
+ | Property | `swap` / `reorder` / `snapTo` | `shake` |
43
+ | --- | :---: | :---: |
44
+ | `duration` | 200ms | 400ms |
45
+ | `delay` | 0ms | 0ms |
46
+ | `curve` | `EASE_IN_OUT` | `EASE_IN_OUT` (fixed) |
47
+
48
+ ### Explicit Parameters Take Precedence
49
+
50
+ An explicit value passed as a method parameter always overrides the inherited value:
51
+
52
+ ```javascript
53
+ // All timing controlled by the Animation object's classes
54
+ $.myAnim.swap($.card1, $.card2)
55
+ $.myAnim.reorder(cards, [2, 0, 1])
56
+ $.myAnim.shake($.errorField, 20)
57
+ $.myAnim.snapTo($.card, targets)
58
+ $.myAnim.transition(views, fanOutLayout)
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Timing and Utility Classes
64
+
65
+ ### anchorPoint / origin-* Classes
66
+
67
+ Controls the pivot point of the animation. Available as both `origin-*` and `anchor-point-*` variants:
68
+
69
+ | Class | anchorPoint |
70
+ | --- | --- |
71
+ | `origin-center` / `anchor-point-center` | `{ x: 0.5, y: 0.5 }` |
72
+ | `origin-top` / `anchor-point-top` | `{ x: 0.5, y: 0 }` |
73
+ | `origin-top-right` / `anchor-point-top-right` | `{ x: 1, y: 0 }` |
74
+ | `origin-right` / `anchor-point-right` | `{ x: 0.5, y: 1 }` |
75
+ | `origin-bottom-right` / `anchor-point-bottom-right` | `{ x: 1, y: 1 }` |
76
+ | `origin-bottom` / `anchor-point-bottom` | `{ x: 0.5, y: 1 }` |
77
+ | `origin-bottom-left` / `anchor-point-bottom-left` | `{ x: 0, y: 1 }` |
78
+ | `origin-left` / `anchor-point-left` | `{ x: 0, y: 0.5 }` |
79
+ | `origin-top-left` / `anchor-point-top-left` | `{ x: 0, y: 0 }` |
80
+
81
+ ### autoreverse
82
+
83
+ Whether the animation replays in reverse after completing. Default: `false`.
84
+
85
+ ```css
86
+ '.autoreverse': { autoreverse: true }
87
+ '.autoreverse-false': { autoreverse: false }
88
+ ```
89
+
90
+ ### Curve Classes
91
+
92
+ ```css
93
+ '.curve-animation-ease-in': { curve: Ti.UI.ANIMATION_CURVE_EASE_IN }
94
+ '.curve-animation-ease-in-out': { curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT }
95
+ '.curve-animation-ease-out': { curve: Ti.UI.ANIMATION_CURVE_EASE_OUT }
96
+ '.curve-animation-linear': { curve: Ti.UI.ANIMATION_CURVE_LINEAR }
97
+ ```
98
+
99
+ ### delay-*
100
+
101
+ Delay in milliseconds before starting the animation. Range: **0** to **5000ms**.
102
+
103
+ Values: `delay-0`, `delay-25`, `delay-50`, `delay-75`, `delay-100`, `delay-150`, `delay-200`, `delay-250`, `delay-300`, `delay-350`, `delay-400`, `delay-450`, `delay-500`, `delay-600`, `delay-700`, `delay-800`, `delay-900`, `delay-1000`, `delay-2000`, `delay-3000`, `delay-4000`, `delay-5000`.
104
+
105
+ ### duration-*
106
+
107
+ Duration of the animation in milliseconds. Range: **0** to **5000ms**.
108
+
109
+ Values: `duration-0`, `duration-25`, `duration-50`, `duration-75`, `duration-100`, `duration-150`, `duration-200`, `duration-250`, `duration-300`, `duration-350`, `duration-400`, `duration-450`, `duration-500`, `duration-600`, `duration-700`, `duration-800`, `duration-900`, `duration-1000`, `duration-2000`, `duration-3000`, `duration-4000`, `duration-5000`.
110
+
111
+ ### repeat-*
112
+
113
+ Number of times the animation should run. If `autoreverse` is true, one repeat means the animation plays forward and backward once. Range: **0** to **12**.
114
+
115
+ Values: `repeat-0` through `repeat-12`.
116
+
117
+ ### rotate-* and -rotate-*
118
+
119
+ Rotation in degrees. Range: **0** to **180**, positive and negative.
120
+
121
+ ```css
122
+ '.rotate-0': { rotate: 0 }
123
+ '.rotate-1': { rotate: 1 }
124
+ '.rotate-2': { rotate: 2 }
125
+ '.rotate-3': { rotate: 3 }
126
+ '.rotate-6': { rotate: 6 }
127
+ '.rotate-12': { rotate: 12 }
128
+ '.rotate-45': { rotate: 45 }
129
+ '.rotate-90': { rotate: 90 }
130
+ '.rotate-180': { rotate: 180 }
131
+ '.-rotate-0': { rotate: 0 }
132
+ '.-rotate-1': { rotate: -1 }
133
+ '.-rotate-2': { rotate: -2 }
134
+ '.-rotate-3': { rotate: -3 }
135
+ '.-rotate-6': { rotate: -6 }
136
+ '.-rotate-12': { rotate: -12 }
137
+ '.-rotate-45': { rotate: -45 }
138
+ '.-rotate-90': { rotate: -90 }
139
+ '.-rotate-180': { rotate: -180 }
140
+ ```
141
+
142
+ ### scale-*
143
+
144
+ Scales the matrix by the specified factor. Same value for horizontal and vertical. Default: `1`.
145
+
146
+ ```css
147
+ '.scale-0': { scale: 0 }
148
+ '.scale-1': { scale: 0.01 }
149
+ '.scale-5': { scale: 0.05 }
150
+ '.scale-10': { scale: 0.10 }
151
+ '.scale-25': { scale: 0.25 }
152
+ '.scale-50': { scale: 0.5 }
153
+ '.scale-75': { scale: 0.75 }
154
+ '.scale-90': { scale: 0.9 }
155
+ '.scale-95': { scale: 0.95 }
156
+ '.scale-100': { scale: 1 }
157
+ '.scale-105': { scale: 1.05 }
158
+ '.scale-110': { scale: 1.1 }
159
+ '.scale-125': { scale: 1.25 }
160
+ '.scale-150': { scale: 1.5 }
161
+ ```
162
+
163
+ ### Snap Classes
164
+
165
+ Control how draggable views behave when dropped. All are **off by default**.
166
+
167
+ | Class | TSS | Description |
168
+ | --- | --- | --- |
169
+ | `snap-back` | `{ animationProperties: { snap: { back: true } } }` | Returns to origin when dropped outside target |
170
+ | `snap-back-false` | `{ animationProperties: { snap: { back: false } } }` | Disables snap-back |
171
+ | `snap-center` | `{ animationProperties: { snap: { center: true } } }` | Auto-centers on target |
172
+ | `snap-center-false` | `{ animationProperties: { snap: { center: false } } }` | Disables snap-center |
173
+ | `snap-magnet` | `{ animationProperties: { snap: { magnet: true } } }` | (Planned) Magnetic attraction |
174
+ | `snap-magnet-false` | `{ animationProperties: { snap: { magnet: false } } }` | Disables snap-magnet |
175
+
176
+ ### keep-z-index
177
+
178
+ Prevents the drag system from auto-promoting the dragged view's z-index. Useful when using `transition` presets where z-order is part of the layout.
179
+
180
+ ```css
181
+ '.keep-z-index': { animationProperties: { keepZIndex: true } }
182
+ '.keep-z-index-false': { animationProperties: { keepZIndex: false } }
183
+ ```
184
+
185
+ ### Drag Type
186
+
187
+ ```css
188
+ '.drag-apply': { draggingType: 'apply' }
189
+ '.drag-animate': { draggingType: 'animate' }
190
+ ```
191
+
192
+ ### Opacity and Visibility Utilities
193
+
194
+ ```css
195
+ '.opacity-to-0': { opacity: 1, animationProperties: { open: { opacity: 0 }, close: { opacity: 1 } } }
196
+ '.opacity-to-100': { opacity: 0, animationProperties: { open: { opacity: 1 }, close: { opacity: 0 } } }
197
+ '.toggle-visible': { animationProperties: { open: { visible: true }, close: { visible: false } } }
198
+ ```
199
+
200
+ ### zoom-in-* / zoom-out-*
201
+
202
+ Animates zoom in and zoom out. Sets the View's scale to the specified value, then animates it back to 1. Range: **0** to **150**.
203
+
204
+ ```css
205
+ '.zoom-in-0': { animationProperties: { open: { scale: 0 }, complete: { scale: 1 } } }
206
+ '.zoom-in-50': { animationProperties: { open: { scale: 0.5 }, complete: { scale: 1 } } }
207
+ '.zoom-in-95': { animationProperties: { open: { scale: 0.95 }, complete: { scale: 1 } } }
208
+ '.zoom-in-110': { animationProperties: { open: { scale: 1.1 }, complete: { scale: 1 } } }
209
+ '.zoom-out-0': { animationProperties: { close: { scale: 0 }, complete: { scale: 1 } } }
210
+ '.zoom-out-50': { animationProperties: { close: { scale: 0.5 }, complete: { scale: 1 } } }
211
+ '.zoom-out-95': { animationProperties: { close: { scale: 0.95 }, complete: { scale: 1 } } }
212
+ '.zoom-out-110': { animationProperties: { close: { scale: 1.1 }, complete: { scale: 1 } } }
213
+ ```
214
+
215
+ Full range: `zoom-in-0`, `zoom-in-1`, `zoom-in-5`, `zoom-in-10`, `zoom-in-25`, `zoom-in-50`, `zoom-in-75`, `zoom-in-90`, `zoom-in-95`, `zoom-in-100`, `zoom-in-105`, `zoom-in-110`, `zoom-in-125`, `zoom-in-150` (and same for `zoom-out-*`).
216
+
217
+ ---
218
+
219
+ ## Utility Functions
220
+
221
+ The module exports helper functions alongside the Animation component:
222
+
223
+ ### deviceInfo()
224
+
225
+ Logs detailed platform and display information to the console. Works in both Alloy and Classic Titanium projects.
226
+
227
+ ```javascript
228
+ const { deviceInfo } = require('purgetss.ui')
229
+ deviceInfo()
230
+ ```
231
+
232
+ ### saveComponent({ source, directory })
233
+
234
+ Saves a snapshot of a view as a PNG to the photo gallery.
235
+
236
+ ```javascript
237
+ const { saveComponent } = require('purgetss.ui')
238
+ saveComponent({ source: $.myView, directory: 'screenshots' })
239
+ ```
240
+
241
+ ### createAnimation(args)
242
+
243
+ Factory function to create Animation objects programmatically.
244
+
245
+ ```javascript
246
+ const { createAnimation } = require('purgetss.ui')
247
+ const anim = createAnimation({ duration: 300, curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT })
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Implementation Rules
253
+
254
+ Rules that every method in the Animation module must follow. They keep behavior consistent with the declarative model of PurgeTSS.
255
+
256
+ ### Rule 1: Inherit from the `<Animation />` object via `...args`
257
+
258
+ Every method MUST inherit all properties from the Animation object by spreading `args`. Never cherry-pick individual properties.
259
+
260
+ The `<Animation />` object is the single source of truth for animation behavior. When a developer declares:
261
+
262
+ ```xml
263
+ <Animation id="myAnim" module="purgetss.ui" class="curve-animation-ease-out opacity-50 delay-100 duration-300" />
264
+ ```
265
+
266
+ Every property -- timing AND visual -- is available in `args` and MUST be inherited by all methods.
267
+
268
+ ```javascript
269
+ // CORRECT -- inherits everything, method-specific props override
270
+ view.animate({ ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
271
+
272
+ // WRONG -- cherry-picks individual properties, breaks if new ones are added
273
+ view.animate({ duration: args.duration, delay: args.delay, left: destLeft, top: destTop })
274
+ ```
275
+
276
+ This is the same pattern used by the core `playView` function:
277
+
278
+ ```javascript
279
+ const animation = Ti.UI.createAnimation(args)
280
+ view.animate(animation)
281
+ ```
282
+
283
+ If a developer adds `opacity-50` to their `<Animation>`, they expect ALL methods to animate opacity, not just `play`.
284
+
285
+ ### Rule 2: Override by position, not by exclusion
286
+
287
+ If a method needs fixed values for specific properties, they go AFTER `...args` to override. Never filter or exclude properties from args.
288
+
289
+ ```javascript
290
+ // CORRECT -- shake: inherits everything, then overrides what it needs
291
+ view.animate({
292
+ ...args,
293
+ transform: Ti.UI.createMatrix2D().translate(intensity, 0),
294
+ duration: Math.round((args.duration ?? 400) / 6),
295
+ autoreverse: true,
296
+ repeat: 3,
297
+ curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
298
+ })
299
+
300
+ // WRONG -- filters args, only picks what it thinks it needs
301
+ view.animate({
302
+ duration: args.duration,
303
+ transform: Ti.UI.createMatrix2D().translate(intensity, 0),
304
+ autoreverse: true,
305
+ repeat: 3
306
+ })
307
+ ```
308
+
309
+ Properties declared later in the object literal override earlier ones:
310
+
311
+ ```javascript
312
+ // If args = { duration: 300, opacity: 0.5, curve: EASE_OUT }
313
+ view.animate({
314
+ ...args, // duration: 300, opacity: 0.5, curve: EASE_OUT
315
+ duration: 50, // overrides to 50
316
+ curve: EASE_IN // overrides to EASE_IN
317
+ })
318
+ // Result: { duration: 50, opacity: 0.5, curve: EASE_IN }
319
+ // opacity 0.5 is preserved from args -- not lost by filtering
320
+ ```
321
+
322
+ ### Rule 3: No timing parameters in method signatures
323
+
324
+ The existing core methods (`play`, `open`, `close`, `apply`, `sequence`) do NOT accept `duration`, `delay`, or `curve` as parameters. New methods MUST follow the same pattern. Only parameters specific to the method's unique functionality are allowed.
325
+
326
+ ```javascript
327
+ // CORRECT -- only method-specific parameters
328
+ animationView.swap = (view1, view2) => {
329
+ animationView.reorder = (views, newOrder) => {
330
+ animationView.shake = (view, intensity = 10) => {
331
+ animationView.snapTo = (view, targets) => {
332
+ animationView.pulse = (view, count = 1) => {
333
+
334
+ // WRONG -- timing parameters belong in the <Animation /> object
335
+ animationView.swap = (view1, view2, duration) => {
336
+ animationView.shake = (view, intensity, duration) => {
337
+ ```
338
+
339
+ Users control timing declaratively:
340
+
341
+ ```xml
342
+ <!-- Fast swap -->
343
+ <Animation id="fastSwap" module="purgetss.ui" class="duration-75" />
344
+
345
+ <!-- Slow swap with delay -->
346
+ <Animation id="slowSwap" module="purgetss.ui" class="delay-200 duration-500" />
347
+ ```
348
+
349
+ ```javascript
350
+ // Same method call, different behavior -- controlled by XML
351
+ $.fastSwap.swap($.card1, $.card2)
352
+ $.slowSwap.swap($.card1, $.card2)
353
+ ```
354
+
355
+ ### Rule 4: Consolidate state with `applyProperties` post-animation
356
+
357
+ After animating position (`left`/`top`), ALWAYS consolidate with `applyProperties` in the callback so the final state is real (not just visual via transform).
358
+
359
+ ```javascript
360
+ // CORRECT -- consolidates after animation
361
+ view.animate({
362
+ ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
363
+ }, () => {
364
+ view.applyProperties({ left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
365
+ })
366
+
367
+ // WRONG -- animation ends but view's actual properties are stale
368
+ view.animate({
369
+ ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
370
+ })
371
+ ```
372
+
373
+ On iOS, dragging uses `transform.translate()` -- the view's `left`/`top` properties don't change. `applyProperties` ensures the view's actual properties match the visual position and the transform is reset to identity.
374
+
375
+ ### Rule 5: Track position with `_origin*` properties
376
+
377
+ Methods that move position (`swap`, `reorder`, `snapTo`, and future methods like `slideTo`) MUST update `_originTop`/`_originLeft` after the animation so that subsequent drag/swap operations work correctly.
378
+
379
+ ```javascript
380
+ // CORRECT -- updates origin tracking
381
+ view.animate({
382
+ ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
383
+ }, () => {
384
+ view.applyProperties({ left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
385
+ })
386
+
387
+ view._originTop = destTop
388
+ view._originLeft = destLeft
389
+ ```
390
+
391
+ How `_origin*` works:
392
+ - `_originTop`/`_originLeft` represent the view's "logical grid position"
393
+ - `swap` reads from `view._originTop ?? view.top` -- falls back to the actual `top` if no origin is set
394
+ - `onTouchStart` in the drag handler saves the current `top`/`left` as `_origin*` for bounce-back
395
+ - `undraggable` cleans up all `_origin*` properties
396
+
397
+ ### Rule 6: Consolidate Android drag position before drop animations
398
+
399
+ On Android, drag uses `animate({ duration: 0 })` which is asynchronous -- the last frame may still be in-flight when `touchend` fires. Before starting any drop animation on Android, consolidate the view's position with `applyProperties`:
400
+
401
+ ```javascript
402
+ if (!params.isIOS) {
403
+ draggableView.applyProperties({
404
+ top: draggableView._visualTop ?? draggableView.top,
405
+ left: draggableView._visualLeft ?? draggableView.left
406
+ })
407
+ }
408
+ ```
409
+
410
+ This applies to both the snap path and the bounce-back path in `onTouchEnd`. iOS does not need this because drag uses synchronous `transform.translate()`.
411
+
412
+ **Collision fallback on drop:** During drag, `checkCollision` runs on every `touchmove`. When the user releases while still in motion, the drag center may exit the target between the last `touchmove` and `touchend`. The module tracks `lastKnownTarget` and uses it as fallback when `checkCollision` returns null on drop.
413
+
414
+ ### Rule 7: Clean up in `undraggable`
415
+
416
+ Every internal property added to views MUST be cleaned up in `undraggable`:
417
+
418
+ | Property | Set by | Purpose |
419
+ | --- | --- | --- |
420
+ | `_originTop` / `_originLeft` | `swap`, `reorder`, `snapTo`, `onTouchStart` | Logical position tracking |
421
+ | `_visualTop` / `_visualLeft` | `handleTouchMove` | Visual position during drag |
422
+ | `_dragListeners` | `makeViewsDraggable` | Touch event listener references |
423
+ | `_collisionEnabled` | `detectCollisions` | Collision detection flag |
424
+ | `_wasDragged` | `onTouchStart` / `handleTouchMove` | Drag detection flag |
425
+ | `_bouncingBack` | `onTouchEnd` (bounce-back) | Prevents origin capture during mid-animation; `swap` cancels it before animating |
426
+
427
+ When adding a new method that stores internal state on views, add the cleanup to `undraggable`:
428
+
429
+ ```javascript
430
+ animationView.undraggable = (_views) => {
431
+ const arr = Array.isArray(_views) ? _views : [_views]
432
+ arr.forEach(view => {
433
+ // ... existing cleanup ...
434
+ delete view._newProperty // ADD cleanup for any new internal property
435
+ })
436
+ }
437
+ ```
438
+
439
+ ---
440
+
441
+ ## Method Implementation Template
442
+
443
+ When creating a new method, follow this template:
444
+
445
+ ```javascript
446
+ animationView.newMethod = (view, specificParam = defaultValue) => {
447
+ if (params.debug) { console.log('') }
448
+ logger('`newMethod` method called on: ' + params.id)
449
+ if (!view) { return notFound() }
450
+
451
+ view.animate({
452
+ ...args, // Rule 1: inherit all from <Animation />
453
+ specificProp: computedValue, // Rule 2: override AFTER ...args
454
+ }, () => {
455
+ view.applyProperties({ ... }) // Rule 4: consolidate state
456
+ })
457
+
458
+ view._originTop = newTop // Rule 5: track position (if applicable)
459
+ view._originLeft = newLeft
460
+ }
461
+ // Rule 3: no timing params in signature
462
+ // Rule 7: add cleanup to undraggable (if new internal state)
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Complex UI Example
468
+
469
+ This example combines several Animation module features in a single UI: a collapsible sidebar, an expandable card, and draggable elements.
470
+
471
+ Install FontAwesome fonts first:
472
+
473
+ ```bash
474
+ purgetss icon-library -v=fa
475
+ ```
476
+
477
+ ### XML
478
+
479
+ ```xml
480
+ <Alloy>
481
+ <Window class="exit-on-close-false portrait bg-purple-700">
482
+ <Animation id="draggableAnimation" module="purgetss.ui" class="bounds:m-4 bounds:mb-20" />
483
+
484
+ <!-- Sidebar -->
485
+ <Animation id="sideBarAnimation" module="purgetss.ui" class="close:w-24 duration-150 open:w-72" />
486
+ <Animation id="sideBarAnimationChevron" module="purgetss.ui" class="close:rotate-0 duration-150 open:rotate-180" />
487
+
488
+ <View id="sideBar" class="ml-2 h-1/2 w-24">
489
+ <View class="vertical ios:shadow-lg mr-8 rounded-lg bg-white" ios:onSingletap="doAction" android:onClick="doAction">
490
+ <View class="grid-flow-row">
491
+ <View class="ml-0 w-64 grid-rows-7">
492
+ <View class="horizontal bg-selected-purple-100 items-center" action="home">
493
+ <Label class="touch-enabled-false fas fa-home ml-0 h-full w-16 border-transparent bg-transparent text-center text-xl text-purple-700" />
494
+ <Label class="touch-enabled-false text-purple-700" text="Home" />
495
+ </View>
496
+ </View>
497
+ <!-- More menu items: profile, messages, help, settings, password, sign-out -->
498
+ </View>
499
+ </View>
500
+ <Button id="sideBarChevron" class="fas rounded-10 fa-chevron-right mr-1.5 h-10 w-10 border-4 border-purple-700 bg-white text-xl text-purple-700" ios:onSingletap="sideBarClicked" android:onClick="sideBarClicked" />
501
+ </View>
502
+
503
+ <!-- My Card -->
504
+ <Animation id="myCardAnimation" module="purgetss.ui" class="open:h-(298) close:h-24 duration-150" />
505
+ <Animation id="myCardAnimationChevron" module="purgetss.ui" class="close:rotate-0 duration-150 open:rotate-180" />
506
+
507
+ <View id="myCard" class="mr-6 mt-6 h-24 w-8/12 rounded-lg shadow-lg">
508
+ <View class="vertical rounded-lg bg-white">
509
+ <View class="h-auto w-screen">
510
+ <ImageView class="rounded-16 m-4 ml-4 h-16 w-16" image="https://randomuser.me/api/portraits/women/17.jpg" />
511
+ <View class="vertical ml-24 h-auto">
512
+ <Label class="ml-0 text-sm font-bold text-gray-800" text="Someone Famous" />
513
+ <Label class="ml-0 text-xs font-bold text-gray-400" text="Website Designer" />
514
+ </View>
515
+ </View>
516
+ <View class="rounded-1 mx-2 h-0.5 w-screen bg-gray-300" />
517
+ <View class="bubble-parent-false mt-2 h-48 w-screen grid-flow-row" ios:onSingletap="doAction" android:onClick="doAction">
518
+ <!-- Card menu items: edit profile, inbox, settings, support, sign-out -->
519
+ </View>
520
+ </View>
521
+ <View class="rounded-tl-br-md wh-8 bubble-parent-false mb-0 mr-0 bg-blue-400">
522
+ <Button id="myCardChevron" class="fas fa-chevron-down border-transparent bg-transparent text-white" ios:onSingletap="myCardClicked" android:onClick="myCardClicked" />
523
+ </View>
524
+ </View>
525
+
526
+ <Label id="action" class="mx-4 mb-6 h-10 w-screen rounded-lg bg-purple-800 text-sm font-bold text-purple-50" />
527
+ </Window>
528
+ </Alloy>
529
+ ```
530
+
531
+ ### Controller
532
+
533
+ ```javascript
534
+ $.index.open()
535
+
536
+ $.draggableAnimation.draggable($.myCard)
537
+
538
+ function sideBarClicked() {
539
+ $.sideBarAnimation.play($.sideBar)
540
+ $.sideBarAnimationChevron.play($.sideBarChevron)
541
+ }
542
+
543
+ function myCardClicked() {
544
+ $.myCardAnimation.play($.myCard)
545
+ $.myCardAnimationChevron.play($.myCardChevron)
546
+ }
547
+
548
+ function doAction(event) {
549
+ if (event.source.action) {
550
+ $.action.text = ` Action: ${event.source.action}`
551
+ }
552
+ }
553
+ ```
554
+
555
+ This demonstrates: `play` for sidebar/card toggle, `open:`/`close:` modifiers for different states, `draggable` with `bounds:` modifier, and multiple Animation objects controlling different UI elements.