@maccesar/titools 5.0.0 → 5.1.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/README.md +26 -3
- package/package.json +1 -1
- package/skills/purgetss/SKILL.md +27 -24
- package/skills/purgetss/references/EXAMPLES.md +52 -41
- package/skills/purgetss/references/adopting-purgetss.md +137 -0
- package/skills/purgetss/references/animation-advanced.md +18 -5
- package/skills/purgetss/references/animation-system.md +21 -9
- package/skills/purgetss/references/app-branding.md +36 -97
- package/skills/purgetss/references/appearance-module.md +70 -5
- package/skills/purgetss/references/apply-directive.md +14 -6
- package/skills/purgetss/references/arbitrary-values.md +59 -23
- package/skills/purgetss/references/brand-padding-geometry.md +105 -0
- package/skills/purgetss/references/class-categories.md +684 -272
- package/skills/purgetss/references/class-index-properties.md +713 -607
- package/skills/purgetss/references/class-index.md +149 -84
- package/skills/purgetss/references/classic-projects.md +3 -3
- package/skills/purgetss/references/cli-commands.md +27 -213
- package/skills/purgetss/references/color-commands.md +218 -0
- package/skills/purgetss/references/configurable-properties.md +17 -2
- package/skills/purgetss/references/custom-fonts.md +21 -16
- package/skills/purgetss/references/custom-rules.md +7 -7
- package/skills/purgetss/references/customization-deep-dive.md +14 -11
- package/skills/purgetss/references/dynamic-component-creation.md +104 -61
- package/skills/purgetss/references/grid-layout.md +21 -24
- package/skills/purgetss/references/icon-fonts.md +37 -20
- package/skills/purgetss/references/installation-setup.md +1 -1
- package/skills/purgetss/references/ios-large-titles.md +12 -11
- package/skills/purgetss/references/migration-guide.md +101 -10
- package/skills/purgetss/references/multi-density-images.md +44 -24
- package/skills/purgetss/references/opacity-modifier.md +1 -1
- package/skills/purgetss/references/performance-tips.md +5 -4
- package/skills/purgetss/references/platform-modifiers.md +2 -2
- package/skills/purgetss/references/purgetss-ui-classic.md +21 -5
- package/skills/purgetss/references/semantic-colors.md +20 -13
- package/skills/purgetss/references/smart-mappings.md +8 -7
- package/skills/purgetss/references/svg-pipeline.md +21 -13
- package/skills/purgetss/references/tikit-components.md +2 -2
- package/skills/purgetss/references/titanium-resets.md +27 -17
- package/skills/purgetss/references/ui-ux-design.md +38 -26
- package/skills/purgetss/references/values-and-units.md +1 -2
- package/skills/purgetss/references/version-history.md +32 -4
|
@@ -65,7 +65,7 @@ app/assets/
|
|
|
65
65
|
├── android/images/
|
|
66
66
|
│ ├── res-mdpi/
|
|
67
67
|
│ │ ├── my-hero-illustration.png
|
|
68
|
-
│ │ └── buttons/primary.
|
|
68
|
+
│ │ └── buttons/primary.png
|
|
69
69
|
│ ├── res-hdpi/…
|
|
70
70
|
│ ├── res-xhdpi/…
|
|
71
71
|
│ ├── res-xxhdpi/…
|
|
@@ -75,12 +75,12 @@ app/assets/
|
|
|
75
75
|
├── my-hero-illustration@2x.png
|
|
76
76
|
├── my-hero-illustration@3x.png
|
|
77
77
|
└── buttons/
|
|
78
|
-
├── primary.
|
|
79
|
-
├── primary@2x.
|
|
80
|
-
└── primary@3x.
|
|
78
|
+
├── primary.png (@1x)
|
|
79
|
+
├── primary@2x.png
|
|
80
|
+
└── primary@3x.png
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
Classic projects output to `Resources/android/images/res-*/` and `Resources/iphone/images/` — the command auto-detects the layout.
|
|
83
|
+
SVG sources are written as `.png` (Sharp cannot write SVG) unless `--format` or `images.format` picks another format. Classic projects output to `Resources/android/images/res-*/` and `Resources/iphone/images/` — the command auto-detects the layout.
|
|
84
84
|
|
|
85
85
|
## The `purgetss/images/` convention
|
|
86
86
|
|
|
@@ -119,7 +119,11 @@ That means:
|
|
|
119
119
|
| 200×200 | Button, inline icon |
|
|
120
120
|
| 96×96 | Small inline icon |
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
For example, a 1024×512 `logos/hero.webp` produces eight files: `res-xxxhdpi` 1024×512 (the master, untouched), `res-xxhdpi` and `@3x` 768×384, `res-xhdpi` and `@2x` 512×256, `res-hdpi` 384×192, and `res-mdpi` and `@1x` 256×128. The intended `@1x` display size is the source ÷ 4.
|
|
123
|
+
|
|
124
|
+
In this default mode every density is the source or a reduction of it, so nothing is upscaled: a source that is too small simply yields a smaller `@1x` than you wanted. Upscaling only happens when a width is pinned (`--width` or `images.files`); then, for raster sources, the run warns `source is Xpx wide but xxxhdpi needs Ypx` and the output may look blurry. SVGs are exempt from that warning.
|
|
125
|
+
|
|
126
|
+
Every generated file is capped at **4096 px per side**. A source (or pinned width) that would exceed it aborts the run with `… would render W×Hpx, which exceeds the 4096px cap`. In practice the largest usable raster master is 4096 px wide, and the largest usable `--width` is 1024.
|
|
123
127
|
|
|
124
128
|
Recommended sizes for common UI elements (in source pixels, assumed 4×):
|
|
125
129
|
|
|
@@ -130,7 +134,7 @@ Recommended sizes for common UI elements (in source pixels, assumed 4×):
|
|
|
130
134
|
|
|
131
135
|
## Pinning the output width with `--width`
|
|
132
136
|
|
|
133
|
-
The 4× master convention works well for raster sources (`.png`, `.jpg`, `.webp`) because the file's pixel dimensions usually reflect the intended 4× size. **SVGs are different.** Their logical size comes from the `viewBox`, and vector editors (Affinity Designer, Illustrator, Figma exports) frequently emit viewBoxes in points or with disproportionate values — a logo can ship with `viewBox="0 0 29559 13542"
|
|
137
|
+
The 4× master convention works well for raster sources (`.png`, `.jpg`, `.webp`) because the file's pixel dimensions usually reflect the intended 4× size. **SVGs are different.** Their logical size comes from the `viewBox`, and vector editors (Affinity Designer, Illustrator, Figma exports) frequently emit viewBoxes in points or with disproportionate values — a logo can ship with `viewBox="0 0 29559 13542"`, and `purgetss images` would scale every density from that base: even `res-mdpi` would be 7390×3386 px, so the run aborts on the 4096 px cap.
|
|
134
138
|
|
|
135
139
|
`--width <n>` (added in PurgeTSS v7.8.0) is the escape hatch: it pins the **`mdpi` / `@1x`** output width to exactly `n` pixels, then derives every other density from that base. Height stays proportional to the source aspect ratio — you only specify width.
|
|
136
140
|
|
|
@@ -165,20 +169,18 @@ purgetss images logo.svg --width abc
|
|
|
165
169
|
# Invalid --width 'NaN'. Must be an integer between 1 and 8192.
|
|
166
170
|
```
|
|
167
171
|
|
|
168
|
-
|
|
172
|
+
Passing validation does not guarantee a run: every output is capped at 4096 px per side, and `xxxhdpi` is 4× the pinned width, so any `--width` above `1024` aborts with `… exceeds the 4096px cap`.
|
|
169
173
|
|
|
170
174
|
### The hint message for unflagged SVGs
|
|
171
175
|
|
|
172
|
-
Whenever you run `purgetss images` against an SVG
|
|
176
|
+
Whenever you run `purgetss images` against an SVG that has neither `--width` nor an entry in `images.files`, PurgeTSS prints a one-time hint:
|
|
173
177
|
|
|
174
178
|
```text
|
|
175
|
-
⚠ SVG source detected without --width. Output sizes will be derived from
|
|
176
|
-
|
|
177
|
-
For SVGs from vector editors with disproportionate viewBoxes, pass
|
|
178
|
-
--width <n> (e.g. --width 256) to pin the @1x/mdpi width.
|
|
179
|
+
⚠ SVG source detected without --width and no entry in config.cjs > images.files. Output sizes will be derived from each SVG's viewBox (treated as a 4× master).
|
|
180
|
+
For SVGs from vector editors with disproportionate viewBoxes, pass --width <n> (e.g. --width 256) or add an entry to images.files to pin the @1x/mdpi width.
|
|
179
181
|
```
|
|
180
182
|
|
|
181
|
-
This is a hint, **not an error**. The legacy 4×-from-viewBox behavior still runs in the same invocation. If your SVG has a sensible viewBox (`
|
|
183
|
+
This is a hint, **not an error**. The legacy 4×-from-viewBox behavior still runs in the same invocation. If your SVG has a sensible viewBox (`1200×600` for a logo meant to display 300 px wide at @1x, since the viewBox is read as the 4× size), the default is fine. If the viewBox is in points or noticeably larger than expected, re-run with `--width <n>` for predictable scaling.
|
|
182
184
|
|
|
183
185
|
### Why CLI-only (no `images:` config equivalent)
|
|
184
186
|
|
|
@@ -186,7 +188,7 @@ This is a hint, **not an error**. The legacy 4×-from-viewBox behavior still run
|
|
|
186
188
|
|
|
187
189
|
## The `images:` config section
|
|
188
190
|
|
|
189
|
-
|
|
191
|
+
When `purgetss images` runs without a positional source and an existing `purgetss/config.cjs` has no `images:` section, it injects this block before `theme:` (after `brand:` when present). A run with a positional source never touches the config:
|
|
190
192
|
|
|
191
193
|
```javascript
|
|
192
194
|
// Sources in purgetss/images/ are 4x masters: a 1024px file yields
|
|
@@ -259,6 +261,8 @@ Precedence is the same for every file processed:
|
|
|
259
261
|
2. Otherwise, the matching entry in `images.files` (if any).
|
|
260
262
|
3. Otherwise, the source's natural size: `viewBox` for SVG, intrinsic pixel size for rasters (treated as 4× master).
|
|
261
263
|
|
|
264
|
+
`purgetss images` only applies an entry that has a numeric `width` (plus `height` when present). An entry with only `height`, which the SVG pipeline writes for `h-*`-only references, is not used by this command.
|
|
265
|
+
|
|
262
266
|
| Source | In `files`? | Width source | Output format |
|
|
263
267
|
| --- | --- | --- | --- |
|
|
264
268
|
| `logos/logo.svg` | yes | 256 px @1x (from `files`) | `png` (forced; see below) |
|
|
@@ -273,7 +277,7 @@ Raster entries you add by hand survive subsequent runs untouched. For SVGs detec
|
|
|
273
277
|
|
|
274
278
|
## Output layouts
|
|
275
279
|
|
|
276
|
-
**Alloy layout** (auto-detected when `app/
|
|
280
|
+
**Alloy layout** (auto-detected when `app/views/` exists):
|
|
277
281
|
|
|
278
282
|
```text
|
|
279
283
|
<project>/
|
|
@@ -290,7 +294,7 @@ Raster entries you add by hand survive subsequent runs untouched. For SVGs detec
|
|
|
290
294
|
└── <name>@3x.<ext>
|
|
291
295
|
```
|
|
292
296
|
|
|
293
|
-
**Classic layout** (auto-detected
|
|
297
|
+
**Classic layout** (auto-detected when there is no `app/views/` but a `Resources/` folder exists; with neither, the command stops with `Could not detect a Titanium Alloy or Classic project`):
|
|
294
298
|
|
|
295
299
|
```text
|
|
296
300
|
<project>/
|
|
@@ -375,7 +379,7 @@ Useful when:
|
|
|
375
379
|
- You're iterating on an iOS-only screen and don't need to regenerate Android assets every time.
|
|
376
380
|
- You want to tune JPEG quality differently for the two platforms (run the command twice with different flags).
|
|
377
381
|
|
|
378
|
-
The two flags are mutually exclusive. Passing neither means “use the project's enabled targets,” not “always generate both.”
|
|
382
|
+
The two flags are mutually exclusive. Passing neither means “use the project's enabled targets,” not “always generate both.” If `tiapp.xml` enables neither platform, the command stops with `No Android or iOS deployment target is enabled in tiapp.xml.`
|
|
379
383
|
|
|
380
384
|
## Format conversion
|
|
381
385
|
|
|
@@ -386,7 +390,7 @@ purgetss images --format webp # convert every output to WebP
|
|
|
386
390
|
purgetss images --format jpeg --quality 90
|
|
387
391
|
```
|
|
388
392
|
|
|
389
|
-
Valid targets: `webp`, `jpeg`, `png`, `avif`, `gif`, `tiff
|
|
393
|
+
Valid targets: `webp`, `jpeg`, `png`, `avif`, `gif`, `tiff` (`jpg` is also accepted).
|
|
390
394
|
|
|
391
395
|
### Why WebP?
|
|
392
396
|
|
|
@@ -425,7 +429,7 @@ purgetss images logo.svg --opacity 50 --format png
|
|
|
425
429
|
|
|
426
430
|
### Format interactions
|
|
427
431
|
|
|
428
|
-
- **PNG / WebP / AVIF:** alpha is preserved.
|
|
432
|
+
- **PNG / WebP / AVIF / TIFF / GIF:** alpha is preserved.
|
|
429
433
|
- **JPEG:** JPEG has no alpha. The existing flatten-on-white step composites the semi-transparent image onto white before writing, so `--opacity 50 --format jpeg` produces a 50%-faded version **on white**, not a transparent JPEG.
|
|
430
434
|
|
|
431
435
|
## Adding breathing room with `--padding` (v7.10.0)
|
|
@@ -458,6 +462,12 @@ purgetss images purgetss/brand/logo.svg --output logos/default-image
|
|
|
458
462
|
|
|
459
463
|
That writes `images/logos/default-image.png` (or `.jpg` / `.webp` depending on `--format`) across all densities, regardless of the source's location.
|
|
460
464
|
|
|
465
|
+
### Constraints
|
|
466
|
+
|
|
467
|
+
- No extension: pass the basename only. Any extension in `--output` is stripped; the extension comes from `--format` if set, otherwise from the source, or `.png` when the source is SVG.
|
|
468
|
+
- Relative path only: absolute paths and paths containing `..` are rejected, so the output stays inside the platform `images/` folders.
|
|
469
|
+
- Single source only: `--output` with a directory source aborts, because one basename cannot apply to multiple files.
|
|
470
|
+
|
|
461
471
|
### Combining `--opacity`, `--padding`, and `--output`
|
|
462
472
|
|
|
463
473
|
The three flags compose for the canonical "transparent placeholder with padding under a custom path" use case:
|
|
@@ -470,7 +480,11 @@ purgetss images purgetss/brand/logo.svg \
|
|
|
470
480
|
--format png
|
|
471
481
|
```
|
|
472
482
|
|
|
473
|
-
Produces a 30% opacity, 15%-padded version of the brand logo at `images/logos/default-image.png` for every Android density and iPhone scale — a one-command way to ship a default ImageView placeholder.
|
|
483
|
+
Produces a 30% opacity, 15%-padded version of the brand logo at `images/logos/default-image.png` for every Android density and iPhone scale — a one-command way to ship a default ImageView placeholder. Reference it from the view:
|
|
484
|
+
|
|
485
|
+
```xml
|
|
486
|
+
<ImageView defaultImage="/images/logos/default-image.png" image="{remoteUrl}" />
|
|
487
|
+
```
|
|
474
488
|
|
|
475
489
|
## Full pipeline alongside `build`
|
|
476
490
|
|
|
@@ -490,6 +504,8 @@ ti build -p android -T emulator
|
|
|
490
504
|
|
|
491
505
|
If you only tweaked CSS classes (no image changes), you don't need to re-run `purgetss images`. It's safe to skip.
|
|
492
506
|
|
|
507
|
+
Classic projects have no PurgeTSS build step: generate the images, then run `ti build` as usual — the files under `Resources/` are ordinary Titanium resources.
|
|
508
|
+
|
|
493
509
|
## Cleaning up
|
|
494
510
|
|
|
495
511
|
`purgetss images` never deletes files. It only creates them. If you remove a source, generated copies under `app/assets/` (Alloy) or `Resources/` (Classic) stay in place. Remove them manually or with git when you clean up.
|
|
@@ -515,7 +531,7 @@ If you only tweaked CSS classes (no image changes), you don't need to re-run `pu
|
|
|
515
531
|
| --- | --- |
|
|
516
532
|
| `--format <ext>` | Convert all outputs to: `webp`, `jpeg`, `png`, `avif`, `gif`, `tiff`. Default: keep source format. |
|
|
517
533
|
| `--quality <n>` | Quality `0–100` for `webp`, `jpeg`, `avif` and `tiff`. PNG and GIF ignore it. Default `85`. |
|
|
518
|
-
| `--width <n>` | (v7.8.0) Pin `mdpi` / `@1x` output width to `n` pixels; `[1, 8192]
|
|
534
|
+
| `--width <n>` | (v7.8.0) Pin `mdpi` / `@1x` output width to `n` pixels; validated to `[1, 8192]`, but outputs are capped at 4096 px so values above `1024` abort. 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. |
|
|
519
535
|
| `--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. |
|
|
520
536
|
| `--padding <n>` | (v7.10.0) Shrink the rendered image inside each density canvas by `n%` symmetric borders. Range `[0, 40]`. CLI-only. |
|
|
521
537
|
| `--output <relpath>` | (v7.10.0) Override the basename and subpath relative to each platform's `images/` root. Lets a source from outside `purgetss/images/` (e.g. `purgetss/brand/`) write into a custom output folder like `images/logos/`. CLI-only. |
|
|
@@ -565,6 +581,10 @@ purgetss images --format webp # supports alpha
|
|
|
565
581
|
purgetss images --format png # keeps alpha
|
|
566
582
|
```
|
|
567
583
|
|
|
584
|
+
### My `<ImageView image="/.../foo.svg" />` shows nothing
|
|
585
|
+
|
|
586
|
+
Titanium falls back from a `.svg` reference to a `.png` sibling only. Either the `.png` was never generated, or a `.webp` (or other non-png) of the same basename sits next to it and breaks the fallback. See [SVG pipeline → Troubleshooting](./svg-pipeline.md#troubleshooting).
|
|
587
|
+
|
|
568
588
|
### My subdirectories aren't preserved in the output
|
|
569
589
|
|
|
570
590
|
Verify your source path is inside `purgetss/images/`. When passing sources from outside the convention (e.g. `./docs/screenshots`), the directory of the source file is used as the root, so a file at `./docs/screenshots/hero.png` outputs to `images/hero.png` (flat), not `images/screenshots/hero.png`.
|
|
@@ -587,8 +607,8 @@ Shows every file that would be written, no side effects.
|
|
|
587
607
|
|
|
588
608
|
## Community-Discovered Patterns
|
|
589
609
|
|
|
590
|
-
- **4× master convention predates PurgeTSS.** Treating the source as a 4× master (`xxxhdpi` on Android, `@4x` equivalent on iOS) is
|
|
591
|
-
- **Short-path fallback order matters for monorepos.** When you pass `buttons/primary.png`, the command tries `purgetss/images/buttons/primary.png` first, then `./buttons/primary.png` relative to cwd. In a monorepo where a design folder might shadow the convention, the convention wins.
|
|
610
|
+
- **4× master convention predates PurgeTSS.** Treating the source as a 4× master (`xxxhdpi` on Android, `@4x` equivalent on iOS) is described in the PurgeTSS source as a convention inherited from Titanium Alloy. `purgetss images` formalizes the workflow rather than inventing a new rule.
|
|
611
|
+
- **Short-path fallback order matters for monorepos.** When you pass `buttons/primary.png`, the command tries `purgetss/images/buttons/primary.png` first, then `./buttons/primary.png` relative to the project root (`--project`, default cwd). In a monorepo where a design folder might shadow the convention, the convention wins. A `./` prefix does not change that — `./buttons/primary.png` is still looked up in `purgetss/images/` first. To force the other file, pass an absolute path.
|
|
592
612
|
|
|
593
613
|
## See also
|
|
594
614
|
|
|
@@ -70,4 +70,4 @@ See [Semantic colors — Opacity modifier auto-derivation](./semantic-colors.md#
|
|
|
70
70
|
The following note reflects community observations about how the opacity modifier interacts with gradient utilities.
|
|
71
71
|
|
|
72
72
|
> **Gradients**
|
|
73
|
-
> The same modifier logic applies to color-based gradient utilities such as `from-*` and `to-*`.
|
|
73
|
+
> The same modifier logic applies to color-based gradient utilities such as `from-*` and `to-*`.
|
|
@@ -187,13 +187,14 @@ $.nameLabel.applyProperties({
|
|
|
187
187
|
### Dynamic Class Changes
|
|
188
188
|
|
|
189
189
|
```javascript
|
|
190
|
-
//
|
|
191
|
-
$.statusLabel
|
|
192
|
-
classes: isActive ? ['text-green-500'] : ['text-red-500'],
|
|
190
|
+
// Swap PurgeTSS classes on an existing view; the third argument sets inline properties
|
|
191
|
+
$.resetClass($.statusLabel, isActive ? 'text-green-500' : 'text-red-500', {
|
|
193
192
|
text: isActive ? L('active') : L('inactive')
|
|
194
193
|
})
|
|
195
194
|
```
|
|
196
195
|
|
|
196
|
+
`applyProperties({ classes })` does not restyle a view: Alloy resolves classes only through `$.UI.create()`, `$.createStyle()`, and `$.addClass()` / `$.removeClass()` / `$.resetClass()`. Alloy recommends enabling `autoStyle` on views whose classes change this way. An equivalent one-off form is `$.statusLabel.applyProperties($.createStyle({ apiName: 'Label', classes: ['text-green-500'] }))`.
|
|
197
|
+
|
|
197
198
|
## Debouncing and Throttling
|
|
198
199
|
|
|
199
200
|
### Debounce (Wait for User to Stop)
|
|
@@ -257,7 +258,7 @@ exports.throttle = function(fn, limit = 100) {
|
|
|
257
258
|
| **Bridge** | Cached `Ti.Platform` properties |
|
|
258
259
|
| **Bridge** | Using `applyProperties` for batch updates |
|
|
259
260
|
| **Bridge** | PurgeTSS classes instead of inline styles |
|
|
260
|
-
| **Memory** | All global listeners
|
|
261
|
+
| **Memory** | All global listeners removed when the window closes (e.g. a `cleanup` function — project convention, not Alloy API); `$.destroy()` frees data bindings |
|
|
261
262
|
| **Memory** | Heavy objects nulled in cleanup |
|
|
262
263
|
| **Memory** | Images resized appropriately |
|
|
263
264
|
| **Animation** | Using PurgeTSS Animation component |
|
|
@@ -9,7 +9,7 @@ Platform and device modifiers (also called variants or prefixes) apply styles co
|
|
|
9
9
|
- `tablet:`
|
|
10
10
|
- `handheld:`
|
|
11
11
|
|
|
12
|
-
You can set different background colors and font sizes per platform and device, and combine them with arbitrary values. Examples: `ios:bg-(#53606b)`, `ios:text-(
|
|
12
|
+
You can set different background colors and font sizes per platform and device, and combine them with arbitrary values. Examples: `ios:bg-(#53606b)`, `ios:text-(20)`, `android:bg-(#8fb63e)`, and `android:text-(24)`.
|
|
13
13
|
|
|
14
14
|
```xml
|
|
15
15
|
<Alloy>
|
|
@@ -48,4 +48,4 @@ You can set different background colors and font sizes per platform and device,
|
|
|
48
48
|
The following guidance comes from community experience combining platform modifiers with platform-specific Titanium constants.
|
|
49
49
|
|
|
50
50
|
> **Platform-Specific Properties**
|
|
51
|
-
> If a style relies on `Ti.UI.iOS.*` or `Ti.UI.Android.*` constants, scope it with the matching `ios:` or `android:` modifier, or with the equivalent platform block in `config.cjs`.
|
|
51
|
+
> If a style relies on `Ti.UI.iOS.*` or `Ti.UI.Android.*` constants, scope it with the matching `ios:` or `android:` modifier, or with the equivalent platform block in `config.cjs`. Built-in classes that use those constants already ship scoped in `utilities.tss` (for example `'.clip-enabled[platform=ios]'` and `'.large-title-display-mode-never[platform=ios]'`), so this matters most for custom rules and arbitrary values.
|
|
@@ -40,7 +40,7 @@ const {
|
|
|
40
40
|
} = require('lib/purgetss.ui')
|
|
41
41
|
```
|
|
42
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;
|
|
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; it does not need to be added to the window.
|
|
44
44
|
|
|
45
45
|
## Public exports
|
|
46
46
|
|
|
@@ -125,7 +125,9 @@ window.open()
|
|
|
125
125
|
|
|
126
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
127
|
|
|
128
|
-
`animationProperties.complete` starts after the active `play`, `toggle`, `open`, or `close` base phase
|
|
128
|
+
`animationProperties.complete` starts after the active `play`, `toggle`, `open`, or `close` base phase and after each view of a `sequence()`, and is applied immediately after `apply`. The public play callback does not wait for the second top-level `complete` animation.
|
|
129
|
+
|
|
130
|
+
`play` and `toggle` do nothing while the object's internal `playing` flag is set. The flag turns on when a native animation starts and off at the first completion event, so with an array it clears when the first view finishes. `open`, `close`, `apply`, and `sequence` do not check it.
|
|
129
131
|
|
|
130
132
|
## Native animation configuration
|
|
131
133
|
|
|
@@ -148,12 +150,23 @@ const motion = createAnimation({
|
|
|
148
150
|
})
|
|
149
151
|
```
|
|
150
152
|
|
|
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.
|
|
153
|
+
The constructor removes `id` from the native animation object. Add `debug: true` to log every method call and internal step with `console.warn('::ti.animation:: …')`. 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
154
|
|
|
153
155
|
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
156
|
|
|
155
157
|
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
158
|
|
|
159
|
+
```javascript
|
|
160
|
+
const pivot = { x: 0, y: 0.5 }
|
|
161
|
+
const properties = { duration: 220, rotate: 12 }
|
|
162
|
+
const isIOS = ['iphone', 'ipad'].includes(Ti.Platform.osname)
|
|
163
|
+
|
|
164
|
+
if (isIOS) card.anchorPoint = pivot
|
|
165
|
+
if (Ti.Platform.osname === 'android') properties.anchorPoint = pivot
|
|
166
|
+
|
|
167
|
+
const pivotMotion = createAnimation(properties)
|
|
168
|
+
```
|
|
169
|
+
|
|
157
170
|
## States and direct children
|
|
158
171
|
|
|
159
172
|
```javascript
|
|
@@ -237,9 +250,10 @@ Runtime precedence and behavior:
|
|
|
237
250
|
- Every resolved drag/drop property is forwarded, including size, transform, and anchor-point fields.
|
|
238
251
|
- `snap.center` calls `snapTo()` after a valid drop; `snap.back` returns a missed drop to its origin.
|
|
239
252
|
- There is no `snap.magnet` behavior.
|
|
240
|
-
- Collision detection tests the dragged view's center against registered targets
|
|
253
|
+
- Collision detection tests the dragged view's center against registered targets, and only for views registered with `detectCollisions()`: include the dragged view in that list, or drop callbacks and snapping never run.
|
|
254
|
+
- Hit testing during movement runs only when a hover callback was supplied; then the last non-null hover target is a fallback at release. The hover callback also receives `(source, null)` whenever a registered view is released.
|
|
241
255
|
|
|
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.
|
|
256
|
+
Call drag/collision setup after the views are attached and laid out, for example from the window's `open` or `postlayout` event. `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
257
|
|
|
244
258
|
## Drag lifecycle and cleanup
|
|
245
259
|
|
|
@@ -301,6 +315,8 @@ A view without a matching layout fades to opacity `0`, receives `zIndex: 0`, and
|
|
|
301
315
|
|
|
302
316
|
On Mac Catalyst, use a fixed-size parent rather than `Ti.UI.FILL` for rotated layouts; a resizable parent can distort the matrix.
|
|
303
317
|
|
|
318
|
+
Animated `zIndex` is documented differently across Titanium platforms. Use a composite layout where stacking order matters and verify the result on both targets.
|
|
319
|
+
|
|
304
320
|
## Appearance in Classic
|
|
305
321
|
|
|
306
322
|
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.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Semantic colors let your app respond to Light / Dark mode changes without any extra runtime code. You define color **names** once, give each name a `light` and `dark` hex value, and Titanium resolves the right one at render time based on `Ti.UI.overrideUserInterfaceStyle`. PurgeTSS's contribution is the `theme.extend.colors` mapping in `config.cjs` that turns those names into utility classes like `bg-surface` or `text-on-surface`.
|
|
4
4
|
|
|
5
|
-
This file covers the full workflow: the JSON schema, the `config.cjs` mapping, nesting rules (and the `
|
|
5
|
+
This file covers the full workflow: the JSON schema, the `config.cjs` mapping, nesting rules (and the missing-`DEFAULT` trap), the numeric 11-step tonal-inversion pattern, alpha transparency, the `purgetss semantic` CLI, and three patterns for consuming semantic colors from controllers at runtime.
|
|
6
6
|
|
|
7
7
|
For the mode-switching runtime that drives all of this (`Appearance.init()`, `Appearance.set(...)`, persistence), see [appearance-module.md](./appearance-module.md).
|
|
8
8
|
|
|
@@ -57,6 +57,10 @@ Each top-level key is a color name; each value is an object with `light` and `da
|
|
|
57
57
|
"light": "#6B7280",
|
|
58
58
|
"dark": "#94a3b8"
|
|
59
59
|
},
|
|
60
|
+
"textMutedColor": {
|
|
61
|
+
"light": "#9CA3AF",
|
|
62
|
+
"dark": "#64748b"
|
|
63
|
+
},
|
|
60
64
|
"borderColor": {
|
|
61
65
|
"light": "#E5E7EB",
|
|
62
66
|
"dark": "#334155"
|
|
@@ -68,11 +72,11 @@ Each top-level key is a color name; each value is an object with `light` and `da
|
|
|
68
72
|
}
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
This is a reasonable
|
|
75
|
+
This is a reasonable 7-color starter palette: background, elevated surfaces, primary text, secondary text, muted text, borders, and an accent. Most apps can ship with just these and extend later.
|
|
72
76
|
|
|
73
77
|
### Alpha transparency — the 8-digit format
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
In `semantic.colors.json`, the official docs use the 8-digit `#RRGGBBAA` hex format for transparency. Use it in either mode to produce translucent surfaces like overlays or glass sheets:
|
|
76
80
|
|
|
77
81
|
```json
|
|
78
82
|
{
|
|
@@ -87,7 +91,7 @@ Titanium accepts the `#RRGGBBAA` 8-digit hex format anywhere a color is expected
|
|
|
87
91
|
}
|
|
88
92
|
```
|
|
89
93
|
|
|
90
|
-
The last two hex digits are the alpha channel: `00` is fully transparent, `FF` is fully opaque, `80` is ~50%, `B3` is ~70%, `CC` is ~80%.
|
|
94
|
+
The last two hex digits are the alpha channel: `00` is fully transparent, `FF` is fully opaque, `80` is ~50%, `B3` is ~70%, `CC` is ~80%. This order applies to the JSON file only: the hex colors PurgeTSS writes into TSS put alpha first (`#AARRGGBB`), as in `'.bg-sky-500/50': { backgroundColor: '#800ea5e9' }`, and its own warning suggests `bg-(#AARRGGBB)` for arbitrary values.
|
|
91
95
|
|
|
92
96
|
## Registering in `config.cjs`
|
|
93
97
|
|
|
@@ -105,6 +109,7 @@ module.exports = {
|
|
|
105
109
|
},
|
|
106
110
|
'on-surface': 'textColor',
|
|
107
111
|
'on-surface-variant': 'textSecondaryColor',
|
|
112
|
+
muted: 'textMutedColor',
|
|
108
113
|
border: 'borderColor',
|
|
109
114
|
accent: 'accentColor'
|
|
110
115
|
}
|
|
@@ -113,11 +118,11 @@ module.exports = {
|
|
|
113
118
|
}
|
|
114
119
|
```
|
|
115
120
|
|
|
116
|
-
This generates `bg-surface`, `bg-surface-high`, `text-on-surface`, `text-on-surface-variant`, `bg-border`, `text-accent`, `bg-accent`, etc. Any utility that takes a color — `bg-*`, `text-*`, `border-*`, `placeholder-*`, tint classes — can reference the name.
|
|
121
|
+
This generates `bg-surface`, `bg-surface-high`, `text-on-surface`, `text-on-surface-variant`, `text-muted`, `bg-border`, `text-accent`, `bg-accent`, etc. Any utility that takes a color — `bg-*`, `text-*`, `border-*`, `placeholder-*`, tint classes — can reference the name.
|
|
117
122
|
|
|
118
123
|
### Nested pattern with `DEFAULT`
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
Each key of a nested object becomes a class suffix. The `DEFAULT` key (lowercase `default` works too) produces the base class with no suffix:
|
|
121
126
|
|
|
122
127
|
```js
|
|
123
128
|
// Correct -- generates bg-surface and bg-surface-high
|
|
@@ -127,21 +132,21 @@ surface: {
|
|
|
127
132
|
}
|
|
128
133
|
```
|
|
129
134
|
|
|
130
|
-
`bg-surface` resolves to `surfaceColor` (the `DEFAULT`), and `bg-surface-high` resolves to `surfaceHighColor`.
|
|
135
|
+
`bg-surface` resolves to `surfaceColor` (the `DEFAULT`), and `bg-surface-high` resolves to `surfaceHighColor`. Since v7.10.0, deeper nesting flattens the same way: `brand: { primary: { 500: 'brandColor' } }` generates `bg-brand-primary-500`.
|
|
131
136
|
|
|
132
137
|
> **DANGER**
|
|
133
138
|
>
|
|
134
139
|
> Common error: nested objects without `DEFAULT`
|
|
135
140
|
>
|
|
136
141
|
> ```js
|
|
137
|
-
> // Wrong -- generates
|
|
142
|
+
> // Wrong -- generates bg-surface-regular and bg-surface-high, but no bg-surface
|
|
138
143
|
> surface: {
|
|
139
144
|
> regular: 'surfaceColor',
|
|
140
145
|
> high: 'surfaceHighColor'
|
|
141
146
|
> }
|
|
142
147
|
> ```
|
|
143
148
|
>
|
|
144
|
-
>
|
|
149
|
+
> Without a `DEFAULT` key there is no base class. A view that uses `bg-surface` gets no rule for it in `app.tss`, so the class has no effect. The official docs describe the result as `[object Object]`; PurgeTSS 7.17.1 emits no rule at all. Always include `DEFAULT` for the base variant, or use a flat structure.
|
|
145
150
|
|
|
146
151
|
### Flat structure alternative
|
|
147
152
|
|
|
@@ -163,6 +168,8 @@ Both approaches work. The nested form groups related shades under one namespace;
|
|
|
163
168
|
|
|
164
169
|
Instead of purpose-based names, you can model a color as an 11-step tonal scale (`50` through `950`) where each light-mode value **inverts** in dark mode. This gives you a full tonal range from a single palette and keeps dark-mode contrast in lockstep with light mode.
|
|
165
170
|
|
|
171
|
+
The example below uses a neutral gray palette for clarity, but the same inversion pattern works with any hue — blues, greens, warm tones, or a custom brand palette — as long as the 11 stops share a consistent tonal progression.
|
|
172
|
+
|
|
166
173
|
`app/assets/semantic.colors.json`
|
|
167
174
|
```json
|
|
168
175
|
{
|
|
@@ -240,7 +247,7 @@ In Alloy, this:
|
|
|
240
247
|
1. Generates `amazon50` through `amazon950` using the same algorithm as the `shades` command.
|
|
241
248
|
2. Writes `semantic.colors.json` under `app/assets/` with mirror-by-index values — 50 ↔ 950, 100 ↔ 900, …, 500 as the identical anchor.
|
|
242
249
|
3. Writes the `{ 50: 'amazon50', 100: 'amazon100', ... }` mapping into `config.cjs`.
|
|
243
|
-
4. Strips any prior keys for the `amazon` family before writing — re-runs cleanly replace, never duplicate.
|
|
250
|
+
4. Strips any prior keys for the `amazon` family before writing, including a bare `amazon` key and the 11 shade keys — re-runs cleanly replace, never duplicate.
|
|
244
251
|
|
|
245
252
|
In Classic, the same command writes only the native `Resources/semantic.colors.json` entries. It does not create `purgetss/`, `config.cjs`, utility mappings, TSS, `app/`, or an Alloy hook. Use keys such as `amazon50` directly in Titanium color properties.
|
|
246
253
|
|
|
@@ -361,7 +368,7 @@ Re-runs are idempotent: existing derived keys are reused, never duplicated. If y
|
|
|
361
368
|
>
|
|
362
369
|
> `semantic.colors.json` is read at **native build time**, not at runtime. The first time a brand-new opacity variant is auto-derived (a class like `bg-surface/65` you've never used before), the running app **will not see it** until the next full Titanium build. Liveview hot-reload alone does **not** refresh `semantic.colors.json` for the running app — only the native binary does.
|
|
363
370
|
>
|
|
364
|
-
> In practice: after introducing a new opacity class, run `purgetss build` once, then start a fresh native build (`
|
|
371
|
+
> In practice: after introducing a new opacity class, run `purgetss build` once, then start a fresh native build (`ti build`) before resuming your usual Liveview cycle. Subsequent runs of the *same* `/N` value reuse the existing derived key and need no extra rebuild.
|
|
365
372
|
|
|
366
373
|
#### Constraints
|
|
367
374
|
|
|
@@ -444,11 +451,11 @@ Start with these 6-7 colors and add more only when the design requires it. Fewer
|
|
|
444
451
|
## Related
|
|
445
452
|
|
|
446
453
|
- [appearance-module.md](./appearance-module.md) — `Appearance.init()`, `set(...)`, `get()`, `toggle()` — the runtime that switches the whole palette.
|
|
447
|
-
- [
|
|
454
|
+
- [color-commands.md#semantic-command](./color-commands.md#semantic-command) — full reference for `purgetss semantic` (palette and single modes, all flags).
|
|
448
455
|
- [customization-deep-dive.md](./customization-deep-dive.md) — full `config.cjs` structure, including `theme.extend` vs `theme.colors` and the rest of the extendable keys.
|
|
449
456
|
|
|
450
457
|
## Community-Discovered Patterns
|
|
451
458
|
|
|
452
|
-
- **
|
|
459
|
+
- **A base class that does nothing is always a missing `DEFAULT`.** Any time a class like `bg-surface` has no visible effect, check `config.cjs` first: if `surface` is a nested object without a `DEFAULT` key, PurgeTSS 7.17.1 generates only the suffixed classes (`bg-surface-high`, ...) and no `bg-surface` rule. The official docs describe this case as `[object Object]`. The fix is either to add `DEFAULT: 'surfaceColor'` inside the nested object or flatten the structure.
|
|
453
460
|
- **Semantic resolution is Titanium-native, not PurgeTSS.** This matters when debugging: if a semantic name doesn't resolve, the problem is usually the `semantic.colors.json` file (wrong filename, wrong key, malformed JSON, or wrong location — Alloy expects `app/assets/`, Classic expects `Resources/`). PurgeTSS generates the file in both layouts and an additional utility mapping only in Alloy.
|
|
454
461
|
- **Re-running `purgetss semantic` on the same family is safe.** The CLI strips prior keys for that family from both the JSON and `config.cjs` before writing, so switching between palette and single forms — or changing the base hex — does not leave orphans. Other palettes and manually-defined entries are untouched.
|
|
@@ -16,7 +16,7 @@ In PurgeTSS, `gap` is mapped to **external margins** on the element itself (`top
|
|
|
16
16
|
- `.gap-x-{size}` — emits `right` and `left` only (horizontal margins).
|
|
17
17
|
- `.gap-y-{size}` — emits `top` and `bottom` only (vertical margins).
|
|
18
18
|
- `.gap-{t|r|b|l}-{size}` — single-side gap (e.g. `.gap-t-4`).
|
|
19
|
-
- **Evidence**: Official [`
|
|
19
|
+
- **Evidence**: Official [`docs/grid-system.md`](https://github.com/macCesar/purgetss-docs/blob/main/docs/grid-system.md) § "Gutter utilities".
|
|
20
20
|
|
|
21
21
|
### 2. Hybrid Shadows (iOS vs. Android in One Class)
|
|
22
22
|
|
|
@@ -32,16 +32,17 @@ Shadow utilities (`.shadow-xs`, `.shadow-sm`, `.shadow`, `.shadow-md`, `.shadow-
|
|
|
32
32
|
The `.grid` and `.grid-flow-col` classes emit **both** `layout: 'horizontal'` **and** `width: '100%'` (`.grid-flow-row` emits `layout: 'vertical', height: '100%'`).
|
|
33
33
|
|
|
34
34
|
- **Why the width is baked in**: PurgeTSS resets `View` defaults to `SIZE`. A grid container needs an explicit dimension so that percentage-based children (e.g. `.col-span-6` emitting `width: '50%'`) have a parent measurement to resolve against.
|
|
35
|
-
- **Official docs** ([`grid-system.md`](https://github.com/macCesar/
|
|
35
|
+
- **Official docs** ([`grid-system.md`](https://github.com/macCesar/purgetss-docs/blob/main/docs/grid-system.md)) only document the `layout: 'horizontal'` half. The `width: '100%'` behavior is **verified against `lib/templates/` → `dist/utilities.tss`** (lines emit `'.grid': { layout: 'horizontal', width: '100%' }`).
|
|
36
36
|
- **Manual override**: If you need a grid sized to its content only, pair it with `.wh-auto` or an explicit sizing utility.
|
|
37
37
|
|
|
38
|
-
### 4.
|
|
38
|
+
### 4. Rotations via `rotate` Are Matrix2D Properties (Not CSS Transform)
|
|
39
39
|
|
|
40
|
-
The `.rotate-{n}` utilities (`rotate-0`, `rotate-1`, `rotate-2`, `rotate-3`, `rotate-6`, `rotate-12`, `rotate-45`, `rotate-90`, `rotate-135`, `rotate-180`, `rotate-225`, `rotate-270`, `rotate-315`, `rotate-360`)
|
|
40
|
+
The `.rotate-{n}` utilities (`rotate-0`, `rotate-1`, `rotate-2`, `rotate-3`, `rotate-6`, `rotate-12`, `rotate-45`, `rotate-90`, `rotate-135`, `rotate-180`, `rotate-225`, `rotate-270`, `rotate-315`, `rotate-360`) emit `rotate: <degrees>`, which is a `Matrix2DCreationDict` key, not a `Ti.UI.View` property. They take effect through the `purgetss.ui` Animation module (`<Animation module="purgetss.ui" class="rotate-90" />`).
|
|
41
41
|
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
44
|
-
- **
|
|
42
|
+
- **How it is applied**: at construction time the Animation module converts top-level `scale`, `rotate`, and `anchorPoint` into one `Ti.UI.Matrix2D` (via `Ti.UI.createMatrix2D()`) and removes them from the base object; `animationProperties.open` / `.close` get their own matrices the same way. So `rotate-*`, `scale-*`, and `origin-*` combine without building the matrix by hand.
|
|
43
|
+
- **Anchor**: an `.origin-*` utility (`.origin-top`, `.origin-top-left`, `.origin-center`, `.origin-bottom-right`, etc.) emits `anchorPoint: { x, y }`, which feeds the same matrix.
|
|
44
|
+
- **Outside the Animation module**: to rotate a view yourself, assign a `Ti.UI.createMatrix2D({ rotate: 45 })` to its `transform`.
|
|
45
|
+
- **Evidence**: `dist/utilities.tss` (`// Property: rotate` … `// Component(s): Matrix2DCreationDict`, `'.rotate-45': { rotate: 45 }`, `'.origin-center': { anchorPoint: { x: 0.5, y: 0.5 } }`), `dist/purgetss.ui.js` `handleTransformations()`, and official [`purgetss-ui/9-implementation-rules.md`](https://github.com/macCesar/purgetss-docs/blob/main/docs/purgetss-ui/9-implementation-rules.md).
|
|
45
46
|
|
|
46
47
|
### 5. Z-Index Uses the Full `z-index-*` Prefix
|
|
47
48
|
|
|
@@ -38,7 +38,7 @@ The pipeline only touches SVGs you actually reference from views or controllers.
|
|
|
38
38
|
|
|
39
39
|
## What gets detected
|
|
40
40
|
|
|
41
|
-
**XML views:**
|
|
41
|
+
**XML views:** an `image` or `backgroundImage` attribute ending in `.svg` is captured along with the node's `class=""` attribute:
|
|
42
42
|
|
|
43
43
|
```xml
|
|
44
44
|
<ImageView class="w-32 h-auto" image="/images/logos/logo.svg" />
|
|
@@ -46,7 +46,7 @@ The pipeline only touches SVGs you actually reference from views or controllers.
|
|
|
46
46
|
<View backgroundImage="/images/textures/grain.svg" class="wh-screen" />
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Only `image` and `backgroundImage` are scanned, and only paths under `/images/` (`/images/<sub>/<name>.svg`) are handled; the file must exist at `purgetss/images/<sub>/<name>.svg`, or the run warns `SVG not found: … — skipping`.
|
|
50
50
|
|
|
51
51
|
**Controllers:** objects passed to `$.UI.create(...)` (or similar) that mix `image: '...svg'` and `classes: '...'` are captured by an AST walker:
|
|
52
52
|
|
|
@@ -66,7 +66,7 @@ The scanner sees **literal string values only**, not computed paths. Dynamically
|
|
|
66
66
|
image: '/images/' + variant + '.svg'
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
For dynamic references, pin the dimensions
|
|
69
|
+
For dynamic references, pin the dimensions under `images.files` and generate the PNGs with `purgetss images` (see [Manual pinning](#manual-pinning-for-undetected-references)). If acorn cannot parse a controller, a conservative regex scan looks for the same `image`/`backgroundImage` + `classes` pairs.
|
|
70
70
|
|
|
71
71
|
## How dimensions get resolved
|
|
72
72
|
|
|
@@ -79,7 +79,7 @@ For each SVG reference, the cascade of classes is applied against `app.tss` in d
|
|
|
79
79
|
| `class="w-(300)"` | `width: 300` (arbitrary value) | `widthDp = 300` |
|
|
80
80
|
| `class="h-44"` (no `w-*`) | `height: 176` | `heightDp = 176`; width derived from viewBox at generation time |
|
|
81
81
|
| `class="w-32 h-auto"` | `width: 128`, `height: Ti.UI.SIZE` | `widthDp = 128`; height derived from viewBox |
|
|
82
|
-
| `class="w-full"` (non-numeric) | `width:
|
|
82
|
+
| `class="w-full"` (non-numeric) | `width: '100%'` | skipped with a warning; no usable dim |
|
|
83
83
|
|
|
84
84
|
Note the spacing convention: PurgeTSS multiplies the Tailwind-style numeric scale, so `w-32` resolves to `width: 128` (128 dp), not 32.
|
|
85
85
|
|
|
@@ -140,8 +140,8 @@ images: {
|
|
|
140
140
|
|
|
141
141
|
With `autoSync: false`:
|
|
142
142
|
|
|
143
|
-
- The pipeline still derives dimensions and generates PNGs
|
|
144
|
-
- `config.cjs` is **never** written. Those entries are yours to maintain.
|
|
143
|
+
- The pipeline still derives dimensions from the class cascade and generates PNGs at those sizes. It does not read the values you wrote in `images.files`; it only reports what it would have inserted or updated.
|
|
144
|
+
- `config.cjs` is **never** written. Those entries are yours to maintain, and `purgetss images` is the command that applies them.
|
|
145
145
|
|
|
146
146
|
Use this when you want to pin sizes by hand and not have PurgeTSS overwrite your work.
|
|
147
147
|
|
|
@@ -152,7 +152,7 @@ The SVG pipeline **always emits `.png`**, regardless of `images.format`. This is
|
|
|
152
152
|
1. Titanium's `image="/foo.svg"` fallback resolves to `.png` only. `.webp`, `.jpeg`, and `.avif` are not picked up through the `.svg` reference.
|
|
153
153
|
2. Having a sibling `.webp` (or any non-`.png`) next to `.png` on disk for the same basename **breaks the fallback entirely**. Titanium then shows nothing.
|
|
154
154
|
|
|
155
|
-
So even if you set `format: 'webp'` (which is honored by `purgetss images` for raster sources and unlisted SVGs), the post-purge pipeline emits `.png` for every SVG it processes. The
|
|
155
|
+
So even if you set `format: 'webp'` (which is honored by `purgetss images` for raster sources and unlisted SVGs), the post-purge pipeline emits `.png` for every SVG it processes. The pipeline logs each generated file as `✓ <sub>/<name>.svg → <W>×<H>dp`; the `png (forced; ignores format: webp)` line belongs to the standalone `purgetss images` command.
|
|
156
156
|
|
|
157
157
|
If you genuinely want a `.webp` of an SVG, reference it as `image="/.../foo.webp"` in your XML, not as `.svg`. The standalone `purgetss images` command will then generate `.webp` for that file. See the [WebP and SVG warning](./multi-density-images.md) in the multi-density reference.
|
|
158
158
|
|
|
@@ -188,7 +188,7 @@ Otherwise, PurgeTSS regenerates the image. If you `git clean`, delete a PNG by h
|
|
|
188
188
|
|
|
189
189
|
## Manual pinning for undetected references
|
|
190
190
|
|
|
191
|
-
When the static scanner cannot see a reference (dynamic/concatenated paths) or a view has no resolvable `w-*` / `h-*` class, pin the entry by hand
|
|
191
|
+
When the static scanner cannot see a reference (dynamic/concatenated paths) or a view has no resolvable `w-*` / `h-*` class, pin the entry by hand and run `purgetss images`. The post-purge pipeline only generates what it detected and resolved; pinned entries are applied by the standalone command:
|
|
192
192
|
|
|
193
193
|
```javascript title="./purgetss/config.cjs"
|
|
194
194
|
images: {
|
|
@@ -210,11 +210,19 @@ images: {
|
|
|
210
210
|
|
|
211
211
|
Both share the same `images.files` array and the same generation engine (`gen-scales`). They do not fight each other: the pipeline edits `images.files`; the standalone command reads it as overrides. Run either, or both.
|
|
212
212
|
|
|
213
|
+
## Scope and limits
|
|
214
|
+
|
|
215
|
+
- The pipeline runs inside `purgetss` (the purge), which requires an Alloy project, so it does not run in Classic projects. It does nothing when `purgetss/images/` does not exist or no SVG reference is found.
|
|
216
|
+
- It always generates both platforms (5 Android + 3 iPhone PNGs); it does not read `<deployment-targets>` from `tiapp.xml` the way `purgetss images` does.
|
|
217
|
+
- An SVG with neither a `viewBox` nor explicit `width` and `height` attributes is skipped with a warning.
|
|
218
|
+
- Every PNG is capped at 4096 px per side, and `xxxhdpi` is 4× the resolved dp size, so an SVG resolved wider or taller than 1024 dp (for example `w-(1100)`) fails with `✗ <sub>/<name>.svg: … exceeds the 4096px cap`. The other SVGs in the run still generate.
|
|
219
|
+
- A run that generated or reused any PNG ends with `SVG pipeline: N generated, M cached.`
|
|
220
|
+
|
|
213
221
|
## Troubleshooting
|
|
214
222
|
|
|
215
|
-
### "no class resolved width or height to a number
|
|
223
|
+
### "no class resolved width or height to a number — skipping"
|
|
216
224
|
|
|
217
|
-
The SVG is referenced from a view with no resolvable `w-*` or `h-*` class (just `w-full`, `w-screen`, `bg-*`, etc.). Add a numeric `w-*` or `h-*` utility, or pin the size manually in `images.files`:
|
|
225
|
+
The SVG is referenced from a view with no resolvable `w-*` or `h-*` class (just `w-full`, `w-screen`, `bg-*`, etc.). Add a numeric `w-*` or `h-*` utility, or pin the size manually in `images.files` and run `purgetss images`:
|
|
218
226
|
|
|
219
227
|
```javascript
|
|
220
228
|
files: [
|
|
@@ -224,13 +232,13 @@ files: [
|
|
|
224
232
|
|
|
225
233
|
### My change to a class didn't update the PNG
|
|
226
234
|
|
|
227
|
-
Check that the class actually exists in `app.tss` after purging. The Tailwind-style spacing scale skips numbers like `h-50`; only `h-48`, `h-52`, `h-56` are emitted by default. Add custom values under `theme.extend.spacing` if you need them, or use arbitrary values: `h-(50)`, `h-(
|
|
235
|
+
Check that the class actually exists in `app.tss` after purging. The Tailwind-style spacing scale skips numbers like `h-50`; only `h-48`, `h-52`, `h-56` are emitted by default. Add custom values under `theme.extend.spacing` if you need them, or use arbitrary values: `h-(50)`, `h-(200)`, `h-(12.5rem)`.
|
|
228
236
|
|
|
229
237
|
If the class is correct but the image didn't refresh on device, it might be the Titanium simulator's image cache. Do a clean build.
|
|
230
238
|
|
|
231
239
|
### The pipeline didn't detect my SVG reference
|
|
232
240
|
|
|
233
|
-
The static scanner sees literal string values, not computed paths. Concatenated strings (`'/images/' + variant + '.svg'`) are not detected. Pin the entry in `images.files` manually
|
|
241
|
+
The static scanner sees literal string values, not computed paths. Concatenated strings (`'/images/' + variant + '.svg'`) are not detected. Pin the entry in `images.files` manually and run `purgetss images` to generate the PNGs.
|
|
234
242
|
|
|
235
243
|
### `<ImageView image="/.../foo.svg" />` shows nothing
|
|
236
244
|
|
|
@@ -241,7 +249,7 @@ Two common causes:
|
|
|
241
249
|
|
|
242
250
|
### I want to opt out for a specific run
|
|
243
251
|
|
|
244
|
-
Switch `autoSync: false` and manage the `images.files` entries yourself
|
|
252
|
+
Switch `autoSync: false` and manage the `images.files` entries yourself. The pipeline still derives dimensions and generates the PNGs so views keep working, but it does not write to `config.cjs`. Deleting the SVG cache entry does not opt out; it only forces that SVG to regenerate on the next run.
|
|
245
253
|
|
|
246
254
|
## See also
|
|
247
255
|
|