purgetss 7.5.3 → 7.6.1

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 (47) hide show
  1. package/README.md +38 -17
  2. package/bin/purgetss +140 -1
  3. package/dist/purgetss.ui.js +23 -26
  4. package/dist/utilities.tss +13 -1
  5. package/lib/completions/titanium/completions-v3.json +62 -1
  6. package/lib/templates/purgetss.config.js.cjs +15 -1
  7. package/lib/templates/purgetss.ui.js.cjs +22 -25
  8. package/package.json +3 -1
  9. package/src/cli/commands/brand.js +69 -0
  10. package/src/cli/commands/create.js +11 -7
  11. package/src/cli/commands/fonts.js +9 -9
  12. package/src/cli/commands/icon-library.js +18 -16
  13. package/src/cli/commands/images.js +116 -0
  14. package/src/cli/commands/init.js +4 -0
  15. package/src/cli/commands/module.js +4 -2
  16. package/src/cli/commands/purge.js +48 -98
  17. package/src/cli/commands/semantic.js +180 -0
  18. package/src/cli/commands/shades.js +332 -13
  19. package/src/cli/utils/project-detection.js +4 -2
  20. package/src/core/analyzers/class-extractor.js +110 -3
  21. package/src/core/branding/brand-config.js +111 -0
  22. package/src/core/branding/branding-logger.js +40 -0
  23. package/src/core/branding/cleanup-legacy.js +220 -0
  24. package/src/core/branding/ensure-brand-section.js +80 -0
  25. package/src/core/branding/gen-android-adaptive.js +116 -0
  26. package/src/core/branding/gen-android-legacy.js +63 -0
  27. package/src/core/branding/gen-ic-launcher-xml.js +29 -0
  28. package/src/core/branding/gen-ios-dark.js +70 -0
  29. package/src/core/branding/gen-ios-tinted.js +55 -0
  30. package/src/core/branding/gen-ios.js +69 -0
  31. package/src/core/branding/gen-marketplace.js +71 -0
  32. package/src/core/branding/gen-notification.js +76 -0
  33. package/src/core/branding/gen-splash.js +64 -0
  34. package/src/core/branding/index.js +336 -0
  35. package/src/core/branding/post-gen-notes.js +145 -0
  36. package/src/core/branding/prepare-master.js +108 -0
  37. package/src/core/branding/tiapp-reader.js +110 -0
  38. package/src/core/images/ensure-images-section.js +57 -0
  39. package/src/core/images/gen-scales.js +181 -0
  40. package/src/core/images/index.js +171 -0
  41. package/src/shared/config-manager.js +46 -0
  42. package/src/shared/config-writer.js +84 -0
  43. package/src/shared/constants.js +3 -0
  44. package/src/shared/logger.js +69 -4
  45. package/src/shared/prompt.js +64 -0
  46. package/src/shared/svg-utils.js +80 -0
  47. package/src/shared/utils.js +8 -4
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  </div>
12
12
 
