@maccesar/titools 4.18.0 → 4.20.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 (28) hide show
  1. package/package.json +1 -1
  2. package/skills/purgetss/SKILL.md +9 -4
  3. package/skills/purgetss/references/animation-advanced.md +93 -226
  4. package/skills/purgetss/references/animation-system.md +26 -17
  5. package/skills/purgetss/references/app-branding.md +31 -8
  6. package/skills/purgetss/references/appearance-module.md +63 -9
  7. package/skills/purgetss/references/class-categories.md +2 -2
  8. package/skills/purgetss/references/class-index.md +1 -1
  9. package/skills/purgetss/references/classic-projects.md +24 -5
  10. package/skills/purgetss/references/cli-commands.md +59 -30
  11. package/skills/purgetss/references/custom-fonts.md +29 -2
  12. package/skills/purgetss/references/customization-deep-dive.md +20 -10
  13. package/skills/purgetss/references/dynamic-component-creation.md +1 -1
  14. package/skills/purgetss/references/icon-fonts.md +3 -1
  15. package/skills/purgetss/references/installation-setup.md +5 -1
  16. package/skills/purgetss/references/multi-density-images.md +35 -5
  17. package/skills/purgetss/references/purgetss-ui-classic.md +341 -0
  18. package/skills/purgetss/references/version-history.md +17 -0
  19. package/skills/ti-game/SKILL.md +5 -2
  20. package/skills/ti-game/references/api.md +38 -4
  21. package/skills/ti-game/references/debugging-performance.md +1 -1
  22. package/skills/ti-game/references/examples.md +6 -5
  23. package/skills/ti-game/references/patterns.md +1 -1
  24. package/skills/ti-game/references/project-setup.md +7 -3
  25. package/skills/ti-game/references/recipes-action.md +1 -1
  26. package/skills/ti-game/references/recipes-adventure-effects.md +1 -1
  27. package/skills/ti-game/references/roadmap.md +8 -4
  28. package/skills/ti-game/references/tilemaps.md +1 -1
@@ -178,7 +178,7 @@ After `purgetss build-fonts`, the generated `fonts.tss` includes the family clas
178
178
 
179
179
  Two optional flags adjust what `build-fonts` generates:
180
180
 
181
- - `-m, --module`: generates a CommonJS module in `app/lib/purgetss.fonts.js` (Alloy) or `Resources/lib/purgetss.fonts.js` (Classic) that exposes each icon's Unicode string to JavaScript.
181
+ - `-m, --module`: generates a CommonJS module in `app/lib/purgetss.fonts.js` (Alloy) or `Resources/lib/purgetss.fonts.js` (Classic). Its `families` object exposes the PostScript name of every processed TTF/OTF; icon CSS additionally populates `icons` with Unicode strings.
182
182
  - `-f, --font-class-from-filename`: uses the font's filename as the font class name and icon prefix instead of the font family name. Useful when you want shorter prefixes. Replaces the old `-p` flag.
183
183
 
184
184
  ### Using `--module`
@@ -190,7 +190,7 @@ $ purgetss build-fonts --module
190
190
  $ purgetss bf -m
191
191
  ```
192
192
 
193
- To avoid prefix conflicts between libraries, each icon keeps its prefix as a nested object. Both `icons` and `families` are exported:
193
+ The module is generated for text-only font collections too. By default, every TTF/OTF contributes a camel-cased PostScript-name key to `families`; with `--font-class-from-filename`, that key comes from the filename instead. Icon CSS can add a shorter family alias and a nested `icons` map. Both singular and plural export names remain available.
194
194
 
195
195
  `app/lib/purgetss.fonts.js` (Alloy) or `Resources/lib/purgetss.fonts.js` (Classic)
196
196
  ```javascript
@@ -212,17 +212,40 @@ const icons = {
212
212
  // ...
213
213
  }
214
214
  };
215
+ exports.icon = icons;
215
216
  exports.icons = icons;
216
217
 
217
218
  const families = {
219
+ 'bevanItalic': 'Bevan-Italic',
220
+ 'bevanRegular': 'Bevan-Regular',
221
+ 'dancingScriptBold': 'DancingScript-Bold',
222
+ 'dancingScriptMedium': 'DancingScript-Medium',
223
+ 'dancingScriptRegular': 'DancingScript-Regular',
224
+ 'dancingScriptSemiBold': 'DancingScript-SemiBold',
225
+ 'mapIcons': 'map-icons',
226
+ 'microns': 'microns',
218
227
  // map-icons/map-icons.css
219
228
  'mapIcon': 'map-icons',
220
229
  // microns/microns.css
221
230
  'mu': 'microns'
222
231
  };
232
+ exports.family = families;
223
233
  exports.families = families;
224
234
  ```
225
235
 
236
+ In Classic, load the generated file relative to `Resources/`:
237
+
238
+ ```javascript
239
+ const customFonts = require('lib/purgetss.fonts')
240
+
241
+ const heading = Ti.UI.createLabel({
242
+ text: 'Custom typography',
243
+ font: { fontFamily: customFonts.families.dancingScriptSemiBold, fontSize: 28 }
244
+ })
245
+ ```
246
+
247
+ See the complete [Classic module path table](./classic-projects.md#loading-generated-modules-in-classic).
248
+
226
249
  ### Using `--font-class-from-filename`
227
250
 
228
251
  When you want the class name and the icon prefix to come from the **filename** of the `.ttf` and `.css` instead of the font family, use `-f`.
@@ -282,14 +305,18 @@ const icons = {
282
305
  // ...
283
306
  }
284
307
  };
308
+ exports.icon = icons;
285
309
  exports.icons = icons;
286
310
 
287
311
  const families = {
312
+ 'map': 'map-icons',
313
+ 'mic': 'microns',
288
314
  // map-icons/mp.css
289
315
  'mp': 'map-icons',
290
316
  // microns/mc.css
291
317
  'mc': 'microns'
292
318
  };
319
+ exports.family = families;
293
320
  exports.families = families;
294
321
  ```
