@maccesar/titools 4.18.0 → 4.19.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maccesar/titools",
3
- "version": "4.18.0",
3
+ "version": "4.19.0",
4
4
  "description": "Titanium SDK skills and agents for AI coding assistants, including native game and sound synthesis modules",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -24,15 +24,19 @@ Before recommending a command, class, output path, `$.UI.create()`, TSS file, or
24
24
  | Task involves | Required reading |
25
25
  |---|---|
26
26
  | Alloy vs. Classic support or output paths | [references/classic-projects.md](references/classic-projects.md), [references/cli-commands.md](references/cli-commands.md) |
27
+ | Setup, auto-purge, or `watch` diagnostics | [references/installation-setup.md](references/installation-setup.md), [references/cli-commands.md](references/cli-commands.md) |
27
28
  | Choosing utility classes | [references/class-index.md](references/class-index.md) |
28
29
  | Layout (horizontal/vertical/composite, grid) | [references/grid-layout.md](references/grid-layout.md), [references/ui-ux-design.md](references/ui-ux-design.md) |
29
30
  | Dynamic components in controllers | [references/dynamic-component-creation.md](references/dynamic-component-creation.md) |
31
+ | App branding or artwork geometry | [references/app-branding.md](references/app-branding.md), [references/cli-commands.md](references/cli-commands.md) |
30
32
  | SVG / multi-density image assets | [references/svg-pipeline.md](references/svg-pipeline.md), [references/multi-density-images.md](references/multi-density-images.md) |
31
33
  | Custom values / arbitrary syntax | [references/arbitrary-values.md](references/arbitrary-values.md) |
32
34
  | Platform-specific styles (`ios:`, `android:`) | [references/platform-modifiers.md](references/platform-modifiers.md) |
33
35
  | Dark/Light mode | [references/semantic-colors.md](references/semantic-colors.md), [references/appearance-module.md](references/appearance-module.md) |
34
36
  | Apply directive / class extraction | [references/apply-directive.md](references/apply-directive.md) |
35
- | Animations | [references/animation-system.md](references/animation-system.md) |
37
+ | Custom or icon fonts | [references/custom-fonts.md](references/custom-fonts.md), [references/icon-fonts.md](references/icon-fonts.md) |
38
+ | Animations in Alloy | [references/animation-system.md](references/animation-system.md), [references/animation-advanced.md](references/animation-advanced.md) |
39
+ | `purgetss.ui` in Classic | [references/purgetss-ui-classic.md](references/purgetss-ui-classic.md), [references/classic-projects.md](references/classic-projects.md) |
36
40
 
37
41
  ### Step 3 — Output contract
38
42
 
@@ -445,7 +449,7 @@ Load these only when needed:
445
449
  - [iOS Large Titles](references/ios-large-titles.md) - Best practice for iOS Large Title navigation
446
450
 
447
451
  ### App Assets
448
- - [App Branding](references/app-branding.md) - `brand` command: the 14 pieces it regenerates (launcher, adaptive, iOS 18+ Dark/Tinted, marketplace, both splash sets), the per-piece `brand:` config, `--only`, `--optimize` (v7.13.0)
452
+ - [App Branding](references/app-branding.md) - `brand` command: 14 pieces, per-piece config, selection, optimization, appicon padding, and rounded non-icon artwork (v7.16.0)
449
453
  - [Launch Background](references/launch-background.md) - Carrying `brand.background` into the iOS LaunchScreen and a launcher-only Android theme (`--notes` output)
450
454
  - [Multi-Density Images](references/multi-density-images.md) - `images` command for Android res-*dpi + iPhone @1x/@2x/@3x UI images (v7.6.0)
451
455
  - [SVG Pipeline](references/svg-pipeline.md) - SVG-aware compile-time image pipeline: `.svg` refs + numeric `w-*`/`h-*` classes compile to 8 density PNGs; `images.files`/`images.autoSync` (v7.11.0–v7.11.1)
@@ -460,10 +464,11 @@ Load these only when needed:
460
464
  - [Custom Fonts](references/custom-fonts.md) - `build-fonts` command for Google Fonts, brand typefaces, and community icon fonts (.ttf + .css)
461
465
  - [Icon Fonts](references/icon-fonts.md) - The 4 official families (Font Awesome 7, Material Icons, Material Symbols, Framework7) — variant tables + `icon-library` install flow + recreating removed libraries
462
466
  - [Animation System](references/animation-system.md) - 15 methods including collision detection, transitions, and sequential animations
463
- - [Animation Advanced](references/animation-advanced.md) - Property inheritance, utility classes, implementation rules, complex UI example
467
+ - [Animation Advanced](references/animation-advanced.md) - Property forwarding, runtime rules, utility classes, and platform behavior
468
+ - [PurgeTSS UI in Classic](references/purgetss-ui-classic.md) - Native JavaScript setup, public exports, animation objects, cleanup, and platform notes
464
469
 
465
470
  ### Release Notes
