@maccesar/titools 4.3.0 → 4.4.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 +8 -2
- package/package.json +1 -1
- package/skills/ti-expert/SKILL.md +38 -5
- package/skills/ti-expert/references/alloy-builtins.md +11 -22
- package/skills/ti-expert/references/alloy-structure.md +42 -18
- package/skills/ti-expert/references/anti-patterns.md +13 -5
- package/skills/ti-expert/references/code-conventions.md +9 -3
- package/skills/ti-expert/references/controller-patterns.md +33 -26
- package/skills/ti-expert/references/examples.md +48 -21
- package/skills/ti-expert/references/feedback-migration.md +165 -0
- package/skills/ti-expert/references/feedback-surfaces.md +140 -0
- package/skills/ti-expert/references/feedback-widget-contracts.md +213 -0
- package/skills/ti-expert/references/migration-patterns.md +47 -25
- package/skills/ti-expert/references/security-device.md +58 -56
- package/skills/ti-expert/references/state-management.md +5 -5
- package/skills/ti-expert/references/theming.md +134 -392
package/README.md
CHANGED
|
@@ -333,6 +333,10 @@ When it activates:
|
|
|
333
333
|
- Choosing data strategies (Models vs Collections)
|
|
334
334
|
- Writing clean ES6+ code
|
|
335
335
|
- Memory management and cleanup patterns
|
|
336
|
+
- Choosing a feedback surface (Snackbar, Dialog, Bottom Sheet) and deciding what stays native
|
|
337
|
+
- Designing reusable Alloy Widgets and their lifecycle contracts
|
|
338
|
+
- Adaptive layouts for tablets, foldables, and large screens
|
|
339
|
+
- Theming and dark mode
|
|
336
340
|
- Performance optimization
|
|
337
341
|
- Security patterns
|
|
338
342
|
- Migrating legacy apps
|
|
@@ -343,13 +347,15 @@ Example prompts:
|
|
|
343
347
|
"Create a user service that fetches data from an API."
|
|
344
348
|
"What's the best pattern for navigation between screens?"
|
|
345
349
|
"Prevent memory leaks in my controllers."
|
|
350
|
+
"Every confirmation in my app is an alertDialog — what should each one actually be?"
|
|
351
|
+
"Should this be a Widget or just a <Require>?"
|
|
346
352
|
"Migrate classic Titanium code to modern Alloy patterns."
|
|
347
353
|
"What's a clean way to organize services in an Alloy app?"
|
|
348
354
|
```
|
|
349
355
|
|
|
350
356
|
Key features:
|
|
351
357
|
- Quick decision matrix for common questions
|
|
352
|
-
-
|
|
358
|
+
- 24 reference guides for deep dives
|
|
353
359
|
- Delegates to specialized skills when needed
|
|
354
360
|
|
|
355
361
|
---
|
|
@@ -718,7 +724,7 @@ This pattern is documented across three TiTools skills: `ti-ui`, `ti-expert`, an
|
|
|
718
724
|
|
|
719
725
|
| Skill | SKILL.md | References |
|
|
720
726
|
| --------- | ----------------------------- | ----------------------------------------------------- |
|
|
721
|
-
| ti-expert | Architecture + Implementation |
|
|
727
|
+
| ti-expert | Architecture + Implementation | 24 files (patterns, feedback surfaces, testing, security, etc.) |
|
|
722
728
|
| purgetss | Setup + Critical Rules | 33 files (grid, animations, icons, class-index, SVG pipeline, etc.) |
|
|
723
729
|
| ti-ui | UI Rules + Platform Diffs | 14 files (layouts, lists, gestures, etc.) |
|
|
724
730
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ti-expert
|
|
3
|
-
description: "Use when designing, reviewing, analyzing, or examining Titanium SDK architecture and implementation (Alloy or Classic) — creating controllers/views/services, choosing models vs collections,
|
|
3
|
+
description: "Use when designing, reviewing, auditing, analyzing, or examining Titanium SDK architecture and implementation (Alloy or Classic) — creating controllers/views/services, choosing models vs collections, deciding between app-owned and system UI, designing reusable Widgets or feedback/modality patterns, hunting memory leaks and cleanup gaps, testing, migrating legacy apps, or building adaptive layouts for tablets, foldables, and large screens. AUTO-DETECT: If tiapp.xml exists, invoke BEFORE architectural decisions, new controllers/views, feedback components, or restructuring. Titanium navigation, memory, event, and modal patterns differ from web frameworks."
|
|
4
4
|
argument-hint: "[architecture-topic]"
|
|
5
5
|
allowed-tools: Read, Grep, Glob, Edit, Write, Bash(git *), Bash(node *)
|
|
6
6
|
---
|
|
@@ -9,6 +9,26 @@ allowed-tools: Read, Grep, Glob, Edit, Write, Bash(git *), Bash(node *)
|
|
|
9
9
|
|
|
10
10
|
Practical architecture and implementation guidance for Titanium SDK apps (Alloy and Classic). Focus on maintainability, clear boundaries, and low-friction testing.
|
|
11
11
|
|
|
12
|
+
## Required workflow
|
|
13
|
+
|
|
14
|
+
This file is an index. Read the references that govern the task before making a
|
|
15
|
+
recommendation or changing code.
|
|
16
|
+
|
|
17
|
+
| Task | Required reading |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| Architecture tier, folders, or boundaries | [Architecture tiers](references/architecture-tiers.md), [Alloy structure](references/alloy-structure.md) |
|
|
20
|
+
| Feedback, alerts, choices, or app-owned versus system UI | [Feedback surfaces](references/feedback-surfaces.md) |
|
|
21
|
+
| Reusable Snackbar, Dialog, Bottom Sheet, Widget, or `<Require>` | [Feedback Widget contracts](references/feedback-widget-contracts.md), [Alloy structure](references/alloy-structure.md) |
|
|
22
|
+
| Migrating native dialogs, Toasts, or local overlays | [Feedback migration](references/feedback-migration.md), [Migration patterns](references/migration-patterns.md) |
|
|
23
|
+
| Theme or dark mode | [Theming](references/theming.md); if PurgeTSS is detected, invoke the `purgetss` skill before styling |
|
|
24
|
+
| Tablet, foldable, large screen, or responsive behavior | [Adaptive layouts](references/adaptive-layouts.md) |
|
|
25
|
+
| Security architecture | [Security fundamentals](references/security-fundamentals.md), [Device security](references/security-device.md) |
|
|
26
|
+
| Testing or performance | Load the matching guide under Quality & Performance below |
|
|
27
|
+
|
|
28
|
+
Support recommendations with `[source: references/<file>.md]`. If a claim cannot
|
|
29
|
+
be verified from the loaded references or inspected project, prefix it with
|
|
30
|
+
`FROM_MEMORY (unverified):` and do not present it as a settled project fact.
|
|
31
|
+
|
|
12
32
|
## Project detection
|
|
13
33
|
|
|
14
34
|
> **️ℹ️ Auto-detects Alloy vs Classic projects**
|
|
@@ -33,9 +53,10 @@ Practical architecture and implementation guidance for Titanium SDK apps (Alloy
|
|
|
33
53
|
1. Architecture: define structure by technical type with flat folders (`lib/api`, `lib/services`, `lib/actions`, `lib/repositories`, `lib/helpers`)
|
|
34
54
|
2. Data strategy: choose Models (SQLite) or Collections (API)
|
|
35
55
|
3. Contracts: define I/O specs between layers
|
|
36
|
-
4.
|
|
37
|
-
5.
|
|
38
|
-
6.
|
|
56
|
+
4. UI ownership: classify app-owned feedback, screen state, and system-owned flows
|
|
57
|
+
5. Implementation: write XML views and ES6+ controllers
|
|
58
|
+
6. Quality: testing, error handling, logging, performance
|
|
59
|
+
7. Cleanup: implement a `cleanup()` pattern for memory management
|
|
39
60
|
|
|
40
61
|
## Architectural Maturity Tiers
|
|
41
62
|
|
|
@@ -84,6 +105,12 @@ Detailed examples and full implementation samples are available in: [Architectur
|
|
|
84
105
|
|
|
85
106
|
## Titanium style sheets rules (low freedom)
|
|
86
107
|
|
|
108
|
+
Before styling, detect `purgetss/` or `purgetss/config.cjs`. If present, invoke
|
|
109
|
+
the `purgetss` skill, verify every utility, and never edit the generated
|
|
110
|
+
`app/styles/app.tss`. Without PurgeTSS, use standard Alloy TSS files. In either
|
|
111
|
+
case, keep behavior in XML/controllers and use semantic colors for app-owned
|
|
112
|
+
surfaces when the project supports them.
|
|
113
|
+
|
|
87
114
|
> **🚨 Critical: platform-specific properties require modifiers**
|
|
88
115
|
> Using `Ti.UI.iOS.*` or `Ti.UI.Android.*` properties without platform modifiers breaks cross-platform builds.
|
|
89
116
|
>
|
|
@@ -138,7 +165,10 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
|
|
|
138
165
|
| Models or Collections? | Collections for API data, Models for SQLite persistence |
|
|
139
166
|
| Ti.App.fireEvent or EventBus? | Always EventBus (Backbone.Events) |
|
|
140
167
|
| Direct navigation or service? | Always Navigation service (auto cleanup) |
|
|
141
|
-
|
|
|
168
|
+
| Snackbar, Dialog, or Bottom Sheet? | Classify urgency, persistence, reversibility, and choice first |
|
|
169
|
+
| Widget or `<Require>`? | Widget for a self-contained API/lifecycle; `<Require>` for app composition |
|
|
170
|
+
| App-owned or native UI? | Keep OS trust/protected flows native; style app-owned feedback |
|
|
171
|
+
| Inline styles or style system? | PurgeTSS utilities when detected; otherwise Alloy TSS |
|
|
142
172
|
|
|
143
173
|
## Reference guides (progressive disclosure)
|
|
144
174
|
|
|
@@ -155,6 +185,9 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
|
|
|
155
185
|
- [Alloy Builtins & Globals](references/alloy-builtins.md)
|
|
156
186
|
- [Code Conventions](references/code-conventions.md)
|
|
157
187
|
- [Controller Patterns](references/controller-patterns.md)
|
|
188
|
+
- [Feedback Surfaces & App-Owned UI](references/feedback-surfaces.md)
|
|
189
|
+
- [Feedback Widget Contracts](references/feedback-widget-contracts.md)
|
|
190
|
+
- [Feedback Migration](references/feedback-migration.md)
|
|
158
191
|
- [Theming & Dark Mode](references/theming.md)
|
|
159
192
|
- [Migration Patterns](references/migration-patterns.md)
|
|
160
193
|
- [Examples Collection](references/examples.md)
|
|
@@ -75,13 +75,6 @@ Shared state and utilities accessible from any controller or lib file. Set in `a
|
|
|
75
75
|
// alloy.js
|
|
76
76
|
const Backbone = require('alloy/backbone')
|
|
77
77
|
|
|
78
|
-
// --- Theme / Design Tokens ---
|
|
79
|
-
Alloy.Globals.theme = {
|
|
80
|
-
primary: '#2563eb',
|
|
81
|
-
bg: '#ffffff',
|
|
82
|
-
text: '#111827'
|
|
83
|
-
}
|
|
84
|
-
|
|
85
78
|
// --- Device Info (cached once) ---
|
|
86
79
|
Alloy.Globals.isTablet = Alloy.isTablet
|
|
87
80
|
Alloy.Globals.screenWidth = Ti.Platform.displayCaps.platformWidth
|
|
@@ -100,27 +93,23 @@ Alloy.Collections.notifications = new Backbone.Collection()
|
|
|
100
93
|
### Access from anywhere
|
|
101
94
|
|
|
102
95
|
```javascript
|
|
103
|
-
// In any controller
|
|
104
|
-
const theme = Alloy.Globals.theme
|
|
105
|
-
$.label.color = theme.primary
|
|
106
|
-
|
|
107
|
-
// In TSS (evaluated at compile time for controller creation)
|
|
108
|
-
"#header": {
|
|
109
|
-
backgroundColor: Alloy.Globals.theme.primary
|
|
110
|
-
}
|
|
111
|
-
|
|
112
96
|
// In XML with conditional binding
|
|
113
97
|
// <View if="Alloy.Globals.isLoggedIn">
|
|
114
98
|
```
|
|
115
99
|
|
|
100
|
+
Do not use `Alloy.Globals` as the default dark-mode palette. Prefer Titanium
|
|
101
|
+
semantic colors; when PurgeTSS is detected, use its semantic color and Appearance
|
|
102
|
+
workflow. Reserve a global runtime palette for custom theme requirements that
|
|
103
|
+
semantic light/dark/system roles cannot model. See [Theming](theming.md).
|
|
104
|
+
|
|
116
105
|
### Globals vs services
|
|
117
106
|
|
|
118
|
-
| Use `Alloy.Globals` for
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
| Values
|
|
123
|
-
| Read-only data
|
|
107
|
+
| Use `Alloy.Globals` for | Use a Service for |
|
|
108
|
+
| ------------------------------------- | ----------------------------- |
|
|
109
|
+
| Immutable device/build facts | Business logic |
|
|
110
|
+
| Framework-level shared registries | Complex state with listeners |
|
|
111
|
+
| Values initialized once in `alloy.js` | Values that change frequently |
|
|
112
|
+
| Read-only interoperability data | Data with side effects |
|
|
124
113
|
|
|
125
114
|
---
|
|
126
115
|
|
|
@@ -30,7 +30,7 @@ app/
|
|
|
30
30
|
│ ├── home.xml
|
|
31
31
|
│ └── userProfile.xml
|
|
32
32
|
├── styles/ # TSS styles (one per view + global)
|
|
33
|
-
│ ├── app.tss #
|
|
33
|
+
│ ├── app.tss # Standard Alloy global styles or PurgeTSS generated output
|
|
34
34
|
│ ├── index.tss # Styles for index view
|
|
35
35
|
│ ├── home.tss
|
|
36
36
|
│ └── userProfile.tss
|
|
@@ -59,7 +59,7 @@ app/
|
|
|
59
59
|
│ └── providers/
|
|
60
60
|
│ ├── containerProvider.js
|
|
61
61
|
│ └── loggerProvider.js
|
|
62
|
-
├── widgets/ #
|
|
62
|
+
├── widgets/ # Self-contained components with an API/lifecycle boundary
|
|
63
63
|
│ └── customButton/
|
|
64
64
|
├── config.json # Alloy configuration
|
|
65
65
|
└── alloy.js # Collections & Global services
|
|
@@ -188,7 +188,8 @@ api.getFrames()
|
|
|
188
188
|
## Controller rules
|
|
189
189
|
|
|
190
190
|
**DO:**
|
|
191
|
-
-
|
|
191
|
+
- Use verified PurgeTSS utilities when detected; otherwise define styles in
|
|
192
|
+
per-controller TSS and manually maintained global TSS.
|
|
192
193
|
- Orchestrate view and model/collection interactions.
|
|
193
194
|
- Handle UI events and delegate to services.
|
|
194
195
|
- Format data for display (simple cases).
|
|
@@ -196,7 +197,7 @@ api.getFrames()
|
|
|
196
197
|
- Keep `lib` modules flat and easy to locate.
|
|
197
198
|
|
|
198
199
|
**DON'T:**
|
|
199
|
-
-
|
|
200
|
+
- Scatter visual properties inline when the active style system can express them.
|
|
200
201
|
- Make direct API calls (use lib/api/ or lib/services/).
|
|
201
202
|
- Contain heavy business logic.
|
|
202
203
|
- Call native modules directly (use a service wrapper).
|
|
@@ -245,7 +246,10 @@ See [ControllerAutoCleanup.js](../assets/ControllerAutoCleanup.js) for the compl
|
|
|
245
246
|
|
|
246
247
|
## Widget structure
|
|
247
248
|
|
|
248
|
-
Widgets are self-contained
|
|
249
|
+
Widgets are self-contained components with their own public API, state, assets,
|
|
250
|
+
and lifecycle. Choose one for a portable boundary, not after an arbitrary number
|
|
251
|
+
of usages. A first-use component with timers, modal state, and cleanup can merit
|
|
252
|
+
a Widget; repeated static styling does not.
|
|
249
253
|
|
|
250
254
|
```
|
|
251
255
|
app/widgets/
|
|
@@ -255,7 +259,7 @@ app/widgets/
|
|
|
255
259
|
├── views/
|
|
256
260
|
│ └── widget.xml # Main widget view
|
|
257
261
|
├── styles/
|
|
258
|
-
│ └── widget.tss #
|
|
262
|
+
│ └── widget.tss # Optional component-specific styles
|
|
259
263
|
└── widget.json # Widget manifest
|
|
260
264
|
```
|
|
261
265
|
|
|
@@ -276,6 +280,17 @@ app/widgets/
|
|
|
276
280
|
}
|
|
277
281
|
```
|
|
278
282
|
|
|
283
|
+
Register the Widget in the app's `app/config.json`; the dependency key must
|
|
284
|
+
match the manifest `id`:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
{
|
|
288
|
+
"dependencies": {
|
|
289
|
+
"com.app.loadingOverlay": "1.0.0"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
279
294
|
### Widget view (widget.xml)
|
|
280
295
|
```xml
|
|
281
296
|
<Alloy>
|
|
@@ -290,10 +305,10 @@ app/widgets/
|
|
|
290
305
|
|
|
291
306
|
```tss
|
|
292
307
|
/* styles/widget.tss */
|
|
293
|
-
"#container": { width: Ti.UI.FILL, height: Ti.UI.FILL, visible: false, backgroundColor: '
|
|
294
|
-
"#box": { width: 128, height: 128, layout: 'vertical', borderRadius: 16, backgroundColor: '
|
|
308
|
+
"#container": { width: Ti.UI.FILL, height: Ti.UI.FILL, visible: false, backgroundColor: 'overlayColor' }
|
|
309
|
+
"#box": { width: 128, height: 128, layout: 'vertical', borderRadius: 16, backgroundColor: 'surfaceColor' }
|
|
295
310
|
"#spinner": { top: 24 }
|
|
296
|
-
"#messageLabel": { left: 16, right: 16, top: 16, font: { fontSize: 14 }, color: '
|
|
311
|
+
"#messageLabel": { left: 16, right: 16, top: 16, font: { fontSize: 14 }, color: 'textColor' }
|
|
297
312
|
```
|
|
298
313
|
|
|
299
314
|
### Widget controller (widget.js)
|
|
@@ -352,8 +367,12 @@ const loadData = () => {
|
|
|
352
367
|
}
|
|
353
368
|
```
|
|
354
369
|
|
|
355
|
-
>
|
|
356
|
-
>
|
|
370
|
+
> **Widget styles**
|
|
371
|
+
> In a standard Alloy project, keep component-specific properties in
|
|
372
|
+
> `styles/widget.tss` and consume semantic colors. If PurgeTSS is detected, load
|
|
373
|
+
> its skill first, enable Widget scanning, place verified utilities in XML, and
|
|
374
|
+
> reserve `widget.tss` for properties with no utility. Never edit the generated
|
|
375
|
+
> `app/styles/app.tss`.
|
|
357
376
|
|
|
358
377
|
### Widget ↔ controller communication
|
|
359
378
|
|
|
@@ -464,13 +483,18 @@ render()
|
|
|
464
483
|
|
|
465
484
|
### When to use widget vs require
|
|
466
485
|
|
|
467
|
-
| Use `<Widget>` when | Use `<Require>` when
|
|
468
|
-
| ------------------------------------- |
|
|
469
|
-
|
|
|
470
|
-
|
|
|
471
|
-
| Has
|
|
472
|
-
| Could be extracted to another project |
|
|
473
|
-
| Needs its own `widget.json` manifest |
|
|
486
|
+
| Use `<Widget>` when | Use `<Require>` when |
|
|
487
|
+
| ------------------------------------- | ------------------------------------ |
|
|
488
|
+
| Owns a stable public API | Composes an app-specific controller |
|
|
489
|
+
| Owns state, timers, listeners, or cleanup | Shares the host's lifecycle |
|
|
490
|
+
| Has a portable design-system boundary | Shares the host's domain and styling |
|
|
491
|
+
| Could be extracted to another project | Has no independent package contract |
|
|
492
|
+
| Needs its own `widget.json` manifest | Remains lightweight and app-local |
|
|
493
|
+
|
|
494
|
+
Usage count is supporting evidence only. Also consider a screen-local XML state
|
|
495
|
+
for loading/empty/error content, a style class for appearance without behavior,
|
|
496
|
+
or native UI when the operating system owns the workflow. See
|
|
497
|
+
[Feedback Widget Contracts](feedback-widget-contracts.md).
|
|
474
498
|
|
|
475
499
|
## config.json reference
|
|
476
500
|
|
|
@@ -67,14 +67,22 @@
|
|
|
67
67
|
## 13. Using `Ti.UI.createNotification`
|
|
68
68
|
**Symptom:** `Ti.UI.createNotification({ message: 'Hi' }).show()`
|
|
69
69
|
**Problem:** This API doesn't exist in Titanium. Causes "invalid method" error.
|
|
70
|
-
**Solution:**
|
|
70
|
+
**Solution:** Classify the feedback before choosing UI. Use the app's Snackbar
|
|
71
|
+
Widget for transient foreground feedback, inline/Banner state for persistent
|
|
72
|
+
conditions, and an app-owned Dialog only for blocking or critical information.
|
|
73
|
+
Use the supported platform notification APIs only for actual OS notifications.
|
|
74
|
+
See [Feedback Surfaces](feedback-surfaces.md).
|
|
71
75
|
|
|
72
76
|
## 14. Using nonexistent iOS share APIs
|
|
73
77
|
**Symptom:** `Ti.UI.iOS.createActivityPopover` or `alloy/social` with wrong methods.
|
|
74
78
|
**Problem:** These APIs either don't exist or have changed. Causes runtime errors.
|
|
75
79
|
**Solution:**
|
|
76
|
-
-
|
|
77
|
-
|
|
80
|
+
- Keep sharing system-owned. Use the currently supported Titanium share/document
|
|
81
|
+
API or native module for the target platform and verify its current contract
|
|
82
|
+
with the `ti-api` skill.
|
|
83
|
+
- Do not imitate a share sheet with an app-owned `OptionDialog`. A Bottom Sheet
|
|
84
|
+
may choose an app action such as an export format, but the OS share surface
|
|
85
|
+
owns the final destination.
|
|
78
86
|
|
|
79
87
|
## Community-Discovered Patterns
|
|
80
88
|
|
|
@@ -117,8 +125,8 @@ This applies to Windows inside both standalone NavigationWindow and TabGroup (wh
|
|
|
117
125
|
| ----------------------- | ------------------------ | --------------------- |
|
|
118
126
|
| `padding` on View | No padding on containers | Margins on children |
|
|
119
127
|
| `layout: 'composite'` | Already default | Omit it |
|
|
120
|
-
| Inline style attributes | Scattered styling |
|
|
128
|
+
| Inline style attributes | Scattered styling | PurgeTSS utilities when detected; otherwise TSS |
|
|
121
129
|
| `lib/` prefix | lib/ is flattened | Use path without lib/ |
|
|
122
130
|
| `$.index.open()` | Wrong ID reference | Use actual Window ID |
|
|
123
|
-
| `createNotification` | API doesn't exist |
|
|
131
|
+
| `createNotification` | API doesn't exist | Classify feedback; Snackbar is the transient default |
|
|
124
132
|
| `extendEdges` alone | Content behind nav bar | Add `autoAdjustScrollViewInsets: true` |
|
|
@@ -54,9 +54,15 @@ const name = user.name
|
|
|
54
54
|
|
|
55
55
|
## Styling with TSS
|
|
56
56
|
|
|
57
|
+
First detect `purgetss/` or `purgetss/config.cjs`. If present, invoke the
|
|
58
|
+
`purgetss` skill, use verified utilities, and do not edit generated
|
|
59
|
+
`app/styles/app.tss`. The manual TSS organization below applies when PurgeTSS is
|
|
60
|
+
not present or to specific properties for which that skill confirms no utility.
|
|
61
|
+
|
|
57
62
|
### TSS file organization
|
|
58
63
|
|
|
59
|
-
Every controller can have a matching TSS file. Alloy also supports a
|
|
64
|
+
Every controller can have a matching TSS file. Standard Alloy also supports a
|
|
65
|
+
manually maintained global `app.tss`.
|
|
60
66
|
|
|
61
67
|
```
|
|
62
68
|
app/styles/
|
|
@@ -95,7 +101,7 @@ app/styles/
|
|
|
95
101
|
| Selector | Use for | Example |
|
|
96
102
|
| ------------------- | --------------------------- | ---------------------- |
|
|
97
103
|
| Element (`"Label"`) | App-wide defaults | Base font, color |
|
|
98
|
-
| Class (`".card"`) | Reusable
|
|
104
|
+
| Class (`".card"`) | Reusable visual pattern | Cards, buttons, inputs |
|
|
99
105
|
| ID (`"#name"`) | Unique element positioning | Specific layout needs |
|
|
100
106
|
|
|
101
107
|
### Class selectors in XML
|
|
@@ -127,7 +133,7 @@ When an element has both an ID and classes, properties merge with ID taking prio
|
|
|
127
133
|
<Button id="saveButton" class="btn-primary" title="L('save')" />
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
### Building a design system in app.tss
|
|
136
|
+
### Building a standard Alloy design system in app.tss
|
|
131
137
|
|
|
132
138
|
Define reusable style classes for consistency:
|
|
133
139
|
|
|
@@ -281,43 +281,42 @@ async function loadData() {
|
|
|
281
281
|
|
|
282
282
|
## Modal and dialog patterns
|
|
283
283
|
|
|
284
|
+
Declare the app-owned `dialog` and `bottomSheet` Widgets in the window XML.
|
|
285
|
+
Attach them after the host exists and destroy them from the controller cleanup.
|
|
286
|
+
Keep a modal Window for a distinct multi-step task, not for ordinary feedback.
|
|
287
|
+
|
|
284
288
|
```javascript
|
|
285
|
-
|
|
289
|
+
function attachFeedback(hostWindow) {
|
|
290
|
+
$.dialog.attach(hostWindow)
|
|
291
|
+
$.bottomSheet.attach(hostWindow)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Critical, irreversible confirmation
|
|
286
295
|
function showConfirmDelete(itemName) {
|
|
287
|
-
|
|
296
|
+
$.dialog.show({
|
|
288
297
|
title: L('confirm_delete'),
|
|
289
298
|
message: String.format(L('delete_message'), itemName),
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
destructive:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
dialog.addEventListener('click', (e) => {
|
|
296
|
-
if (e.index === 1) {
|
|
297
|
-
performDelete()
|
|
298
|
-
}
|
|
299
|
+
cancelTitle: L('cancel'),
|
|
300
|
+
confirmTitle: L('delete'),
|
|
301
|
+
destructive: true,
|
|
302
|
+
tone: 'warning',
|
|
303
|
+
onConfirm: performDelete
|
|
299
304
|
})
|
|
300
|
-
|
|
301
|
-
dialog.show()
|
|
302
305
|
}
|
|
303
306
|
|
|
304
|
-
//
|
|
307
|
+
// Contextual choices
|
|
305
308
|
function showSortOptions() {
|
|
306
|
-
const
|
|
309
|
+
const items = [
|
|
310
|
+
{ id: 'name', title: L('sort_name') },
|
|
311
|
+
{ id: 'date', title: L('sort_date') },
|
|
312
|
+
{ id: 'price', title: L('sort_price') }
|
|
313
|
+
]
|
|
307
314
|
|
|
308
|
-
|
|
315
|
+
$.bottomSheet.show({
|
|
309
316
|
title: L('sort_by'),
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
dialog.addEventListener('click', (e) => {
|
|
315
|
-
if (e.index < options.length) {
|
|
316
|
-
applySortOption(e.index)
|
|
317
|
-
}
|
|
317
|
+
items,
|
|
318
|
+
onSelect: item => applySortOption(item.id)
|
|
318
319
|
})
|
|
319
|
-
|
|
320
|
-
dialog.show()
|
|
321
320
|
}
|
|
322
321
|
|
|
323
322
|
// Modal Window
|
|
@@ -341,6 +340,14 @@ function openModal(route, params) {
|
|
|
341
340
|
|
|
342
341
|
return controller
|
|
343
342
|
}
|
|
343
|
+
|
|
344
|
+
function cleanup() {
|
|
345
|
+
$.dialog.destroy()
|
|
346
|
+
$.bottomSheet.destroy()
|
|
347
|
+
$.destroy()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
$.cleanup = cleanup
|
|
344
351
|
```
|
|
345
352
|
|
|
346
353
|
## Tabgroup pattern
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<!-- TOC-START -->
|
|
4
4
|
## Contents
|
|
5
5
|
|
|
6
|
+
- [Reading these examples](#reading-these-examples)
|
|
6
7
|
- [API client service](#api-client-service)
|
|
7
8
|
- [Native module wrapper service](#native-module-wrapper-service)
|
|
8
9
|
- [i18n helper](#i18n-helper)
|
|
@@ -17,6 +18,19 @@
|
|
|
17
18
|
|
|
18
19
|
<!-- TOC-END -->
|
|
19
20
|
|
|
21
|
+
## Reading these examples
|
|
22
|
+
|
|
23
|
+
Snippets that call `$.dialog`, `$.bottomSheet`, or `$.snackbar` expect the app to
|
|
24
|
+
own those surfaces as Alloy Widgets under `app/widgets/`. Their public API,
|
|
25
|
+
queueing, and cleanup rules live in [Feedback Widget Contracts](feedback-widget-contracts.md);
|
|
26
|
+
[Feedback Surfaces](feedback-surfaces.md) covers which surface a given event
|
|
27
|
+
deserves. The `<Widget>` declaration alone will not compile until those Widgets
|
|
28
|
+
exist and are registered in `app/config.json`.
|
|
29
|
+
|
|
30
|
+
Styles below carry literal hex values so each example reads on its own. Production
|
|
31
|
+
code should name semantic color roles instead, so light and dark resolve without a
|
|
32
|
+
second code path — see [Theming and dark mode](theming.md).
|
|
33
|
+
|
|
20
34
|
## API client service
|
|
21
35
|
Standard logic for network requests.
|
|
22
36
|
|
|
@@ -350,6 +364,8 @@ $.cleanup = cleanup
|
|
|
350
364
|
<ImageView id="emptyIcon" image="/images/search.png" />
|
|
351
365
|
<Label id="emptyLabel" text="L('no_results')" />
|
|
352
366
|
</View>
|
|
367
|
+
|
|
368
|
+
<Widget id="bottomSheet" src="com.example.ui.bottomsheet" />
|
|
353
369
|
</Window>
|
|
354
370
|
</Alloy>
|
|
355
371
|
```
|
|
@@ -375,6 +391,7 @@ let searchQuery = ''
|
|
|
375
391
|
let debounceTimer = null
|
|
376
392
|
|
|
377
393
|
function init() {
|
|
394
|
+
$.bottomSheet.attach($.contactsWindow)
|
|
378
395
|
loadData()
|
|
379
396
|
|
|
380
397
|
$.searchBar.addEventListener('cancel', clearSearch)
|
|
@@ -395,21 +412,23 @@ function clearSearch() {
|
|
|
395
412
|
}
|
|
396
413
|
|
|
397
414
|
function showFilters() {
|
|
398
|
-
const
|
|
415
|
+
const items = [
|
|
416
|
+
{ id: 'all', title: L('all') },
|
|
417
|
+
{ id: 'work', title: L('work') },
|
|
418
|
+
{ id: 'personal', title: L('personal') },
|
|
419
|
+
{ id: 'family', title: L('family') }
|
|
420
|
+
]
|
|
421
|
+
|
|
422
|
+
$.bottomSheet.show({
|
|
399
423
|
title: L('filter_by_category'),
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
dialog.addEventListener('click', (e) => {
|
|
405
|
-
if (e.index < 4) {
|
|
406
|
-
activeFilters.category = e.index === 0 ? null : ['work', 'personal', 'family'][e.index - 1]
|
|
424
|
+
items,
|
|
425
|
+
selectedId: activeFilters.category || 'all',
|
|
426
|
+
onSelect: item => {
|
|
427
|
+
activeFilters.category = item.id === 'all' ? null : item.id
|
|
407
428
|
updateFilterTags()
|
|
408
429
|
applyFilters()
|
|
409
430
|
}
|
|
410
431
|
})
|
|
411
|
-
|
|
412
|
-
dialog.show()
|
|
413
432
|
}
|
|
414
433
|
|
|
415
434
|
function applyFilters() {
|
|
@@ -465,6 +484,7 @@ function createFilterTag(label, onRemove) {
|
|
|
465
484
|
|
|
466
485
|
function cleanup() {
|
|
467
486
|
clearTimeout(debounceTimer)
|
|
487
|
+
$.bottomSheet.destroy()
|
|
468
488
|
$.searchBar.removeEventListener('change', onSearchChange)
|
|
469
489
|
$.destroy()
|
|
470
490
|
}
|
|
@@ -530,6 +550,7 @@ $.cleanup = cleanup
|
|
|
530
550
|
<Switch id="termsSwitch" />
|
|
531
551
|
<Label id="termsLabel" text="L('accept_terms')" />
|
|
532
552
|
</View>
|
|
553
|
+
<Label id="termsError" />
|
|
533
554
|
|
|
534
555
|
<!-- Register Button -->
|
|
535
556
|
<Button id="registerBtn"
|
|
@@ -544,6 +565,8 @@ $.cleanup = cleanup
|
|
|
544
565
|
</View>
|
|
545
566
|
</View>
|
|
546
567
|
</ScrollView>
|
|
568
|
+
|
|
569
|
+
<Widget id="dialog" src="com.example.ui.dialog" />
|
|
547
570
|
</Window>
|
|
548
571
|
</Alloy>
|
|
549
572
|
```
|
|
@@ -570,6 +593,7 @@ $.cleanup = cleanup
|
|
|
570
593
|
"#termsRow": { layout: 'horizontal', left: 16, right: 16, top: 24, width: Ti.UI.FILL }
|
|
571
594
|
"#termsSwitch": { width: 48 }
|
|
572
595
|
"#termsLabel": { left: 8 }
|
|
596
|
+
"#termsError": { left: 16, right: 16, top: 4, font: { fontSize: 12 }, color: '#ef4444', visible: false }
|
|
573
597
|
"#registerBtn": { left: 16, right: 16, top: 24, height: 56, borderRadius: 12, backgroundColor: '#2563eb', color: '#fff', font: { fontWeight: 'bold' } }
|
|
574
598
|
"#loginRow": { layout: 'horizontal', top: 16 }
|
|
575
599
|
"#haveAccountLabel": { font: { fontSize: 14 }, color: '#4b5563' }
|
|
@@ -611,6 +635,8 @@ const validators = {
|
|
|
611
635
|
}
|
|
612
636
|
|
|
613
637
|
function init() {
|
|
638
|
+
$.dialog.attach($.registerWindow)
|
|
639
|
+
|
|
614
640
|
// Field navigation
|
|
615
641
|
$.nameField.addEventListener('return', () => $.emailField.focus())
|
|
616
642
|
$.emailField.addEventListener('return', () => $.passwordField.focus())
|
|
@@ -658,16 +684,14 @@ function validateAll() {
|
|
|
658
684
|
const emailValid = validateField('email')
|
|
659
685
|
const passwordValid = validateField('password')
|
|
660
686
|
const confirmValid = validateField('confirm')
|
|
687
|
+
const termsValid = $.termsSwitch.value
|
|
661
688
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}).show()
|
|
667
|
-
return false
|
|
668
|
-
}
|
|
689
|
+
$.termsError.applyProperties({
|
|
690
|
+
text: termsValid ? '' : L('error_accept_terms'),
|
|
691
|
+
visible: !termsValid
|
|
692
|
+
})
|
|
669
693
|
|
|
670
|
-
return nameValid && emailValid && passwordValid && confirmValid
|
|
694
|
+
return nameValid && emailValid && passwordValid && confirmValid && termsValid
|
|
671
695
|
}
|
|
672
696
|
|
|
673
697
|
async function onRegister() {
|
|
@@ -686,10 +710,12 @@ async function onRegister() {
|
|
|
686
710
|
Navigation.replace('main')
|
|
687
711
|
|
|
688
712
|
} catch (error) {
|
|
689
|
-
|
|
713
|
+
$.dialog.show({
|
|
690
714
|
title: L('error'),
|
|
691
|
-
message: error.message
|
|
692
|
-
|
|
715
|
+
message: error.message,
|
|
716
|
+
tone: 'error',
|
|
717
|
+
confirmTitle: L('close')
|
|
718
|
+
})
|
|
693
719
|
} finally {
|
|
694
720
|
setLoading(false)
|
|
695
721
|
}
|
|
@@ -707,6 +733,7 @@ function goToLogin() {
|
|
|
707
733
|
}
|
|
708
734
|
|
|
709
735
|
function cleanup() {
|
|
736
|
+
$.dialog.destroy()
|
|
710
737
|
$.destroy()
|
|
711
738
|
}
|
|
712
739
|
|