295
322
 
@@ -50,12 +50,14 @@ module.exports = {
50
50
  }
51
51
  },
52
52
  brand: {
53
- background: '#FFFFFF', // inherited by every piece that doesn't set its own
54
- confirmOverwrites: true, // prompt before overwriting files (set false to skip)
55
- optimize: false, // true = quantize the generated PNGs to a palette (lossy, ~71% smaller)
53
+ background: '#FFFFFF', // inherited by every piece that doesn't set its own
54
+ artworkCornerRadius: '0%', // rounded non-icon artwork: splashes, Feature Graphic and LaunchLogo
55
+ confirmOverwrites: true, // prompt before overwriting files (set false to skip)
56
+ optimize: false, // true = quantize the generated PNGs to a palette (lossy, ~71% smaller)
56
57
 
57
58
  // One block per piece. Artwork comes from purgetss/brand/logo-<piece>.{svg,png};
58
59
  // these keys are for numbers, colors and activation. Padding is never inherited.
60
+ // Only iosSplash, androidSplash, featureGraphic and launchLogo accept cornerRadius.
59
61
  icon: { padding: '0%' }, // DefaultIcon.png + DefaultIcon-ios.png
60
62
  dark: { background: null }, // DefaultIcon-Dark.png
61
63
  tinted: {}, // DefaultIcon-Tinted.png
@@ -65,7 +67,7 @@ module.exports = {
65
67
  featureGraphic: { padding: '12%' }, // MarketplaceArtworkFeature.png (1024×500)
66
68
  adaptive: { padding: '18%' }, // ic_launcher_{foreground,background,monochrome}.png × 5 + ic_launcher.xml
67
69
  legacyIcon: { padding: '10%' }, // ic_launcher.png × 5
68
- appicon: {}, // appicon.png (128×128)
70
+ appicon: { padding: '10%' }, // appicon.png (128×128)
69
71
  androidSplash: { padding: '26%' }, // assets/android/default.png + images/res-*/default.png × 11
70
72
 
71
73
  // Opt-in: inert until you edit the Android theme / FCM meta-data by hand.
@@ -73,14 +75,19 @@ module.exports = {
73
75
  notificationIcon: { enabled: false }, // drawable-*/ic_stat_notify.png × 5
74
76
  ninePatch: { enabled: false } // background.9.png (not implemented yet)
75
77
  },
78
+ // Sources in purgetss/images/ are 4x masters: a 1024px file yields
79
+ // 256 (mdpi/@1x), 384 (hdpi), 512 (xhdpi/@2x), 768 (xxhdpi/@3x), 1024 (xxxhdpi).
80
+ // There is no width to configure here — the source's own pixels decide.
81
+ // SVGs have no natural pixels; pin theirs in files: [] below.
76
82
  images: {
77
- autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files (v7.11.0)
78
- quality: 85, // JPEG/WebP/AVIF quality (0-100)
83
+ quality: 85, // webp/jpeg/avif/tiff quality (0-100); PNG and GIF ignore it
79
84
  format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
85
+ autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
80
86
  confirmOverwrites: true, // prompt before overwriting files (set false to skip)
81
- files: [] // per-file overrides: [{ filename, width, height? }] (v7.11.0)
87
+ files: [] // per-file overrides: [{ filename: 'images/<sub>/<name>.<ext>', width, height? }]
82
88
  // Note: --width (v7.8.0) and --opacity / --padding / --output (v7.10.0) are CLI-only
83
- // by design — those decisions are per-asset, not project-wide.
89
+ // by design — those decisions are per-asset, not project-wide. Since v7.17.0
90
+ // writing any of them here aborts the run instead of being ignored.
84
91
  },
85
92
  theme: {
86
93
  extend: {}
@@ -98,14 +105,17 @@ The config file has four main sections: `purge`, `brand`, `images`, and `theme`.
98
105
 
99
106
  `brand:` and `images:` configure the matching CLI commands — see [CLI Commands: `brand`](./cli-commands.md#brand-command) and [CLI Commands: `images`](./cli-commands.md#images-command) for the full option lists. The rest of this page covers `purge` and `theme`.
100
107
 
101
- For `brand`, the structure is **one block per piece of artwork** (v7.13.0), each accepting the same four keys where they apply:
108
+ For `brand`, the structure is **one block per piece of artwork** (v7.13.0), each accepting the same five keys where they apply:
102
109
 
103
110
  - `logo`: path to this piece's artwork, when it lives outside `purgetss/brand/`
104
111
  - `padding`: inset per side, as a number or a percentage string like `'19%'` — **never inherited**
112
+ - `cornerRadius`: integer or percentage string from `0` through `50`, valid only in `iosSplash`, `androidSplash`, `featureGraphic`, and `launchLogo`
105
113
  - `background`: hex color, or `null` for transparent — inherited from `brand.background`
106
114
  - `enabled`: `false` turns a default piece off, `true` turns an opt-in piece on
107
115
 
108
- Plus `brand.background`, `brand.confirmOverwrites`, `brand.optimize`, `brand.logo` (the main logo) and `brand.monochromeLogo`.
116
+ Plus `brand.background`, shared `brand.artworkCornerRadius` (default `'0%'`), optional splash-only `brand.splashCornerRadius`, `brand.confirmOverwrites`, `brand.optimize`, `brand.logo` (the main logo) and `brand.monochromeLogo`.
117
+
118
+ Use flags for temporary artwork, geometry, the shared background, selection, activation, and optimization. Keep `confirmOverwrites`, permanent `enabled` values, and exceptional per-piece backgrounds in config. For radius, a piece-specific flag wins over the splash shortcut when applicable, then `--artwork-corner-radius`, piece config, `brand.splashCornerRadius` for splashes, `brand.artworkCornerRadius`, and `0%`.
109
119
 
110
120
  > **INFO — Older `brand:` blocks update themselves**
111
121
  > A `brand:` block written for an earlier PurgeTSS is rewritten **on disk** to this structure on the next run, carrying over every value that had been customized and printing each one it moved. Both earlier shapes are recognized: the original flat keys and the v7.7.0 grouped sections (`logos` / `padding` / `android` / `ios` / `colors`). This replaced the in-memory translation used from v7.10.2 to v7.12.1. A key that belongs to no structure at all — a typo — aborts the run with the list of valid ones instead of being ignored. See [App Icons & Branding → Older configs update themselves](./app-branding.md#older-configs-update-themselves).
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **SCOPE NOTE**
4
4
  >
5
- > This reference covers dynamic component creation using **Alloy's `$.UI.create()` helper** and **`Alloy.createStyle()` + `applyProperties()`**, both combined with PurgeTSS utility classes. It is an Alloy + PurgeTSS integration guide, **not** documentation for the `purgetss.ui` native module (see [animation-system.md](./animation-system.md) and [animation-advanced.md](./animation-advanced.md) for that module).
5
+ > This reference covers dynamic component creation using **Alloy's `$.UI.create()` helper** and **`Alloy.createStyle()` + `applyProperties()`**, both combined with PurgeTSS utility classes. It is an Alloy + PurgeTSS integration guide, **not** documentation for the `purgetss.ui` native module (see [animation-system.md](./animation-system.md) and [animation-advanced.md](./animation-advanced.md) for Alloy animation usage, or [purgetss-ui-classic.md](./purgetss-ui-classic.md) for the generated Classic runtime).
6
6
  >
7
7
  > For general Alloy controller and view patterns, refer to the `alloy-guides` and `alloy-howtos` skills.
8
8
 
@@ -93,7 +93,7 @@ Each module exposes its existing icon lookup API plus a `families` object with `
93
93
  | `framework7icons` | `fontFamily` |
94
94
 
95
95
  ```javascript
96
- const fontAwesome = require('fontawesome')
96
+ const fontAwesome = require('lib/fontawesome')
97
97
 
98
98
  const home = Ti.UI.createLabel({
99
99
  text: fontAwesome.icons.home,
@@ -104,6 +104,8 @@ const home = Ti.UI.createLabel({
104
104
  })
105
105
  ```
106
106
 
107
+ All generated modules live under `Resources/lib/`, so use the pattern shown above: include `lib/` in the path and omit both `Resources/` and `.js`. See the centralized [Classic module path table](./classic-projects.md#loading-generated-modules-in-classic) for all four filenames.
108
+
107
109
  The direct alias and `families` values agree, for example `fontAwesome.solid === fontAwesome.families.solid`; `families.default` provides the library's default variant.
108
110
 
109
111
  ## Using icons in Alloy XML
@@ -61,7 +61,11 @@ When you run `purgetss` for the first time in your project, it does the followin
61
61
 
62
62
  ### 1. Auto-run Hook
63
63
 
64
- PurgeTSS adds a task in `alloy.jmk` to run `purgetss` every time you compile your app. Works well with `liveview`.
64
+ PurgeTSS adds a task in `alloy.jmk` to run `purgetss` every time you compile your app. This works well with `liveview`.
65
+
66
+ With the default synchronous method, the hook inherits PurgeTSS output so XML validation and other diagnostics appear before Alloy's generic compiler failure. It then prints a final hint to run `purgetss` from the project root and rethrows the error.
67
+
68
+ Current `init` runs update an older active or disabled hook without changing its enabled state. Invoking `watch` normally enables a disabled hook.
65
69
 
66
70
  ### 2. `purgetss` Folder
67
71
 
@@ -189,25 +189,55 @@ This is a hint, **not an error**. The legacy 4×-from-viewBox behavior still run
189
189
  On the first run, `purgetss images` injects an `images:` block into your existing `purgetss/config.cjs` (between `brand:` and `theme:`) with these defaults:
190
190
 
191
191
  ```javascript
192
+ // Sources in purgetss/images/ are 4x masters: a 1024px file yields
193
+ // 256 (mdpi/@1x), 384 (hdpi), 512 (xhdpi/@2x), 768 (xxhdpi/@3x), 1024 (xxxhdpi).
194
+ // There is no width to configure here — the source's own pixels decide.
195
+ // SVGs have no natural pixels; pin theirs in files: [] below.
192
196
  images: {
193
- autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
194
- quality: 85, // JPEG/WebP/AVIF quality (0-100)
197
+ quality: 85, // webp/jpeg/avif/tiff quality (0-100); PNG and GIF ignore it
195
198
  format: null, // null = keep original; 'webp' | 'jpeg' | 'png' to convert every image
199
+ autoSync: true, // false = SVG pipeline computes dims but doesn't write to images.files
196
200
  confirmOverwrites: true, // prompt before overwriting files (set false to skip)
197
201
  files: [] // per-file overrides: [{ filename: 'images/<sub>/<name>.<ext>', width, height? }]
198
202
  }
199
203
  ```
200
204
 
205
+ Since v7.17.0 the four comment lines above the section ship with the block itself, and the same text seeds through `init`. Reproduce the key order above when writing a config by hand — it is the order both generated copies use.
206
+
201
207
  | Key | Default | Purpose |
202
208
  | --- | --- | --- |
203
- | `autoSync` | `true` | When `false`, the SVG pipeline still computes dimensions and generates PNGs but never writes back to `images.files`. See [SVG-aware compile-time pipeline](./svg-pipeline.md). |
204
- | `quality` | `85` | Quality for lossy formats (JPEG, WebP, AVIF). Range `0–100`. |
209
+ | `quality` | `85` | Range `0–100`. Reaches `webp`, `jpeg`, `avif` and `tiff` only. PNG is written with `compressionLevel: 9` and GIF takes no quality parameter, so the value never applies to either. |
205
210
  | `format` | `null` | `null` keeps each source's original format. Set `'webp'`, `'jpeg'`, or `'png'` to convert every output. |
211
+ | `autoSync` | `true` | When `false`, the SVG pipeline still computes dimensions and generates PNGs but never writes back to `images.files`. See [SVG-aware compile-time pipeline](./svg-pipeline.md). |
206
212
  | `confirmOverwrites` | `true` | When `false`, the `[y/N/a]` prompt is skipped. |
207
213
  | `files` | `[]` | Per-file dimension overrides. See [Per-file overrides](#per-file-overrides-with-imagesfiles). |
208
214
 
209
215
  Change whatever you want to override globally; CLI flags still win for one-off runs. `autoSync` and `files` arrived in v7.11.0 to support the [SVG-aware compile-time pipeline](./svg-pipeline.md).
210
216
 
217
+ ### Unknown keys are an error (v7.17.0)
218
+
219
+ Those five keys are the entire section. Since v7.17.0 anything else aborts the run before a single file is written, the same validation `brand:` has had since v7.13.0:
220
+
221
+ ```text
222
+ > purgetss images
223
+ Error running images: Unknown key(s) in the images: section of purgetss/config.cjs:
224
+ • images.qualty
225
+ • images.files[0].widht
226
+
227
+ Top-level keys: quality, format, autoSync, confirmOverwrites, files
228
+ Inside files[]: filename, width, height
229
+
230
+ Check the spelling. No images were generated.
231
+ ```
232
+
233
+ A `qualty: 95` that gets ignored is indistinguishable from the default, so before v7.17.0 the typo produced the wrong output silently. The check runs at both levels and reports every problem in one pass:
234
+
235
+ - **Top level** — only `quality`, `format`, `autoSync`, `confirmOverwrites`, `files`.
236
+ - **Inside each `files[]` entry** — only `filename`, `width`, `height`. `filename` is **required** and must be a string; an entry without one matches no file and used to be skipped in the same silence.
237
+ - `files` itself must be an array, and each entry an object.
238
+
239
+ Do not suggest `width`, `opacity`, `padding` or `output` as `images:` keys. They are CLI flags and were never part of the section — the validator now names them as errors instead of ignoring them. When one file needs a fixed width, pin it in `files`; the other three describe a single run.
240
+
211
241
  ## Per-file overrides with `images.files`
212
242
 
213
243
  `images.files` (v7.11.0) is an array of per-file dimension pins. It does **not** filter which images get processed — `purgetss images` always walks every supported file under `purgetss/images/`. Entries in `files` only override the source and derived sizing for files whose `filename` matches.
@@ -484,7 +514,7 @@ If you only tweaked CSS classes (no image changes), you don't need to re-run `pu
484
514
  | Flag | Purpose |
485
515
  | --- | --- |
486
516
  | `--format <ext>` | Convert all outputs to: `webp`, `jpeg`, `png`, `avif`, `gif`, `tiff`. Default: keep source format. |
487
- | `--quality <n>` | Quality `0–100` for lossy formats. Default `85`. |
517
+ | `--quality <n>` | Quality `0–100` for `webp`, `jpeg`, `avif` and `tiff`. PNG and GIF ignore it. Default `85`. |
488
518
  | `--width <n>` | (v7.8.0) Pin `mdpi` / `@1x` output width to `n` pixels; `[1, 8192]`. Other densities derive from this base (×1.5 / ×2 / ×3 / ×4). Most useful for SVG sources with non-standard viewBoxes. CLI-only — no `config.cjs` equivalent because width is per-asset. |
489
519
  | `--opacity <n>` | (v7.10.0) Multiply the alpha channel of every generated density by `n/100`. Range `[0, 100]`. Combine with `--format jpeg` and the alpha is flattened on white instead of producing a transparent JPEG. CLI-only. |
490
520
  | `--padding <n>` | (v7.10.0) Shrink the rendered image inside each density canvas by `n%` symmetric borders. Range `[0, 40]`. CLI-only. |
@@ -0,0 +1,341 @@
1
+ # `purgetss.ui` in Titanium Classic
2
+
3
+ Titanium Classic uses the same generated `purgetss.ui` runtime as Alloy, but it does not compile Alloy XML, TSS, utility classes, controllers, or `$.*` references. Configure the runtime with JavaScript objects containing native Titanium properties.
4
+
5
+ <!-- TOC-START -->
6
+ ## Contents
7
+
8
+ - [Install and load](#install-and-load)
9
+ - [Public exports](#public-exports)
10
+ - [Quick start](#quick-start)
11
+ - [Animation method contract](#animation-method-contract)
12
+ - [Native animation configuration](#native-animation-configuration)
13
+ - [States and direct children](#states-and-direct-children)
14
+ - [Position helpers](#position-helpers)
15
+ - [Drag and collision configuration](#drag-and-collision-configuration)
16
+ - [Drag lifecycle and cleanup](#drag-lifecycle-and-cleanup)
17
+ - [Layout transitions](#layout-transitions)
18
+ - [Appearance in Classic](#appearance-in-classic)
19
+ - [Runtime utilities](#runtime-utilities)
20
+ - [Related references](#related-references)
21
+
22
+ <!-- TOC-END -->
23
+
24
+ ## Install and load
25
+
26
+ Run this from the Classic project root:
27
+
28
+ ```bash
29
+ purgetss module
30
+ ```
31
+
32
+ The command creates `Resources/lib/purgetss.ui.js`. Load it from a file such as `Resources/app.js`:
33
+
34
+ ```javascript
35
+ const {
36
+ createAnimation,
37
+ Appearance,
38
+ deviceInfo,
39
+ saveComponent
40
+ } = require('lib/purgetss.ui')
41
+ ```
42
+
43
+ Use `createAnimation(args)` for ordinary Classic code. It returns a zero-size, touch-disabled `Ti.UI.View` decorated with animation methods. Treat this view as a behavior object; do not add it to the window.
44
+
45
+ ## Public exports
46
+
47
+ | Export | Type and return | Contract |
48
+ | --- | --- | --- |
49
+ | `AnimationProperties` | Constructor returning the decorated animation view | Public, but less clear than the factory in Classic |
50
+ | `createAnimation(args)` | Factory returning the decorated animation view | Preferred Classic entry point |
51
+ | `Appearance` | Singleton object | `init()`, `set()`, `get()`, `toggle()` |
52
+ | `deviceInfo()` | Function returning `undefined` | Logs platform and display information |
53
+ | `saveComponent({ source, directory? })` | Function returning `undefined` | Writes a PNG and invokes the photo-gallery API |
54
+
55
+ ## Quick start
56
+
57
+ ```javascript
58
+ const { createAnimation, Appearance } = require('lib/purgetss.ui')
59
+
60
+ Appearance.init()
61
+
62
+ const window = Ti.UI.createWindow({ backgroundColor: 'surfaceColor' })
63
+ const card = Ti.UI.createView({
64
+ width: 220,
65
+ height: 120,
66
+ backgroundColor: 'accentColor',
67
+ opacity: 0,
68
+ transform: Ti.UI.createMatrix2D().scale(0.92)
69
+ })
70
+ const motion = createAnimation({
71
+ duration: 220,
72
+ curve: Ti.UI.ANIMATION_CURVE_EASE_OUT,
73
+ animationProperties: {
74
+ open: { opacity: 1, scale: 1 },
75
+ close: { opacity: 0, scale: 0.92 }
76
+ }
77
+ })
78
+
79
+ function onCardClick() { motion.play(card) }
80
+ function onWindowOpen() { motion.open(card) }
81
+ function disposeWindow() {
82
+ card.removeEventListener('click', onCardClick)
83
+ window.removeEventListener('open', onWindowOpen)
84
+ window.removeEventListener('close', disposeWindow)
85
+ }
86
+
87
+ card.addEventListener('click', onCardClick)
88
+ window.add(card)
89
+ window.addEventListener('open', onWindowOpen)
90
+ window.addEventListener('close', disposeWindow)
91
+ window.open()
92
+ ```
93
+
94
+ `surfaceColor` and `accentColor` are names from `Resources/semantic.colors.json`; see [appearance-module.md#titanium-classic](./appearance-module.md#titanium-classic).
95
+
96
+ ## Animation method contract
97
+
98
+ | Method | Signature | Return and callback |
99
+ | --- | --- | --- |
100
+ | `play` | `play(viewOrViews, callback?)` | `undefined`; callback once per view after its base animation completes |
101
+ | `toggle` | `toggle(viewOrViews, callback?)` | Exact alias of `play` |
102
+ | `apply` | `apply(viewOrViews, callback?)` | `undefined`; synchronous callback once per view |
103
+ | `open` | `open(viewOrViews, callback?)` | `undefined`; forces open state, callback once per view |
104
+ | `close` | `close(viewOrViews, callback?)` | `undefined`; forces close state, callback once per view |
105
+ | `draggable` | `draggable(viewOrViews)` | `undefined` |
106
+ | `undraggable` | `undraggable(viewOrViews)` | `undefined` |
107
+ | `detectCollisions` | `detectCollisions(views, dragCallback?, dropCallback?)` | `undefined` |
108
+ | `sequence` | `sequence(viewOrViews, callback?)` | `undefined`; callback once after the final view |
109
+ | `swap` | `swap(view1, view2)` | `undefined` |
110
+ | `pulse` | `pulse(view, count = 1)` | `undefined` |
111
+ | `shake` | `shake(view, intensity = 10)` | `undefined` |
112
+ | `snapTo` | `snapTo(view, targets)` | Selected target, `null` if none qualifies, or `undefined` for a missing source |
113
+ | `reorder` | `reorder(views, newOrder)` | `undefined` |
114
+ | `transition` | `transition(viewOrViews, layouts)` | `undefined` |
115
+
116
+ `play`, `toggle`, `apply`, `open`, and `close` accept one view or an array. The array callback runs once per view with:
117
+
118
+ - `type`: native completion type, or `'applied'` for `apply`.
119
+ - `bubbles` and `cancelBubble`: selected native primitives.
120
+ - `action`: `'play'` or `'apply'`.
121
+ - `state`: `'open'` or `'close'`.
122
+ - `id` and `targetId`.
123
+ - `index` and `total`.
124
+ - `getTarget()`: returns the affected view.
125
+
126
+ This is a newly constructed object, not the original Titanium event. For array playback, the configured base delay accumulates for successive views. `sequence()` instead waits for each native completion and invokes its callback once after the last view. Passing an empty array to `sequence()` produces no callback.
127
+
128
+ `animationProperties.complete` starts after the active `play`, `toggle`, `open`, or `close` base phase, and is applied immediately after `apply`. The public play callback does not wait for the second top-level `complete` animation.
129
+
130
+ ## Native animation configuration
131
+
132
+ Pass native `Ti.UI.Animation` values directly:
133
+
134
+ ```javascript
135
+ const { createAnimation } = require('lib/purgetss.ui')
136
+
137
+ const motion = createAnimation({
138
+ id: 'cardMotion',
139
+ duration: 240,
140
+ delay: 40,
141
+ curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT,
142
+ repeat: 1,
143
+ autoreverse: false,
144
+ opacity: 0.8,
145
+ backgroundColor: '#2563eb',
146
+ scale: 1.05,
147
+ rotate: 4
148
+ })
149
+ ```
150
+
151
+ The constructor removes `id` from the native animation object. It converts top-level `scale`, `rotate`, and `anchorPoint` into one `Ti.UI.Matrix2D`; it performs the same conversion separately for `animationProperties.open` and `.close`. Helpers such as `pulse`, `shake`, `swap`, `snapTo`, `reorder`, and `transition` can replace or reset that transform.
152
+
153
+ Most helpers spread the base object before their method-specific assignments, so later helper fields win. There is no shared `200ms` timing fallback for position/layout helpers. Only `shake()` computes `(duration ?? 400) / 6`, rounded to the nearest millisecond; `pulse()` supplies a default scale of `1.2`. Both force `ANIMATION_CURVE_EASE_IN_OUT`.
154
+
155
+ Titanium exposes `anchorPoint` differently across platforms. Test pivot-dependent animation on both platforms; in Classic, set the view's iOS anchor point before animation and pass an Android animation anchor point only inside a platform guard.
156
+
157
+ ## States and direct children
158
+
159
+ ```javascript
160
+ const panelMotion = createAnimation({
161
+ duration: 220,
162
+ animationProperties: {
163
+ open: { opacity: 1, scale: 1 },
164
+ close: { opacity: 0, scale: 0.94 },
165
+ complete: { borderColor: '#22c55e' },
166
+ children: { duration: 160 }
167
+ }
168
+ })
169
+
170
+ titleLabel.animationProperties = {
171
+ child: { delay: 40 },
172
+ open: { opacity: 1, top: 16 },
173
+ close: { opacity: 0, top: 24 },
174
+ complete: { color: '#22c55e' }
175
+ }
176
+
177
+ panelMotion.open(panel)
178
+ ```
179
+
180
+ For a direct child, merge precedence is:
181
+
182
+ 1. Parent `animationProperties.children`.
183
+ 2. Child `animationProperties.child`.
184
+ 3. Child's active `open`, `close`, or `complete` object.
185
+
186
+ Later values win. Nested descendants are not traversed automatically.
187
+
188
+ ## Position helpers
189
+
190
+ - `swap()` exchanges two rendered positions, temporarily promotes both views, persists `top`/`left`, and updates private drag origins. It restores stacking from the current draggable registry order, not arbitrary original z-index values.
191
+ - `snapTo()` chooses the nearest target by center distance and returns it immediately. Views must be laid out because it reads `rect`.
192
+ - `reorder()` maps each view to the captured position at `newOrder[index]`. A length mismatch returns without changes.
193
+ - `pulse()` forces autoreverse, the supplied repeat count, and ease-in-out, then resets the transform.
194
+ - `shake()` starts at `-intensity`, animates to `+intensity` through six short phases, and resets the transform.
195
+
196
+ These methods do not expose completion callbacks. Configure duration, delay, and curve on the object passed to `createAnimation()`.
197
+
198
+ ## Drag and collision configuration
199
+
200
+ ```javascript
201
+ const dragMotion = createAnimation({
202
+ duration: 160,
203
+ bounds: { top: 12, right: 12, bottom: 12, left: 12 },
204
+ draggingType: 'animate',
205
+ draggable: {
206
+ drag: { opacity: 0.7, scale: 1.04 },
207
+ drop: { opacity: 1, scale: 1 }
208
+ },
209
+ animationProperties: {
210
+ keepZIndex: true,
211
+ snap: { back: true, center: true }
212
+ }
213
+ })
214
+
215
+ piece.bounds = { bottom: 40 }
216
+ piece.constraint = 'horizontal'
217
+ piece.draggingType = 'apply'
218
+ piece.draggable = {
219
+ drag: { opacity: 0.5 },
220
+ drop: { opacity: 1 }
221
+ }
222
+
223
+ dragMotion.draggable(piece)
224
+ dragMotion.detectCollisions([piece, target], (source, hovered) => {
225
+ target.borderColor = hovered === target ? '#22c55e' : 'transparent'
226
+ }, (source, droppedOn) => {
227
+ Ti.API.info(`${source.id} dropped on ${droppedOn.id}`)
228
+ })
229
+ ```
230
+
231
+ Runtime precedence and behavior:
232
+
233
+ - Global bounds are the base; `view.bounds` overrides individual edges.
234
+ - Axis restriction comes from `view.constraint`; a constructor-level constraint is not read.
235
+ - Per-view `draggingType` overrides the constructor value.
236
+ - Global and per-view `draggable.drag/drop` objects are merged; view fields win.
237
+ - Every resolved drag/drop property is forwarded, including size, transform, and anchor-point fields.
238
+ - `snap.center` calls `snapTo()` after a valid drop; `snap.back` returns a missed drop to its origin.
239
+ - There is no `snap.magnet` behavior.
240
+ - Collision detection tests the dragged view's center against registered targets. The last non-null hover target is a fallback at release.
241
+
242
+ Call drag/collision setup after the views are attached and laid out. `draggable(array)` assigns each view `zIndex` from its array index before registration. `keepZIndex` only prevents later touch-start promotion; register views individually if their existing z-index values must not be overwritten.
243
+
244
+ ## Drag lifecycle and cleanup
245
+
246
+ Register each view once. The current runtime does not deduplicate repeated `draggable()` calls: they create additional listeners and registry entries. One `undraggable()` call removes only the latest stored listener set and the first matching registry entries.
247
+
248
+ `undraggable()` removes the stored `touchstart`, `touchmove`, `touchend`, and global `orientationchange` listeners and most private position/collision fields, but it leaves `_wasDragged`. `detectCollisions()` retains the last non-null callbacks on the animation object; passing `null` does not clear them.
249
+
250
+ ```javascript
251
+ let dragReady = false
252
+
253
+ function enableDrag() {
254
+ if (dragReady) return
255
+ dragReady = true
256
+ dragMotion.draggable(piece)
257
+ dragMotion.detectCollisions([piece, target])
258
+ }
259
+
260
+ function disposeWindow() {
261
+ dragMotion.undraggable([piece, target])
262
+ window.removeEventListener('open', enableDrag)
263
+ window.removeEventListener('close', disposeWindow)
264
+ dragReady = false
265
+ }
266
+
267
+ window.addEventListener('open', enableDrag)
268
+ window.addEventListener('close', disposeWindow)
269
+ ```
270
+
271
+ Release view, callback, and animation references after close. Application code should not depend on private underscore fields.
272
+
273
+ ## Layout transitions
274
+
275
+ `transition(views, layouts)` accepts these layout fields:
276
+
277
+ | Field | Default | Behavior |
278
+ | --- | --- | --- |
279
+ | `translation` | `{ x: 0, y: 0 }` | Matrix translation |
280
+ | `rotate` | `0` | Matrix rotation in degrees |
281
+ | `scale` | `1` | Matrix scale |
282
+ | `zIndex` | unchanged | Assigned before animation |
283
+ | `width`, `height` | unchanged | Optional size animation |
284
+ | `opacity` | unchanged | Optional opacity animation |
285
+
286
+ ```javascript
287
+ const layoutMotion = createAnimation({
288
+ duration: 220,
289
+ curve: Ti.UI.ANIMATION_CURVE_EASE_IN_OUT
290
+ })
291
+ const fan = [
292
+ { translation: { x: -120, y: 20 }, rotate: -15, scale: 0.8, zIndex: 1 },
293
+ { translation: { x: 0, y: 0 }, rotate: 0, scale: 1, zIndex: 3 },
294
+ { translation: { x: 120, y: 20 }, rotate: 15, scale: 0.8, zIndex: 2 }
295
+ ]
296
+
297
+ layoutMotion.transition([cardA, cardB, cardC], fan)
298
+ ```
299
+
300
+ A view without a matching layout fades to opacity `0`, receives `zIndex: 0`, and has touch disabled. Extra layouts are ignored. A later matching layout fades a hidden view in and reenables touch. iOS preserves the last transform while hidden; Android resets transform, translation, rotation, and scale. Completion forces `touchEnabled: true` for every matched view, so reapply `false` afterward when a view must remain non-interactive.
301
+
302
+ On Mac Catalyst, use a fixed-size parent rather than `Ti.UI.FILL` for rotated layouts; a resizable parent can distort the matrix.
303
+
304
+ ## Appearance in Classic
305
+
306
+ Put semantic colors in `Resources/semantic.colors.json`, use their names directly in native color properties, and call `Appearance.init()` before opening the first window. Classic does not need utility classes, TSS, `purgetss/config.cjs`, or Alloy runtime files. Run a full native build after adding or changing semantic keys; LiveView alone does not rebuild the native catalog.
307
+
308
+ See [appearance-module.md#titanium-classic](./appearance-module.md#titanium-classic) for setup and all four method contracts.
309
+
310
+ ## Runtime utilities
311
+
312
+ ```javascript
313
+ const { deviceInfo, saveComponent } = require('lib/purgetss.ui')
314
+
315
+ deviceInfo()
316
+
317
+ function savePreview() {
318
+ if (!Ti.Media.hasPhotoGalleryPermissions()) {
319
+ Ti.Media.requestPhotoGalleryPermissions(event => {
320
+ if (event.success) savePreview()
321
+ })
322
+ return
323
+ }
324
+
325
+ saveComponent({
326
+ source: preview,
327
+ directory: Ti.Filesystem.applicationDataDirectory
328
+ })
329
+ }
330
+ ```
331
+
332
+ `deviceInfo()` logs values and returns `undefined`. It logs `xdpi` and `ydpi` only on Android. Its tablet flag recognizes iPad only, so Android tablets are currently logged as `isTablet: false` and `isHandheld: true`.
333
+
334
+ `saveComponent()` calls `source.toImage()`, derives an MD5-based `.png` filename, writes it to `directory` (default: `Ti.Filesystem.tempDirectory`), then invokes `Ti.Media.saveToPhotoGallery()`. It returns `undefined` and provides no completion or error callback. Check/request gallery permission before calling it; on iOS, declare the required photo-library add usage description.
335
+
336
+ ## Related references
337
+
338
+ - [animation-system.md](./animation-system.md) — Alloy syntax and all animation methods.
339
+ - [animation-advanced.md](./animation-advanced.md) — generated timing classes and runtime rules.
340
+ - [appearance-module.md](./appearance-module.md) — Appearance workflow and API.
341
+ - [classic-projects.md](./classic-projects.md) — commands and capabilities available to Classic projects.
@@ -8,6 +8,23 @@ When in doubt about whether a class, flag, or config key exists in the user's in
8
8
 
9
9
  ---
10
10
 
11
+ ## v7.17.0
12
+ - The `images:` section rejects unknown keys instead of ignoring them, at both levels: the five top-level keys (`quality`, `format`, `autoSync`, `confirmOverwrites`, `files`) and the three inside each `files[]` entry (`filename`, `width`, `height`, with `filename` required). Nothing is generated when validation fails. Never suggest `width`, `opacity`, `padding` or `output` as config keys — they are CLI flags and now abort the run. See [multi-density-images.md](multi-density-images.md).
13
+ - The generated `images:` block gained four comment lines explaining the 4× master convention, and the `quality` comment now names the formats it reaches: `webp`, `jpeg`, `avif`, `tiff`. PNG uses `compressionLevel: 9` and GIF takes no quality parameter, so neither is affected. Both copies changed — the one patched into an existing config and the one `init` seeds.
14
+ - `install-dependencies` and `create --dependencies` now install only `eslint` and `@eslint/js` and copy a flat `eslint.config.mjs`. `eslint-config-axway` and `eslint-plugin-alloy` are gone: the old `.eslintrc.js` could not run under ESLint 9. See [cli-commands.md](cli-commands.md#install-dependencies-command).
15
+
16
+ ## v7.16.2
17
+ - `build-fonts --module` now exports every processed TTF/OTF PostScript name through `families`, including text-only collections; icon CSS additionally populates `icons`. Filename-derived keys such as `poppinsSemiBold` are supported. See [custom-fonts.md](custom-fonts.md).
18
+ - In clean Classic projects, saved `shades` palettes and `color-module` no longer scaffold empty brand, font, or image source folders. `shades` still refreshes an existing generated color module. See [classic-projects.md](classic-projects.md).
19
+
20
+ ## v7.16.1
21
+ - Alloy auto-purge hooks now expose PurgeTSS output before Alloy's generic failure, print a command-line diagnostic hint, and migrate older active or disabled hooks without losing their state. See [installation-setup.md](installation-setup.md).
22
+
23
+ ## v7.16.0
24
+ - `brand.artworkCornerRadius` and optional `brand.splashCornerRadius` add shared rounding for non-icon artwork; the iOS/Android splash, Feature Graphic, and LaunchLogo pieces also accept `cornerRadius`. Values are 0–50, and unsupported pieces reject the key before writing. Platform-masked and store icon outputs remain unrounded. See [app-branding.md](app-branding.md).
25
+ - Six CLI radius flags provide shared, splash-only, and per-piece one-run overrides. `--appicon-padding` completes the existing `brand.appicon.padding` contract, whose canonical default is `10%`.
26
+ - Branding summaries and `--dry-run` now report effective padding and corner radius for rounded pieces.
27
+
11
28
  ## v7.15.0
12
29
  - Standalone `images`, `semantic`, `shades`, `color-module`, `module`, `icon-library`, and `build-fonts` now support Classic without installing the Alloy hook or utility-class lifecycle. Outputs route to native `Resources/` paths where appropriate. See [classic-projects.md](classic-projects.md).
13
30
  - `images` follows `<deployment-targets>` from `tiapp.xml` by default; explicit `--android` / `--ios` override that selection.