466
- - [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.13.1)
471
+ - [Version History](references/version-history.md) - Release-by-release feature additions and behavior changes (v7.4.0 → v7.16.2)
467
472
 
468
473
  > **💡 TEXT FONTS (Google Fonts, Roboto, etc.)**
469
474
  > For text fonts, see [Custom Fonts](references/custom-fonts.md).
@@ -1,6 +1,6 @@
1
1
  # PurgeTSS Animation -- Advanced Reference
2
2
 
3
- Property inheritance, timing/utility classes, utility functions, implementation rules, and complex UI examples.
3
+ Runtime property forwarding, timing/utility classes, utility functions, implementation rules, and complex UI examples.
4
4
 
5
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
6
 
@@ -9,18 +9,42 @@ For core animation methods (play, toggle, apply, open, close, draggable, undragg
9
9
  <!-- TOC-START -->
10
10
  ## Contents
11
11
 
12
- - [Property Inheritance from the Animation Object](#property-inheritance-from-the-animation-object)
12
+ - [Runtime Property Forwarding](#runtime-property-forwarding)
13
+ - [Forwarding and Overrides](#forwarding-and-overrides)
13
14
  - [Timing and Utility Classes](#timing-and-utility-classes)
15
+ - [anchorPoint / origin-* Classes](#anchorpoint--origin--classes)
16
+ - [autoreverse](#autoreverse)
17
+ - [Curve Classes](#curve-classes)
18
+ - [delay-*](#delay-)
19
+ - [duration-*](#duration-)
20
+ - [repeat-*](#repeat-)
21
+ - [rotate-* and -rotate-*](#rotate--and--rotate-)
22
+ - [scale-*](#scale-)
23
+ - [Snap Classes](#snap-classes)
24
+ - [keep-z-index](#keep-z-index)
25
+ - [Drag Type](#drag-type)
26
+ - [Opacity and Visibility Utilities](#opacity-and-visibility-utilities)
27
+ - [zoom-in-* / zoom-out-*](#zoom-in---zoom-out-)
14
28
  - [Utility Functions](#utility-functions)
15
- - [Implementation Rules](#implementation-rules)
16
- - [Method Implementation Template](#method-implementation-template)
29
+ - [deviceInfo()](#deviceinfo)
30
+ - [saveComponent({ source, directory })](#savecomponent-source-directory-)
31
+ - [Runtime Implementation Rules](#runtime-implementation-rules)
32
+ - [Transform conversion](#transform-conversion)
33
+ - [State and children](#state-and-children)
34
+ - [Arrays and callbacks](#arrays-and-callbacks)
35
+ - [Position and drag state](#position-and-drag-state)
36
+ - [Android position consolidation](#android-position-consolidation)
37
+ - [Transition state](#transition-state)
38
+ - [Cleanup limitations](#cleanup-limitations)
17
39
  - [Complex UI Example](#complex-ui-example)
40
+ - [XML](#xml)
41
+ - [Controller](#controller)
18
42
 
19
43
  <!-- TOC-END -->
20
44
 
21
- ## Property Inheritance from the Animation Object
45
+ ## Runtime Property Forwarding
22
46
 
23
- 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.
47
+ The constructor keeps the resolved `<Animation>` properties as its base object. Most helpers spread that object into `Ti.UI.createAnimation()` before adding their own fields. This forwards native values such as `duration`, `delay`, `curve`, `opacity`, or `backgroundColor`, but it does not guarantee that every Titanium property is meaningful for every helper.
24
48
 
25
49
  When you declare an Animation object with utility classes:
26
50
 
@@ -28,38 +52,22 @@ When you declare an Animation object with utility classes:
28
52
  <Animation id="myAnim" module="purgetss.ui" class="duration-150 delay-100 curve-animation-ease-out" />
29
53
  ```
30
54
 
31
- 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.
55
+ In Classic, the equivalent values are passed directly to `createAnimation()`; see [purgetss-ui-classic.md](./purgetss-ui-classic.md).
32
56
 
33
- ### Inheritance Matrix
57
+ ### Forwarding and Overrides
34
58
 
35
- | Property | play/toggle | open/close | apply | sequence | swap | reorder | snapTo | shake | pulse | transition |
36
- | --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
37
- | `duration` | Y | Y | -- | Y | Y | Y | Y | Y (div6) | Y | Y |
38
- | `delay` | Y | Y | -- | Y | Y | Y | Y | Y | Y | Y |
39
- | `curve` | Y | Y | -- | Y | Y | Y | Y | fixed | fixed | Y |
40
- | `autoreverse` | Y | Y | -- | Y | -- | -- | -- | fixed | fixed | -- |
41
- | `repeat` | Y | Y | -- | Y | -- | -- | -- | fixed | param | -- |
42
-
43
- **Legend:**
44
- - **Y** = inherited from Animation object
45
- - **--** = not applicable to this method
46
- - **fixed** = uses internal fixed values (`shake`: `autoreverse: true`, `repeat: 3`, `curve: EASE_IN_OUT`; `pulse`: `autoreverse: true`, `curve: EASE_IN_OUT`)
47
- - **param** = controlled by method parameter (`pulse` `count` sets the repeat value)
48
- - **(div6)** = `shake` divides the inherited duration by 6 for each oscillation cycle
49
-
50
- ### Fallback Defaults
51
-
52
- When a property is not set on the Animation object and no explicit parameter is passed:
53
-
54
- | Property | `swap` / `reorder` / `snapTo` | `shake` |
55
- | --- | :---: | :---: |
56
- | `duration` | 200ms | 400ms |
57
- | `delay` | 0ms | 0ms |
58
- | `curve` | `EASE_IN_OUT` | `EASE_IN_OUT` (fixed) |
59
-
60
- ### Explicit Parameters Take Precedence
59
+ | Method | Forwarded base values | Forced or computed values |
60
+ | --- | --- | --- |
61
+ | `play` / `toggle` / `open` / `close` | Base object plus active state | Active-state transform |
62
+ | `apply` | Base object plus active state | Applies immediately rather than creating native motion |
63
+ | `sequence` | Same state values as `play` | Serial start order |
64
+ | `swap` | Base object | Destination position; source identity transform |
65
+ | `pulse` | Base object | Transform fallback, `autoreverse`, `repeat`, and curve |
66
+ | `shake` | Non-overridden base values | Transform, divided duration, `autoreverse`, `repeat`, and curve |
67
+ | `snapTo` / `reorder` | Base object | Destination position and identity transform |
68
+ | `transition` | Base object | Combined layout transform and optional size/opacity |
61
69
 
62
- An explicit value passed as a method parameter always overrides the inherited value:
70
+ Method-specific assignments are appended after the spread and therefore win on duplicate keys. The method parameters are feature-specific rather than alternative timing values:
63
71
 
64
72
  ```javascript
65
73
  // All timing controlled by the Animation object's classes
@@ -70,6 +78,8 @@ $.myAnim.snapTo($.card, targets)
70
78
  $.myAnim.transition(views, fanOutLayout)
71
79
  ```
72
80
 
81
+ There is no shared `200ms`/`0ms`/`EASE_IN_OUT` fallback for `swap`, `snapTo`, `reorder`, or `transition`. Configure timing on the Animation object when predictable visible motion matters. Only `shake` computes `(duration ?? 400) / 6`; `pulse` supplies a default scale of `1.2`. Both helpers force ease-in-out.
82
+
73
83
  ---
74
84
 
75
85
  ## Timing and Utility Classes
@@ -182,12 +192,12 @@ Control how draggable views behave when dropped. All are **off by default**.
182
192
  | `snap-back-false` | `{ animationProperties: { snap: { back: false } } }` | Disables snap-back |
183
193
  | `snap-center` | `{ animationProperties: { snap: { center: true } } }` | Auto-centers on target |
184
194
  | `snap-center-false` | `{ animationProperties: { snap: { center: false } } }` | Disables snap-center |
185
- | `snap-magnet` | `{ animationProperties: { snap: { magnet: true } } }` | (Planned) Magnetic attraction |
186
- | `snap-magnet-false` | `{ animationProperties: { snap: { magnet: false } } }` | Disables snap-magnet |
195
+
196
+ Generated utility inventories may still contain `snap-magnet` names, but the current `purgetss.ui` runtime never reads `animationProperties.snap.magnet`. Do not use or recommend those classes as working drag behavior.
187
197
 
188
198
  ### keep-z-index
189
199
 
190
- 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.
200
+ Prevents the drag system from promoting the active view on touch start. It does not prevent `draggable(array)` from first assigning each view `zIndex` from its array position, and `swap()` restores z-order from the current draggable registry rather than arbitrary original values.
191
201
 
192
202
  ```css
193
203
  '.keep-z-index': { animationProperties: { keepZIndex: true } }
@@ -234,236 +244,93 @@ The module exports helper functions alongside the Animation component:
234
244
 
235
245
  ### deviceInfo()
236
246
 
237
- Logs detailed platform and display information to the console. Works in both Alloy and Classic Titanium projects.
247
+ Logs platform and display information and returns `undefined`. It includes `xdpi` and `ydpi` only on Android. Its tablet flag currently recognizes iPad only, so Android tablets are reported as handheld.
238
248
 
239
249
  ```javascript
240
250
  const { deviceInfo } = require('purgetss.ui')
241
251
  deviceInfo()
242
252
  ```
243
253
 
254
+ Classic loads the generated file with `require('lib/purgetss.ui')`.
255
+
244
256
  ### saveComponent({ source, directory })
245
257
 
246
- Saves a snapshot of a view as a PNG to the photo gallery.
258
+ Calls `source.toImage()`, creates an MD5-based PNG filename, writes it to `directory` (default: `Ti.Filesystem.tempDirectory`), and then invokes `Ti.Media.saveToPhotoGallery()`. It returns `undefined` and has no completion or error callback.
247
259
 
248
260
  ```javascript
249
261
  const { saveComponent } = require('purgetss.ui')
250
- saveComponent({ source: $.myView, directory: 'screenshots' })
251
- ```
252
-
253
- ---
254
-
255
- ## Implementation Rules
256
-
257
- Rules that every method in the Animation module must follow. They keep behavior consistent with the declarative model of PurgeTSS.
258
-
259
- ### Rule 1: Inherit from the `<Animation />` object via `...args`
260
-
261
- Every method MUST inherit all properties from the Animation object by spreading `args`. Never cherry-pick individual properties.
262
-
263
- The `<Animation />` object is the single source of truth for animation behavior. When a developer declares:
264
-
265
- ```xml
266
- <Animation id="myAnim" module="purgetss.ui" class="curve-animation-ease-out opacity-50 delay-100 duration-300" />
262
+ saveComponent({ source: $.myView, directory: Ti.Filesystem.applicationDataDirectory })
267
263
  ```
268
264
 
269
- Every property -- timing AND visual -- is available in `args` and MUST be inherited by all methods.
270
-
271
- ```javascript
272
- // CORRECT -- inherits everything, method-specific props override
273
- view.animate({ ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
274
-
275
- // WRONG -- cherry-picks individual properties, breaks if new ones are added
276
- view.animate({ duration: args.duration, delay: args.delay, left: destLeft, top: destTop })
277
- ```
278
-
279
- This is the same pattern used by the core `playView` function:
280
-
281
- ```javascript
282
- const animation = Ti.UI.createAnimation(args)
283
- view.animate(animation)
284
- ```
285
-
286
- If a developer adds `opacity-50` to their `<Animation>`, they expect ALL methods to animate opacity, not just `play`.
287
-
288
- ### Rule 2: Override by position, not by exclusion
289
-
290
- If a method needs fixed values for specific properties, they go AFTER `...args` to override. Never filter or exclude properties from args.
265
+ Check or request photo-gallery permission before calling it. On iOS, declare the photo-library add usage description required by Titanium. Classic uses `require('lib/purgetss.ui')`; see [purgetss-ui-classic.md#runtime-utilities](./purgetss-ui-classic.md#runtime-utilities).
291
266
 
292
- ```javascript
293
- // CORRECT -- shake: inherits everything, then overrides what it needs
294
- view.animate({
295
- ...args,
296
- transform: Ti.UI.createMatrix2D().translate(intensity, 0),
297
- duration: Math.round((args.duration ?? 400) / 6),
298
- autoreverse: true,
299
- repeat: 3,
300
- curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
301
- })
302
-
303
- // WRONG -- filters args, only picks what it thinks it needs
304
- view.animate({
305
- duration: args.duration,
306
- transform: Ti.UI.createMatrix2D().translate(intensity, 0),
307
- autoreverse: true,
308
- repeat: 3
309
- })
310
- ```
267
+ ---
311
268
 
312
- Properties declared later in the object literal override earlier ones:
269
+ ## Runtime Implementation Rules
313
270
 
314
- ```javascript
315
- // If args = { duration: 300, opacity: 0.5, curve: EASE_OUT }
316
- view.animate({
317
- ...args, // duration: 300, opacity: 0.5, curve: EASE_OUT
318
- duration: 50, // overrides to 50
319
- curve: EASE_IN // overrides to EASE_IN
320
- })
321
- // Result: { duration: 50, opacity: 0.5, curve: EASE_IN }
322
- // opacity 0.5 is preserved from args -- not lost by filtering
323
- ```
271
+ These are observable rules of the generated module. Use them to diagnose surprising output; they are not a template or promise for future extension methods.
324
272
 
325
- ### Rule 3: No timing parameters in method signatures
273
+ ### Transform conversion
326
274
 
327
- 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.
275
+ At construction time, top-level `scale`, `rotate`, and `anchorPoint` are converted into one `Ti.UI.Matrix2D` and removed from the base object. Separate matrices are prepared for `animationProperties.open` and `.close`. `pulse`, `shake`, `swap`, `snapTo`, `reorder`, and `transition` may replace or reset that transform, so a position helper does not promise to preserve it.
328
276
 
329
- ```javascript
330
- // CORRECT -- only method-specific parameters
331
- animationView.swap = (view1, view2) => {
332
- animationView.reorder = (views, newOrder) => {
333
- animationView.shake = (view, intensity = 10) => {
334
- animationView.snapTo = (view, targets) => {
335
- animationView.pulse = (view, count = 1) => {
336
-
337
- // WRONG -- timing parameters belong in the <Animation /> object
338
- animationView.swap = (view1, view2, duration) => {
339
- animationView.shake = (view, intensity, duration) => {
340
- ```
277
+ ### State and children
341
278
 
342
- Users control timing declaratively:
279
+ `play()`, `toggle()`, `apply()`, and `sequence()` toggle internal open/close state. `open()` and `close()` set it explicitly. The active state is merged into the current base object when `animationProperties` exists.
343
280
 
344
- ```xml
345
- <!-- Fast swap -->
346
- <Animation id="fastSwap" module="purgetss.ui" class="duration-75" />
281
+ After a top-level `play`, `toggle`, `open`, or `close`, `animationProperties.complete` starts as a second animation. `apply()` applies `complete` immediately. The public callback belongs to the base play/apply operation; it does not wait for the second top-level `complete` animation.
347
282
 
348
- <!-- Slow swap with delay -->
349
- <Animation id="slowSwap" module="purgetss.ui" class="delay-200 duration-500" />
350
- ```
283
+ For each direct child with an active state, merge precedence is:
351
284
 
352
- ```javascript
353
- // Same method call, different behavior -- controlled by XML
354
- $.fastSwap.swap($.card1, $.card2)
355
- $.slowSwap.swap($.card1, $.card2)
356
- ```
285
+ 1. Parent `animationProperties.children`.
286
+ 2. Child `animationProperties.child`.
287
+ 3. Child `animationProperties.open`, `.close`, or `.complete`.
357
288
 
358
- ### Rule 4: Consolidate state with `applyProperties` post-animation
289
+ Later objects win. Only direct children of the target are considered.
359
290
 
360
- After animating position (`left`/`top`), ALWAYS consolidate with `applyProperties` in the callback so the final state is real (not just visual via transform).
291
+ ### Arrays and callbacks
361
292
 
362
- ```javascript
363
- // CORRECT -- consolidates after animation
364
- view.animate({
365
- ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
366
- }, () => {
367
- view.applyProperties({ left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
368
- })
369
-
370
- // WRONG -- animation ends but view's actual properties are stale
371
- view.animate({
372
- ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
373
- })
374
- ```
293
+ `play()`, `open()`, `close()`, and `apply()` accept one view or an array. Array callbacks run once per view and include `index`, `total`, and `getTarget()`. The configured base delay accumulates for successive views. `sequence()` waits for each native completion and invokes its callback once after the final view; an empty array never invokes it.
375
294
 
376
- 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.
295
+ The callback is a new object containing selected native primitives plus PurgeTSS metadata. It is not the original Titanium event.
377
296
 
378
- ### Rule 5: Track position with `_origin*` properties
297
+ ### Position and drag state
379
298
 
380
- 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.
299
+ `swap()`, `snapTo()`, and `reorder()` persist destinations with `applyProperties()` and update private origin fields. They fall back to rendered `rect` coordinates, so call them only after layout.
381
300
 
382
- ```javascript
383
- // CORRECT -- updates origin tracking
384
- view.animate({
385
- ...args, left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D()
386
- }, () => {
387
- view.applyProperties({ left: destLeft, top: destTop, transform: Ti.UI.createMatrix2D() })
388
- })
389
-
390
- view._originTop = destTop
391
- view._originLeft = destLeft
392
- ```
301
+ Drag precedence is precise:
393
302
 
394
- How `_origin*` works:
395
- - `_originTop`/`_originLeft` represent the view's "logical grid position"
396
- - `swap` reads from `view._originTop ?? view.top` -- falls back to the actual `top` if no origin is set
397
- - `onTouchStart` in the drag handler saves the current `top`/`left` as `_origin*` for bounce-back
398
- - `undraggable` cleans up all `_origin*` properties
303
+ - Constructor bounds are the base; `view.bounds` overrides individual edges.
304
+ - Axis restriction comes only from `view.constraint`.
305
+ - `view.draggingType` overrides constructor `draggingType`.
306
+ - Constructor `draggable.drag/drop` is merged with `view.draggable.drag/drop`; view properties win.
307
+ - Every resolved drag/drop property is forwarded to Titanium.
308
+ - `snap.center` invokes `snapTo()` after a valid drop; `snap.back` returns a missed drop to its captured origin.
309
+ - No `snap.magnet` behavior exists.
399
310
 
400
- ### Rule 6: Consolidate Android drag position before drop animations
311
+ Collision detection uses the dragged view's center. On release, the last non-null hover target is a fallback when the final hit test returns `null`.
401
312
 
402
- 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`:
313
+ `draggable(array)` immediately assigns each view `zIndex` from its array index. `keepZIndex` only disables later touch-start promotion. `swap()` restores z-order by the current draggable-registry order, not by arbitrary values present before registration.
403
314
 
404
- ```javascript
405
- if (!params.isIOS) {
406
- draggableView.applyProperties({
407
- top: draggableView._visualTop ?? draggableView.top,
408
- left: draggableView._visualLeft ?? draggableView.left
409
- })
410
- }
411
- ```
412
-
413
- 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()`.
315
+ ### Android position consolidation
414
316
 
415
- **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.
317
+ Android drag uses zero-duration native animations. At touch end, when translation state exists, the runtime consolidates `translation`, `rotate`, `scale`, and an equivalent `Matrix2D` before collision, snap, or drop callbacks. It does not consolidate only `top` and `left`. iOS uses synchronous property application for standard dragging and a separate transformed-view path.
416
318
 
417
- ### Rule 7: Clean up in `undraggable`
418
-
419
- Every internal property added to views MUST be cleaned up in `undraggable`:
420
-
421
- | Property | Set by | Purpose |
422
- | --- | --- | --- |
423
- | `_originTop` / `_originLeft` | `swap`, `reorder`, `snapTo`, `onTouchStart` | Logical position tracking |
424
- | `_visualTop` / `_visualLeft` | `handleTouchMove` | Visual position during drag |
425
- | `_dragListeners` | `makeViewsDraggable` | Touch event listener references |
426
- | `_collisionEnabled` | `detectCollisions` | Collision detection flag |
427
- | `_wasDragged` | `onTouchStart` / `handleTouchMove` | Drag detection flag |
428
- | `_bouncingBack` | `onTouchEnd` (bounce-back) | Prevents origin capture during mid-animation; `swap` cancels it before animating |
319
+ ### Transition state
429
320
 
430
- When adding a new method that stores internal state on views, add the cleanup to `undraggable`:
321
+ `transition()` builds one matrix per view from translation, rotation, and scale, applies a supplied `zIndex` before animation, and can animate width, height, and opacity.
431
322
 
432
- ```javascript
433
- animationView.undraggable = (_views) => {
434
- const arr = Array.isArray(_views) ? _views : [_views]
435
- arr.forEach(view => {
436
- // ... existing cleanup ...
437
- delete view._newProperty // ADD cleanup for any new internal property
438
- })
439
- }
440
- ```
323
+ Without a matching layout, a view fades out, receives `zIndex: 0`, and has touch disabled. iOS preserves its last transform; Android resets transform, translation, rotation, and scale. A later layout fades the view back in. Completion forces `touchEnabled: true` for every matched view, so reapply `false` for intentionally non-interactive views.
441
324
 
442
- ---
325
+ ### Cleanup limitations
443
326
 
444
- ## Method Implementation Template
327
+ `undraggable()` removes the latest stored touch/orientation listeners, the first matching draggable and collision registrations, and most private drag state. Current limitations require application discipline:
445
328
 
446
- When creating a new method, follow this template:
329
+ - `draggable()` does not deduplicate. Repeated registration creates extra listeners and entries that one cleanup call cannot fully remove.
330
+ - `_wasDragged` remains on the view after `undraggable()`.
331
+ - `detectCollisions()` retains non-null callbacks on the animation object; passing `null` later does not clear them.
447
332
 
448
- ```javascript
449
- animationView.newMethod = (view, specificParam = defaultValue) => {
450
- if (params.debug) { console.log('') }
451
- logger('`newMethod` method called on: ' + params.id)
452
- if (!view) { return notFound() }
453
-
454
- view.animate({
455
- ...args, // Rule 1: inherit all from <Animation />
456
- specificProp: computedValue, // Rule 2: override AFTER ...args
457
- }, () => {
458
- view.applyProperties({ ... }) // Rule 4: consolidate state
459
- })
460
-
461
- view._originTop = newTop // Rule 5: track position (if applicable)
462
- view._originLeft = newLeft
463
- }
464
- // Rule 3: no timing params in signature
465
- // Rule 7: add cleanup to undraggable (if new internal state)
466
- ```
333
+ Register each view once. On window close, call `undraggable()` for draggable views and registered collision targets, remove application-owned listeners, and release callbacks and animation references that are no longer needed. Do not make application code depend on private underscore fields.
467
334
 
468
335
  ---
469
336
 
@@ -27,6 +27,8 @@
27
27
 
28
28
  PurgeTSS includes an Animation module for 2D Matrix animations and transformations. It works on single elements, arrays of elements, or individual children of an element.
29
29
 
30
+ The XML and `$.*` examples in this file are for Alloy. Titanium Classic uses the same generated runtime through JavaScript objects; see [purgetss-ui-classic.md](./purgetss-ui-classic.md) for the Classic installation path, factory API, method contracts, and lifecycle rules.
31
+
30
32
  The Animation object describes an animation in a few ways:
31
33
  - A single-phase animation with an end state
32
34
  - A multi-phase animation using the `open`, `close`, and `complete` modifiers
@@ -62,13 +64,13 @@ You can use any position, size, color, transformation, and opacity classes from
62
64
  | `open` | `open(views, cb)` | Explicitly run the opening animation (no toggle) |
63
65
  | `close` | `close(views, cb)` | Explicitly run the closing animation (no toggle) |
64
66
  | `draggable` | `draggable(views)` | Make views draggable via touch events |
65
- | `undraggable` | `undraggable(views)` | Remove draggable behavior and clean up all listeners |
67
+ | `undraggable` | `undraggable(views)` | Remove the module-owned drag listeners and most private drag state |
66
68
  | `detectCollisions` | `detectCollisions(views, dragCB, dropCB)` | Enable collision detection with hover/drop callbacks |
67
69
  | `sequence` | `sequence(views, cb)` | Animate views one after another (sequential, not parallel) |
68
70
  | `swap` | `swap(view1, view2)` | Animate two views exchanging positions |
69
71
  | `pulse` | `pulse(view, count)` | Scale-up-and-back animation (default count=1) |
70
72
  | `shake` | `shake(view, intensity)` | Horizontal shake for feedback (default intensity=10) |
71
- | `snapTo` | `snapTo(view, targets)` | Snap view to nearest target by center distance; returns matched target or null |
73
+ | `snapTo` | `snapTo(view, targets)` | Snap to the nearest target; returns it immediately, `null` if none qualifies, or `undefined` for a missing source |
72
74
  | `reorder` | `reorder(views, newOrder)` | Animate views to new positions by index mapping |
73
75
  | `transition` | `transition(views, layouts)` | Multi-view layout transitions using Matrix2D (translate, rotate, scale) |
74
76
 
@@ -82,7 +84,7 @@ The `play`, `toggle`, `open`, `close`, `apply`, and `sequence` methods accept an
82
84
  | --- | --- |
83
85
  | `open:` | Properties applied during open state |
84
86
  | `close:` | Properties applied during close state |
85
- | `complete:` | Additional properties applied after an open animation finishes |
87
+ | `complete:` | Additional properties applied after the active `play`/`toggle`/`open`/`close` state, or immediately after `apply` |
86
88
  | `children:` | Global properties for all children of a View |
87
89
  | `child:` | Individual properties for specific children |
88
90
  | `bounds:` | Drag boundaries within parent |
@@ -127,7 +129,7 @@ First call applies `open:` properties; next call applies `close:` properties; co
127
129
 
128
130
  ### `complete` Modifier
129
131
 
130
- Applies additional properties after an `open` animation finishes:
132
+ Applies additional properties after the active state finishes. With `play`, `toggle`, `open`, or `close`, it starts a second native animation; with `apply`, it is applied immediately:
131
133
 
132
134
  ```xml
133
135
  <Animation module="purgetss.ui" id="myAnimationOpen"
@@ -147,6 +149,8 @@ $.myAnimation.play([$.view1, $.view2, $.view3])
147
149
 
148
150
  The callback fires once per view. Use `e.index` and `e.total` to track progress.
149
151
 
152
+ Array playback starts all views without waiting for the previous completion. The configured base delay is increased cumulatively for each successive view. Use `sequence()` when each view must finish before the next begins.
153
+
150
154
  ---
151
155
 
152
156
  ## Callback Event Object
@@ -278,7 +282,7 @@ A blank or existing Animation object can be reused. When used with an array, zIn
278
282
 
279
283
  - **Global modifiers** on the Animation object apply to all views
280
284
  - **Local modifiers** on individual views override globals
281
- - While dragging, `size`, `scale`, and `anchorPoint` transformations are **not** applied
285
+ - All properties in the resolved `drag` or `drop` object are forwarded to Titanium, including size, transform, and anchor-point properties
282
286
 
283
287
  ```xml
284
288
  <Animation id="draggableAnimation" module="purgetss.ui" class="drag:duration-100 drag:opacity-50 drop:opacity-100" />
@@ -312,14 +316,16 @@ Limits movement within the parent. Local on the view or global on the Animation
312
316
 
313
317
  ## The `undraggable` Method
314
318
 
315
- Removes draggable behavior and cleans up all listeners from one or more views.
319
+ Removes the current module-owned draggable behavior from one or more views.
316
320
 
317
321
  ```javascript
318
322
  $.draggableAnimation.undraggable($.card)
319
323
  $.draggableAnimation.undraggable([$.card1, $.card2, $.card3])
320
324
  ```
321
325
 
322
- Removes: `touchstart`/`touchend`/`touchmove` listeners, `Ti.Gesture.orientationchange` listener, collision detection registry entries, and internal properties (`_originTop`, `_originLeft`, `_visualTop`, `_visualLeft`, `_collisionEnabled`, `_dragListeners`, `_wasDragged`, `_bouncingBack`).
326
+ It removes the stored `touchstart`/`touchend`/`touchmove` listeners, the stored `Ti.Gesture.orientationchange` listener, the first matching draggable/collision registry entries, and most private position/collision fields. The current runtime does **not** delete `_wasDragged`.
327
+
328
+ Register each view only once. `draggable()` does not deduplicate registrations: repeated calls add listeners and registry entries, while one `undraggable()` call removes only the latest stored listener set and the first matching entries. `detectCollisions()` also retains the last non-null callbacks on the Animation object; passing `null` later does not clear them.
323
329
 
324
330
  **Use cases:** Lock a piece after correct placement, toggle edit/presentation mode, cleanup on window close (prevents memory leaks from global `orientationchange` listener).
325
331
 
@@ -418,6 +424,7 @@ $.myAnimation.sequence(views, callback)
418
424
  - The `open`/`close` state is toggled once for the entire sequence
419
425
  - Each view fully completes before the next starts
420
426
  - The callback fires once after the last view finishes
427
+ - An empty array starts nothing and does not invoke the callback
421
428
 
422
429
  ### Onboarding Reveal Example
423
430
 
@@ -448,9 +455,9 @@ $.myAnimation.swap(view1, view2)
448
455
  ```
449
456
 
450
457
  **Behavior:**
451
- - Inherits `duration`, `delay`, `curve` from Animation object. Fallback: **200ms**, **0ms**, **EASE_IN_OUT**
452
- - Handles iOS transform reset automatically
453
- - Temporarily elevates z-index; restores after completion
458
+ - Forwards the Animation object's native properties; there is no shared duration, delay, or curve fallback
459
+ - Resets the source transform while persisting the exchanged positions
460
+ - Temporarily elevates z-index; then assigns z-index from the current draggable-registry order rather than restoring arbitrary original values
454
461
  - Updates `_originLeft`/`_originTop` for subsequent drag operations
455
462
  - **Automatic position normalization**: no explicit `top`/`left` needed -- resolved via `view.rect`
456
463
  - **Bounce-back safe**: completes any in-progress bounce-back before swapping
@@ -514,7 +521,7 @@ $.myAnimation.shake(view, intensity)
514
521
  | `view` | `View` | -- | The view to shake |
515
522
  | `intensity` | `Number` | `10` | Horizontal displacement in pixels |
516
523
 
517
- - `duration`: inherited, divided by 6 for each shake cycle. Fallback: 400ms
524
+ - `duration`: inherited, or `400ms` when absent, then divided by 6 for each shake phase
518
525
  - `delay`: inherited, applied before shake starts
519
526
  - `curve`: always `EASE_IN_OUT` (fixed)
520
527
  - `autoreverse` and `repeat`: always `true` and `3` (fixed)
@@ -528,14 +535,14 @@ $.emailField.applyProperties({ borderColor: '#ef4444' })
528
535
 
529
536
  ## The `snapTo` Method
530
537
 
531
- Snaps a view to the nearest target by center-to-center Euclidean distance. Returns the matched target or `null`.
538
+ Snaps a view to the nearest target by center-to-center Euclidean distance. It returns the matched target immediately, `null` when no target can be selected, or `undefined` for a missing source; the return does not mean the animation has completed.
532
539
 
533
540
  ```javascript
534
541
  const matched = $.myAnimation.snapTo(view, targets)
535
542
  ```
536
543
 
537
- - Inherits `duration`, `delay`, `curve`. Fallback: **200ms**, **0ms**, **EASE_IN_OUT**
538
- - Handles iOS transform reset; updates `_originLeft`/`_originTop`
544
+ - Forwards the Animation object's native properties; there is no shared duration, delay, or curve fallback
545
+ - Resets the moved view's transform and updates `_originLeft`/`_originTop`
539
546
  - Automatic position normalization
540
547
 
541
548
  ### Puzzle Game with Collision Detection
@@ -567,7 +574,7 @@ $.myAnimation.reorder(views, newOrder)
567
574
 
568
575
  - `newOrder` maps current index to new position: `[2, 1, 0]` reverses, `[1, 2, 0]` rotates
569
576
  - Length must match `views` length
570
- - Inherits `duration`, `delay`, `curve`. Fallback: **200ms**, **0ms**, **EASE_IN_OUT**
577
+ - Forwards the Animation object's native properties; there is no shared duration, delay, or curve fallback
571
578
  - Automatic position normalization
572
579
 
573
580
  ```javascript
@@ -604,7 +611,9 @@ $.myAnimation.transition(views, layouts)
604
611
  - Each view gets a single `Ti.UI.createAnimation()` with combined `Matrix2D` transform
605
612
  - All animations launch simultaneously
606
613
  - `zIndex` applied synchronously before animation starts
607
- - **Mismatched lengths**: extra views fade out; extra layouts ignored; faded views fade back in when given a layout entry
614
+ - **Mismatched lengths**: extra views fade to opacity `0`, receive `zIndex: 0`, and have touch disabled; extra layouts are ignored; hidden views fade back in and regain touch when later given a layout entry
615
+ - iOS preserves the last transform of a hidden view. Android resets its transform, translation, rotation, and scale
616
+ - A matched layout's completion handler forces `touchEnabled: true`; reapply `false` afterward for a view that must remain non-interactive
608
617
 
609
618
  ### Reusable Presets
610
619
 
@@ -639,7 +648,7 @@ function doStack() { $.galleryAnim.transition(photos, stack) }
639
648
  $.galleryAnim.draggable(photos) // photos keep rotation/scale while dragging
640
649
  ```
641
650
 
642
- `keep-z-index` preserves the layout order during drag.
651
+ `keep-z-index` prevents touch-start promotion during drag. It does not prevent `draggable(array)` from first assigning each view `zIndex` from its array index.
643
652
 
644
653
  ### Mac Catalyst Note
645
654