13
- **PurgeTSS** is a toolkit for building mobile apps with the [Titanium framework](https://titaniumsdk.com). It provides utility classes, icon font support, an Animation module, a grid system, and the `shades` command for generating custom colors.
13
+ **PurgeTSS** is a toolkit for building mobile apps with the [Titanium framework](https://titaniumsdk.com). It provides utility classes, icon font support, an Animation module, a grid system, and color generation commands (`shades` for tonal palettes, `semantic` for Light/Dark mode semantic colors).
14
14
 
15
15
  ---
16
16
 
@@ -20,6 +20,7 @@
20
20
  - Icon font support: Font Awesome, Material Icons, Material Symbols, Framework7-Icons
21
21
  - `build-fonts` command generates `fonts.tss` with class definitions and fontFamily selectors
22
22
  - `shades` command generates color shades from any hex color
23
+ - `semantic` command generates Titanium semantic colors (Light/Dark mode) — tonal palettes with mirror inversion, or single purpose-based colors with optional alpha
23
24
  - Animation module for 2D matrix animations on views or arrays of views
24
25
  - Grid system for aligning and distributing elements within views
25
26
 
@@ -268,17 +269,29 @@ function selectLight() { Appearance.set('light') }
268
269
  function selectSystem() { Appearance.set('system') }
269
270
  ```
270
271
 
271
- Requires `semantic.colors.json` in `app/assets/` for views to respond to mode changes. See the [Titanium docs on semantic colors](https://titaniumsdk.com/guide/Titanium_SDK/Titanium_SDK_How-tos/User_Interface_Deep_Dives/iOS_Dark_Mode.html) for the file format.
272
+ Requires `semantic.colors.json` in `app/assets/` for views to respond to mode changes. Generate it with the `semantic` command instead of writing it by hand:
273
+
274
+ ```bash
275
+ # Tonal palette (11 shades with mirror-by-index Light/Dark inversion)
276
+ purgetss semantic '#15803d' amazon
277
+
278
+ # Purpose-based single colors (auto-mapped to classes in config.cjs)
279
+ purgetss semantic --single '#F9FAFB' surfaceColor --dark '#0f172a'
280
+ purgetss semantic --single '#111827' textColor --dark '#f1f5f9'
281
+ purgetss semantic --single '#000000' overlayColor --alpha 50
282
+ ```
283
+
284
+ See the [Semantic Colors guide](https://purgetss.com/docs/best-practices/semantic-colors) for the full workflow.
272
285
 
273
286
  ### Default font families
274
287
 
275
288
  PurgeTSS generates `font-sans`, `font-serif`, and `font-mono` classes automatically with platform-appropriate values:
276
289
 
277
- | Class | iOS | Android |
278
- | ------------ | ------------------ | ------------- |
279
- | `font-sans` | `Helvetica Neue` | `sans-serif` |
280
- | `font-serif` | `Georgia` | `serif` |
281
- | `font-mono` | `monospace` | `monospace` |
290
+ | Class | iOS | Android |
291
+ | ------------ | ---------------- | ------------ |
292
+ | `font-sans` | `Helvetica Neue` | `sans-serif` |
293
+ | `font-serif` | `Georgia` | `serif` |
294
+ | `font-mono` | `monospace` | `monospace` |
282
295
 
283
296
  Override or add families in `config.cjs`:
284
297
 
@@ -365,6 +378,9 @@ Button: {
365
378
 
366
379
  - [Installation](https://purgetss.com/docs/installation)
367
380
  - [Commands](https://purgetss.com/docs/commands)
381
+ - App Assets
382
+ - [App icons and branding](https://purgetss.com/docs/app-assets/app-icons-and-branding)
383
+ - [Multi-density images](https://purgetss.com/docs/app-assets/multi-density-images)
368
384
  - Customization
369
385
  - [The Config File](https://purgetss.com/docs/customization/the-config-file)
370
386
  - [Custom Rules](https://purgetss.com/docs/customization/custom-rules)
@@ -373,14 +389,19 @@ Button: {
373
389
  - [Arbitrary Values](https://purgetss.com/docs/customization/arbitrary-values)
374
390
  - [Platform and Device Modifiers](https://purgetss.com/docs/customization/platform-and-device-modifiers)
375
391
  - [Icon Fonts Libraries](https://purgetss.com/docs/customization/icon-fonts-libraries)
376
- - Animation Module
377
- - [Introduction](https://purgetss.com/docs/animation-module/introduction)
378
- - [The `play` Method](https://purgetss.com/docs/animation-module/the-play-method)
379
- - [The `apply` Method](https://purgetss.com/docs/animation-module/the-apply-method)
380
- - [The `open` and `close` Methods](https://purgetss.com/docs/animation-module/the-open-and-close-methods)
381
- - [The `draggable` Method](https://purgetss.com/docs/animation-module/the-draggable-method)
382
- - [Complex UI Elements](https://purgetss.com/docs/animation-module/complex-ui-elements)
383
- - [Additional Methods](https://purgetss.com/docs/animation-module/additional-methods)
384
- - [Available Utilities](https://purgetss.com/docs/animation-module/available-utilities)
385
- - [Implementation Rules](https://purgetss.com/docs/animation-module/implementation-rules)
392
+ - The UI Module
393
+ - [Introduction](https://purgetss.com/docs/purgetss-ui/introduction)
394
+ - [The `play` Method](https://purgetss.com/docs/purgetss-ui/the-play-method)
395
+ - [The `apply` Method](https://purgetss.com/docs/purgetss-ui/the-apply-method)
396
+ - [The `open` and `close` Methods](https://purgetss.com/docs/purgetss-ui/the-open-and-close-methods)
397
+ - [The `draggable` Method](https://purgetss.com/docs/purgetss-ui/the-draggable-method)
398
+ - [Complex UI Elements](https://purgetss.com/docs/purgetss-ui/complex-ui-elements)
399
+ - [Additional Methods](https://purgetss.com/docs/purgetss-ui/additional-methods)
400
+ - [Available Utilities](https://purgetss.com/docs/purgetss-ui/available-utilities)
401
+ - [Implementation Rules](https://purgetss.com/docs/purgetss-ui/implementation-rules)
402
+ - [Appearance](https://purgetss.com/docs/purgetss-ui/appearance)
403
+ - Best Practices
404
+ - [Appearance Setup](https://purgetss.com/docs/best-practices/appearance-setup)
405
+ - [Semantic Colors](https://purgetss.com/docs/best-practices/semantic-colors)
406
+ - [Large Titles on iOS](https://purgetss.com/docs/best-practices/large-titles-on-ios)
386
407
  - [Grid System](https://purgetss.com/docs/grid-system)
package/bin/purgetss CHANGED
@@ -17,8 +17,11 @@ import { copyModulesLibrary } from '../src/cli/commands/module.js'
17
17
  import { dependencies } from '../src/cli/commands/dependencies.js'
18
18
  import { init } from '../src/cli/commands/init.js'
19
19
  import { shades, colorModule } from '../src/cli/commands/shades.js'
20
+ import { semantic } from '../src/cli/commands/semantic.js'
20
21
  import { buildFonts } from '../src/cli/commands/fonts.js'
21
22
  import { copyFonts } from '../src/cli/commands/icon-library.js'
23
+ import { brand } from '../src/cli/commands/brand.js'
24
+ import { images } from '../src/cli/commands/images.js'
22
25
 
23
26
  // ESM equivalent of __dirname
24
27
  const __filename = fileURLToPath(import.meta.url)
@@ -113,6 +116,8 @@ program.helpInformation = function () {
113
116
  output.push(` ${chalk.cyan('init|i')} ${chalk.gray('[options]')} Create a ${chalk.yellow('config.cjs')} file for your project`)
114
117
  output.push(` ${chalk.cyan('create|c')} ${chalk.gray('[options]')} ${chalk.gray('<name>')} Create a new Alloy project with ${chalk.green('PurgeTSS')}`)
115
118
  output.push(` ${chalk.cyan('install-dependencies|id')} Install ${chalk.green('ESLint')} and ${chalk.green('Tailwind CSS')}`)
119
+ output.push(` ${chalk.cyan('brand')} ${chalk.gray('[options]')} ${chalk.gray('<logo>')} Generate Titanium app icons & splash from a logo SVG/PNG`)
120
+ output.push(` ${chalk.cyan('images')} ${chalk.gray('[options]')} ${chalk.gray('[source]')} Generate Titanium multi-density UI images (Alloy + Classic)`)
116
121
  output.push('')
117
122
  output.push(` ${chalk.green('Daily Development:')}`)
118
123
  output.push(` ${chalk.cyan('build|b')} ${chalk.gray('[options]')} Generate ${chalk.yellow('utilities.tss')} file`)
@@ -126,6 +131,7 @@ program.helpInformation = function () {
126
131
  output.push('')
127
132
  output.push(` ${chalk.green('Utilities:')}`)
128
133
  output.push(` ${chalk.cyan('shades|s')} ${chalk.gray('[options]')} ${chalk.gray('[hexcode]')} Generate color shades from a hex color`)
134
+ output.push(` ${chalk.cyan('semantic')} ${chalk.gray('[options]')} ${chalk.gray('[hexcode]')} Generate Titanium semantic colors (Light/Dark)`)
129
135
  output.push('')
130
136
  output.push(` ${chalk.green('Maintenance:')}`)
131
137
  output.push(` ${chalk.cyan('update|u')} Update ${chalk.green('PurgeTSS')} to the latest version`)
@@ -248,6 +254,114 @@ program
248
254
  }
249
255
  })
250
256
 
257
+ // Brand command — generate Titanium app icons and splash assets
258
+ program
259
+ .command('brand [logo]')
260
+ .description(`Generate Titanium app icons & branding assets from a logo SVG/PNG`)
261
+ .addHelpText('after', `
262
+ Writes directly into the current project (Alloy or Classic — auto-detected).
263
+ Logos auto-discovered from ${chalk.cyan('purgetss/brand/')}:
264
+ ${chalk.yellow('logo.{svg,png}')} required — main logo
265
+ ${chalk.yellow('logo-mono.{svg,png}')} optional — monochrome layer + notifications
266
+ ${chalk.yellow('logo-dark.{svg,png}')} optional — iOS 18+ dark variant
267
+ ${chalk.yellow('logo-tinted.{svg,png}')} optional — iOS 18+ tinted variant
268
+
269
+ Defaults come from the ${chalk.cyan('brand:')} section in ${chalk.cyan('purgetss/config.cjs')}.
270
+ CLI flags always win over config values.
271
+
272
+ Generates:
273
+ ${chalk.yellow('DefaultIcon.png')} / ${chalk.yellow('DefaultIcon-ios.png')} Root icons (alpha + flattened)
274
+ ${chalk.yellow('DefaultIcon-Dark.png')} / ${chalk.yellow('DefaultIcon-Tinted.png')} iOS 18+ variants (Apple HIG specs)
275
+ ${chalk.yellow('iTunesConnect.png')} / ${chalk.yellow('MarketplaceArtwork.png')} App Store + Play Store artwork
276
+ ${chalk.yellow('mipmap-*/ic_launcher_{foreground,background,monochrome}.png')} Android adaptive × 5
277
+ ${chalk.yellow('mipmap-*/ic_launcher.png')} Android legacy × 5
278
+ ${chalk.yellow('mipmap-anydpi-v26/ic_launcher.xml')} Adaptive icon binder
279
+
280
+ Android dark/light mode is handled by the ${chalk.yellow('monochrome')} adaptive layer
281
+ (Android 13+ tints it from the wallpaper + theme). No separate dark file exists.
282
+
283
+ Examples:
284
+ ${chalk.cyan('purgetss brand')} # uses purgetss/brand/logo.svg + config
285
+ ${chalk.cyan('purgetss brand')} logo.svg # explicit logo path
286
+ ${chalk.cyan('purgetss brand')} --bg-color "#0B1326" # override config bg
287
+ ${chalk.cyan('purgetss brand')} --dark-bg-color "#1C1C1E" --no-tinted # customize dark, skip tinted
288
+ ${chalk.cyan('purgetss brand')} --dry-run # preview without writing
289
+ ${chalk.cyan('purgetss brand')} --cleanup-legacy --dry-run # preview legacy cleanup
290
+ `)
291
+ .option('--bg-color <hex>', 'Background color for Android adaptive + iOS flatten (default: #FFFFFF)')
292
+ .option('--padding <n>', 'Android safe-zone % (range 12-20, default: 15)', (v) => parseInt(v, 10))
293
+ .option('--ios-padding <n>', 'iOS aesthetic % (typical 2-6, default: 4)', (v) => parseInt(v, 10))
294
+ .option('--notification', 'Also generate ic_stat_notify.png × 5 densities')
295
+ .option('--splash', 'Also generate Android 12+ splash_icon.png × 5 densities')
296
+ .option('--monochrome-logo <path>', 'Override the monochrome logo (otherwise purgetss/brand/logo-mono.{svg,png})')
297
+ .option('--dark-bg-color <hex>', 'Opt into opaque dark bg for DefaultIcon-Dark.png (default: transparent per Apple HIG)')
298
+ .option('--dark-logo <path>', 'Override the dark logo (otherwise purgetss/brand/logo-dark.{svg,png})')
299
+ .option('--tinted-logo <path>', 'Override the tinted logo (otherwise purgetss/brand/logo-tinted.{svg,png})')
300
+ .option('--no-dark', 'Skip DefaultIcon-Dark.png generation')
301
+ .option('--no-tinted', 'Skip DefaultIcon-Tinted.png generation')
302
+ .option('--project <path>', 'Project root (default: cwd)')
303
+ .option('-o, --output <dir>', 'Stage to <dir> instead of writing in-place')
304
+ .option('--notes', 'Print full tiapp.xml snippets + tuning guide')
305
+ .option('--dry-run', 'Preview without writing any files')
306
+ .option('--cleanup-legacy', 'Remove legacy branding artifacts (reads tiapp.xml)')
307
+ .option('--aggressive', 'With --cleanup-legacy: also remove ldpi folders')
308
+ .option('-y, --yes', 'Skip the in-place confirmation prompt')
309
+ .option('--debug', 'Debug mode')
310
+ .action(async (logo, options) => {
311
+ try {
312
+ await brand(logo, options)
313
+ } catch (error) {
314
+ console.error(chalk.red('Error running brand:'), error.message)
315
+ process.exit(1)
316
+ }
317
+ })
318
+
319
+ // Images command — generate Titanium multi-density UI assets
320
+ program
321
+ .command('images [source]')
322
+ .description(`Generate Titanium multi-density UI images (Alloy & Classic)`)
323
+ .addHelpText('after', `
324
+ Scales UI images into all Titanium density variants in one pass:
325
+ ${chalk.yellow('Android')} → ${chalk.gray('res-mdpi, res-hdpi, res-xhdpi, res-xxhdpi, res-xxxhdpi')} (5 densities)
326
+ ${chalk.yellow('iPhone')} → ${chalk.gray('@1x, @2x, @3x')} (3 scales via filename suffix)
327
+
328
+ Sources auto-discovered from ${chalk.cyan('purgetss/images/')} (subdirectories preserved).
329
+ Pass a file or directory path to override auto-discovery.
330
+
331
+ Writes directly to the project (auto-detects Alloy vs Classic):
332
+ ${chalk.yellow('Alloy')} → ${chalk.gray('app/assets/{android,iphone}/images/')}
333
+ ${chalk.yellow('Classic')} → ${chalk.gray('Resources/{android,iphone}/images/')}
334
+
335
+ Defaults come from the ${chalk.cyan('images:')} section in ${chalk.cyan('purgetss/config.cjs')}.
336
+ CLI flags always win over config values.
337
+
338
+ Examples:
339
+ ${chalk.cyan('purgetss images')} # uses purgetss/images/ + config
340
+ ${chalk.cyan('purgetss images')} ./docs/screenshots # scope to one folder
341
+ ${chalk.cyan('purgetss images')} logo.png # scope to one file
342
+ ${chalk.cyan('purgetss images')} --android # Android densities only
343
+ ${chalk.cyan('purgetss images')} --ios # iPhone scales only
344
+ ${chalk.cyan('purgetss images')} --format webp # convert all outputs to WebP
345
+ ${chalk.cyan('purgetss images')} --format png --quality 95
346
+ ${chalk.cyan('purgetss images')} --dry-run
347
+ `)
348
+ .option('--android', 'Generate only Android density variants (skip iPhone)')
349
+ .option('--ios', 'Generate only iPhone scale variants (skip Android)')
350
+ .option('--format <ext>', 'Convert all outputs to this format: webp|jpeg|png|avif|gif|tiff')
351
+ .option('--quality <n>', 'JPEG/WebP/AVIF quality 0-100 (default: 85)', (v) => parseInt(v, 10))
352
+ .option('--project <path>', 'Project root (default: cwd)')
353
+ .option('--dry-run', 'Preview without writing any files')
354
+ .option('-y, --yes', 'Skip the overwrite confirmation prompt')
355
+ .option('--debug', 'Debug mode')
356
+ .action(async (source, options) => {
357
+ try {
358
+ await images(source, options)
359
+ } catch (error) {
360
+ console.error(chalk.red('Error running images:'), error.message)
361
+ process.exit(1)
362
+ }
363
+ })
364
+
251
365
  // === DAILY DEVELOPMENT ===
252
366
 
253
367
  // Build command
@@ -391,6 +505,31 @@ program
391
505
  }
392
506
  })
393
507
 
508
+ // Semantic command — generates Titanium semantic colors (Light/Dark mode)
509
+ program
510
+ .command('semantic [hexcode] [name]')
511
+ .description(`Generate Titanium semantic colors in ${chalk.yellow('app/assets/semantic.colors.json')}`)
512
+ .addHelpText('after', `\nWithout ${chalk.yellow('--single')}: one hex → 11-shade palette with Light/Dark inversion.\nWith ${chalk.yellow('--single')}: explicit light + dark + optional alpha → one purpose-based color.`)
513
+ .option('-s, --single', 'Generate a single purpose-based semantic color (requires explicit per-mode hex values)')
514
+ .option('-d, --dark <hex>', `With ${chalk.yellow('--single')}: dark-mode hex (defaults to the light value)`)
515
+ .option('-a, --alpha <value>', `With ${chalk.yellow('--single')}: wrap values as ${chalk.yellow('{ color, alpha }')} (range ${chalk.yellow('0-100')}, integer or float)`)
516
+ .option('-n, --name <name>', 'Specify the name of the color')
517
+ .option('-r, --random', 'Palette mode: use a random base color')
518
+ .option('-o, --override', `Place the mapping in ${chalk.yellow('theme.colors')} instead of ${chalk.yellow('theme.extend.colors')}`)
519
+ .option('-q, --quotes', `Keep double quotes in ${chalk.yellow('config.cjs')}`)
520
+ .option('-l, --log', 'Preview the JSON without writing any files')
521
+ .action(async (hexcode, name, options) => {
522
+ try {
523
+ const result = await semantic({ hexcode, name }, options)
524
+ if (!result) {
525
+ process.exit(1)
526
+ }
527
+ } catch (error) {
528
+ console.error(chalk.red('Error running semantic:'), error.message)
529
+ process.exit(1)
530
+ }
531
+ })
532
+
394
533
  // === MAINTENANCE ===
395
534
 
396
535
  // Update command
@@ -496,7 +635,7 @@ function findSimilarCommands(input, validCommands) {
496
635
  // Validate arguments before parsing
497
636
  const args = process.argv.slice(2)
498
637
  if (args.length > 0) {
499
- const validCommands = ['init', 'i', 'create', 'c', 'install-dependencies', 'id', 'build', 'b', 'watch', 'w', 'icon-library', 'il', 'build-fonts', 'bf', 'color-module', 'cm', 'shades', 's', 'module', 'm', 'update', 'u', 'sudo-update', 'su']
638
+ const validCommands = ['init', 'i', 'create', 'c', 'install-dependencies', 'id', 'brand', 'images', 'build', 'b', 'watch', 'w', 'icon-library', 'il', 'build-fonts', 'bf', 'color-module', 'cm', 'shades', 's', 'semantic', 'module', 'm', 'update', 'u', 'sudo-update', 'su']
500
639
  const validFlags = ['-h', '--help', '-V', '--version', '--debug', '-a', '--all']
501
640
 
502
641
  const firstArg = args[0]
@@ -1,4 +1,4 @@
1
- // PurgeTSS v7.5.3
1
+ // PurgeTSS v7.6.0
2
2
  // Created by César Estrada
3
3
  // https://purgetss.com
4
4
 
@@ -316,7 +316,13 @@ function Animation(args = {}) {
316
316
 
317
317
  if (!layout) {
318
318
  view.animate(Ti.UI.createAnimation({ ...args, zIndex: 0, opacity: 0 }), () => {
319
- view.applyProperties({ zIndex: 0, opacity: 0, transform: Ti.UI.createMatrix2D(), translation: { x: 0, y: 0 }, rotate: 0, scale: 1 })
319
+ if (params.isIOS) {
320
+ // Preserve transform so next fade-in continues from last visual state
321
+ view.applyProperties({ zIndex: 0, opacity: 0, touchEnabled: false })
322
+ } else {
323
+ // Android: reset transform to avoid animator glitches on next fade-in
324
+ view.applyProperties({ zIndex: 0, opacity: 0, touchEnabled: false, transform: Ti.UI.createMatrix2D(), translation: { x: 0, y: 0 }, rotate: 0, scale: 1 })
325
+ }
320
326
  })
321
327
  return
322
328
  }
@@ -343,7 +349,7 @@ function Animation(args = {}) {
343
349
  if (layout.opacity !== undefined) animation.opacity = layout.opacity
344
350
 
345
351
  view.animate(animation, () => {
346
- const props = { transform, translation: { x: tx, y: ty }, rotate, scale, zIndex: layout.zIndex }
352
+ const props = { transform, translation: { x: tx, y: ty }, rotate, scale, zIndex: layout.zIndex, touchEnabled: true }
347
353
  if (needsFadeIn) props.opacity = 1
348
354
  view.applyProperties(props)
349
355
  })
@@ -468,13 +474,6 @@ function Animation(args = {}) {
468
474
  const target = directTarget ?? params.lastKnownTarget
469
475
  logger(` -> collision check: ${draggableView.id} | direct: ${directTarget?.id ?? 'null'} | lastKnown: ${params.lastKnownTarget?.id ?? 'null'} | final: ${target?.id ?? 'null'}`)
470
476
  if (target) {
471
- // On Android, consolidate drag position before snap to avoid animation conflict
472
- if (!params.isIOS) {
473
- draggableView.applyProperties({
474
- top: draggableView._visualTop ?? draggableView.top,
475
- left: draggableView._visualLeft ?? draggableView.left
476
- })
477
- }
478
477
  if (args.animationProperties?.snap?.center) {
479
478
  logger(` -> snap-center: ${draggableView.id} to ${target.id}`)
480
479
  animationView.snapTo(draggableView, [target])
@@ -486,14 +485,6 @@ function Animation(args = {}) {
486
485
  } else if (!target && args.animationProperties?.snap?.back) {
487
486
  logger(` -> bounce-back: ${draggableView.id} to (${draggableView._originTop}, ${draggableView._originLeft})`)
488
487
 
489
- // On Android, consolidate drag position before bounce-back to avoid animation conflict
490
- if (!params.isIOS) {
491
- draggableView.applyProperties({
492
- top: draggableView._visualTop ?? draggableView.top,
493
- left: draggableView._visualLeft ?? draggableView.left
494
- })
495
- }
496
-
497
488
  draggableView._bouncingBack = true
498
489
 
499
490
  draggableView.animate({
@@ -582,8 +573,8 @@ function Animation(args = {}) {
582
573
  draggableView.applyProperties({ duration: 0, transform: Ti.UI.createMatrix2D().translate(x, y) })
583
574
  }
584
575
  } else {
585
- const r = draggableView.rotate ?? 0
586
576
  const s = draggableView.scale ?? 1
577
+ const r = draggableView.rotate ?? 0
587
578
 
588
579
  if (r !== 0 || s !== 1) {
589
580
  // Delta-based drag for transformed views on Android
@@ -593,14 +584,20 @@ function Animation(args = {}) {
593
584
  translation.x += deltaX
594
585
  translation.y += deltaY
595
586
  draggableView.animate(Ti.UI.createAnimation({
596
- transform: Ti.UI.createMatrix2D().translate(translation.x, translation.y).rotate(r).scale(s),
597
- duration: 0
598
- }))
599
- draggableView.translation = translation
600
- draggableView.rotate = r
601
- draggableView.scale = s
587
+ duration: 0,
588
+ transform: Ti.UI.createMatrix2D().translate(translation.x, translation.y).rotate(r).scale(s)
589
+ }), () => {
590
+ draggableView.applyProperties({ translation: translation, rotate: r, scale: s })
591
+ })
602
592
  } else {
603
- draggableView.animate(Ti.UI.createAnimation({ top, left, duration: 0 }))
593
+ // Transform-based (mirror of iOS Rama B) — keeps top/left untouched so snapTo/transition stay coherent
594
+ const { x, y } = calculateTranslation(draggableView, draggableView.parent.rect, left, top)
595
+ draggableView.animate(Ti.UI.createAnimation({
596
+ duration: 0,
597
+ transform: Ti.UI.createMatrix2D().translate(x, y)
598
+ }), () => {
599
+ draggableView.applyProperties({ translation: { x, y }, rotate: 0, scale: 1 })
600
+ })
604
601
  }
605
602
  }
606
603
 
@@ -6891,6 +6891,12 @@
6891
6891
  '.left-11/12': { left: '91.666667%' }
6892
6892
  '.left-full': { left: '100%' }
6893
6893
 
6894
+ // Property: keepHardwareMode
6895
+ // Description: A value indicating the render mode of the View
6896
+ // Component(s): Ti.UI.View, Ti.Media.VideoPlayer, Ti.UI.ActivityIndicator, Ti.UI.AlertDialog, Ti.UI.Android.CardView, Ti.UI.Android.CollapseToolbar, Ti.UI.Android.DrawerLayout, Ti.UI.Android.FloatingActionButton, Ti.UI.Android.ProgressIndicator, Ti.UI.Android.SearchView, Ti.UI.Android.Snackbar, Ti.UI.Button, Ti.UI.ButtonBar, Ti.UI.EmailDialog, Ti.UI.ImageView, Ti.UI.Label, Ti.UI.ListView, Ti.UI.MaskedImage, Ti.UI.NavigationWindow, Ti.UI.OptionBar, Ti.UI.OptionDialog, Ti.UI.Picker, Ti.UI.PickerColumn, Ti.UI.PickerRow, Ti.UI.ProgressBar, Ti.UI.ScrollView, Ti.UI.ScrollableView, Ti.UI.SearchBar, Ti.UI.Slider, Ti.UI.Switch, Ti.UI.Tab, Ti.UI.TabGroup, Ti.UI.TabbedBar, Ti.UI.TableView, Ti.UI.TableViewRow, Ti.UI.TextArea, Ti.UI.TextField, Ti.UI.Toolbar, Ti.UI.WebView, Ti.UI.Window
6897
+ '.keep-hardware-mode': { keepHardwareMode: true }
6898
+ '.keep-hardware-mode-false': { keepHardwareMode: false }
6899
+
6894
6900
  // Property: layout
6895
6901
  // Component(s): Ti.UI.View, Ti.Android.R, Ti.Media.VideoPlayer, Ti.UI.Android.CardView, Ti.UI.Android.DrawerLayout, Ti.UI.Android.SearchView, Ti.UI.Button, Ti.UI.ButtonBar, Ti.UI.DashboardView, Ti.UI.ImageView, Ti.UI.Label, Ti.UI.ListView, Ti.UI.MaskedImage, Ti.UI.NavigationWindow, Ti.UI.OptionBar, Ti.UI.PickerRow, Ti.UI.ProgressBar, Ti.UI.ScrollView, Ti.UI.ScrollableView, Ti.UI.SearchBar, Ti.UI.Slider, Ti.UI.Switch, Ti.UI.TabbedBar, Ti.UI.TableView, Ti.UI.TableViewRow, Ti.UI.TextArea, Ti.UI.TextField, Ti.UI.WebView, Ti.UI.Window, Ti.UI.iOS.BlurView, Ti.UI.iOS.CoverFlowView, Ti.UI.iOS.LivePhotoView, Ti.UI.iOS.SplitWindow, Ti.UI.iOS.Stepper
6896
6902
  '.vertical': { layout: 'vertical' }
@@ -17335,7 +17341,7 @@
17335
17341
 
17336
17342
  // Property: autoAdjustScrollViewInsets
17337
17343
  // Description: Specifies whether or not the view controller should automatically adjust its scroll view insets.
17338
- // Component(s): Ti.UI.NavigationWindow, Ti.UI.TabGroup, Ti.UI.Window, Ti.UI.iOS.SplitWindow
17344
+ // Component(s): Ti.UI.NavigationWindow, Ti.UI.TabGroup, Ti.UI.WebView, Ti.UI.Window, Ti.UI.iOS.SplitWindow
17339
17345
  '.auto-adjust-scroll-view-insets': { autoAdjustScrollViewInsets: true }
17340
17346
  '.auto-adjust-scroll-view-insets-false': { autoAdjustScrollViewInsets: false }
17341
17347
 
@@ -25171,6 +25177,12 @@
25171
25177
  '.keyboard-display-requires-user-action': { keyboardDisplayRequiresUserAction: true }
25172
25178
  '.keyboard-display-requires-user-action-false': { keyboardDisplayRequiresUserAction: false }
25173
25179
 
25180
+ // Property: hideKeyboardAccessoryView
25181
+ // Description: A Boolean value indicating whether to hide the keyboard accessory bar.
25182
+ // Component(s): Ti.UI.WebView
25183
+ '.hide-keyboard-accessory-view': { hideKeyboardAccessoryView: true }
25184
+ '.hide-keyboard-accessory-view-false': { hideKeyboardAccessoryView: false }
25185
+
25174
25186
  // Property: ignoreSslError
25175
25187
  // Description: Controls whether to ignore invalid SSL certificates or not.
25176
25188
  // Component(s): Ti.UI.WebView