squared 0.3.14 → 0.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.
data/README.md CHANGED
@@ -1,464 +1,1362 @@
1
- # squared 0.3
1
+ # squared 5.4
2
2
 
3
- * [source](https://github.com/anpham6/squared-ruby)
4
- * [manifest](https://github.com/anpham6/squared-repo)
5
- * [docs](https://squared.readthedocs.io)
3
+ ## Documentation
6
4
 
7
- ## Version Compatibility
5
+ ### HTML
8
6
 
9
- | Date | squared | Min | Max | Git |
10
- | :--------: | ------: | -----: | -----: | -----: |
11
- | 2024-12-07 | 0.1.0 | 2.4.0 | 3.3.6 | 2.39 |
12
- | 2025-01-07 | 0.2.0 | 2.4.0 | 3.4.0 | 2.39 |
13
- | 2025-02-07 | 0.3.0 | 2.4.0 | 3.4.1 | 2.39 |
7
+ * [squared](https://squared.readthedocs.io)
8
+ * [E-mc](https://e-mc.readthedocs.io)
14
9
 
15
- The range chart indicates the latest Ruby tested against at the time of release.
10
+ > [!NOTE]
11
+ > Content in the README was migrated into `Read the Docs`. The file is no longer fully maintained.
12
+
13
+ ### README
14
+
15
+ * [squared-express](https://github.com/anpham6/squared-express#readme)
16
+ * [E-mc](https://github.com/anpham6/e-mc#readme)
17
+ * [Pi-r](https://github.com/anpham6/pi-r#readme)
16
18
 
17
19
  ## Installation
18
20
 
21
+ * NodeJS 16 LTS
22
+
23
+ ### NPX
24
+
19
25
  ```sh
20
- gem install squared
26
+ > npm init
27
+ > npm i sqd-cli sqd-serve
28
+
29
+ > npx sqd init
30
+ # OR
31
+ > npx sqd init --public --local-serve # Same as squared-express
32
+
33
+ > npx sqd serve --access-all
34
+ # OR
35
+ > node serve.cjs --access-all
21
36
  ```
22
37
 
23
- ### Optional
38
+ ```sh
39
+ > npm init
40
+ > npm i squared sqd-serve
41
+
42
+ > mkdir dist html
43
+ > cp -r ./node_modules/squared/dist/* ./dist
44
+ > cp ./node_modules/squared/html/* ./html # optional
45
+ > cp ./node_modules/sqd-serve/config/json/* . # yaml
24
46
 
25
- * [Repo](https://source.android.com/docs/setup/reference/repo)
26
- * Python 3.6
27
- * Not compatible with Windows
47
+ > npx serve
48
+ ```
49
+
50
+ ### GitHub
28
51
 
29
52
  ```sh
30
- mkdir -p ~/.bin
31
- PATH="${HOME}/.bin:${PATH}"
32
- curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
33
- chmod a+rx ~/.bin/repo
53
+ > git clone https://github.com/anpham6/squared
54
+ > cd squared
55
+
56
+ > npm i
57
+ > npm run prod
58
+
59
+ > cd ..
60
+
61
+ > git clone https://github.com/anpham6/squared-express
62
+ > cd squared-express
63
+
64
+ > npm i
65
+ > npm run prod
66
+ > npm run deploy # deploy:yaml
67
+
68
+ > cd ../squared
69
+
70
+ > node serve.cjs --access-all # squared.{json,yml}
34
71
  ```
35
72
 
36
- ## Example - Rakefile
73
+ ### Repo
37
74
 
38
- Projects from any accessible folder can be added relative to the parent directory (e.g. *REPO_ROOT*) or absolutely. The same Rakefile can also manage other similarly cloned `Repo` repositories remotely by setting the `REPO_ROOT` environment variable to the location. Missing projects will simply be excluded from the task runner.
75
+ * Python 3.6+
76
+ * [Commands](https://source.android.com/docs/setup/reference/repo)
39
77
 
40
- ```ruby
41
- require "squared"
78
+ #### Install
42
79
 
43
- require "squared/workspace"
44
- require "squared/workspace/repo" # Optional
45
- require "squared/workspace/project/node" #
46
- require "squared/workspace/project/python" #
47
- require "squared/workspace/project/ruby" #
80
+ ```sh
81
+ mkdir -p ~/bin/repo
82
+ PATH="${HOME}/bin:${PATH}"
83
+
84
+ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
85
+ chmod a+rx ~/bin/repo
48
86
  # OR
49
- require "squared/app" # All workspace related modules
87
+ scripts/repo-install.sh ~/bin
88
+ ```
89
+
90
+ #### Usage
50
91
 
51
- # NODE_ENV = production
92
+ ```sh
93
+ mkdir workspaces
94
+ cd workspaces
95
+
96
+ repo init -u https://github.com/anpham6/squared-repo -m latest.xml
97
+ repo sync -j4
52
98
 
53
- # REPO_ROOT = /workspaces #
54
- # REPO_HOME = /workspaces/squared # Dir.pwd
55
- # rake = /workspaces/squared/Rakefile # main?
99
+ cd squared
100
+ npm i
101
+ ```
102
+
103
+ #### Ruby
104
+
105
+ Workspace management uses [Ruby](https://www.ruby-lang.org/en/documentation/installation) for syncing and building. It is not installed by default on most operating systems.
106
+
107
+ ```sh
108
+ mkdir workspaces
109
+ cd workspaces # REPO_ROOT
110
+
111
+ wget https://unpkg.com/squared/Rakefile
112
+
113
+ rake -T # List tasks
114
+
115
+ # REPO_BUILD={dev,prod}
116
+ # PIPE_FAIL=1
117
+ rake repo:init # nightly
118
+ rake repo:init[latest] # squared
119
+ rake repo:init[0.11.x] # e-mc
56
120
  # OR
57
- # REPO_ROOT = /workspaces # Dir.pwd
58
- # rake = /workspaces/Rakefile #
59
- # REPO_HOME = /workspaces/squared # main: "squared"
60
-
61
- # pathname = /workspaces/pathname
62
- # optparse = /workspaces/optparse
63
- # log = /workspaces/logger
64
- # emc = /workspaces/e-mc
65
- # pir = /workspaces/pi-r
66
- # squared = /workspaces/squared
67
- # cli = /workspaces/squared/publish/sqd-cli
68
- # sqd-serve = /workspaces/squared/publish/sqd-serve
69
- # sqd = /workspaces/squared/sqd
70
-
71
- Workspace::Application
72
- .new(Dir.pwd, main: "squared") # Dir.pwd? (main? is implicitly basename)
73
- .banner("group", "project", styles: ["yellow", "black"], border: "bold") # name | project | path | ref | group? | parent? | version?
74
- .repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "build:prod"], ref: :node) # Repo (optional)
75
- .run("rake install", ref: :ruby)
76
- .depend(false, group: "default")
77
- .clean("rake clean", group: "default")
78
- .clean(["build/"], group: "app")
79
- .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
80
- .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
81
- "CFLAGS" => "-fPIC -O1"
82
- })
83
- .add("optparse", doc: "rake rdoc", group: "default") # Uses bundler/gem_tasks (without C extensions)
84
- .add("logger", copy: { from: "lib", glob: "**/*.rb", into: "~/.rvm/gems/ruby-3.4.0/gems/logger-1.6.1" }, clean: ["tmp/"]) # autodetect: true
85
- .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
86
- .add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
87
- .add("squared", script: ["build:stage1", "build:stage2"], group: "app") do # Copy target (main)
88
- # Repo (global)
89
- as(:run, "build:dev", "dev") # npm run build:dev -> npm run dev
90
- as(:run, { "build:dev": "dev", "build:prod": "prod" })
91
-
92
- add("publish/sqd-cli", "cli", exclude: [:git]) # rake cli:build
93
- add("publish/sqd-serve") # rake sqd-serve:build
94
- add("publish/sqd-admin", group: "sqd", exclude: [:base])
95
- # OR
96
- with(exclude: [:base]) { add("publish/*", "packages") } # rake packages:sqd-serve:build
97
- # OR
98
- add(["publish/sqd-cli", "publish/sqd-serve", "publish/sqd-admin"], true, exclude: [:base]) # rake squared:sqd-serve:build
99
- end
100
- .add("squared/sqd", exclude: :git, pass: [:node, 'checkout', 'bump']) do # Skip initialize(:node) + squared:checkout:* + squared:bump:*
101
- variable_set :script, "build:sqd" # Override detection
102
- variable_set :depend, false
103
- variable_set :clean, ["build/sqd/"]
104
- end
105
- .pass("pull", group: "default") { test? || doc? } # pathname:pull | optparse:pull
106
- .style("banner", 255.255) # 256 colors (fg | fg.bg | -0.bg)
107
- .build(default: "build", parallel: ["pull", "fetch", "rebase", "copy", "clean", /^outdated:/], pass: ['publish']) do |workspace|
108
- workspace
109
- .enable_aixterm
110
- .style({
111
- banner: ["bright_cyan", "bold", "bright_black!"],
112
- border: "bright_white"
113
- })
114
- end
115
-
116
- # default = /workspaces/ruby/*
117
- # pathname = /workspaces/ruby/pathname
118
- # optparse = /workspaces/ruby/optparse
119
- # logger = /workspaces/ruby/logger
120
- # android = /workspaces/android-docs
121
- # chrome = /workspaces/chrome-docs
122
-
123
- Workspace::Application
124
- .new(ENV["SQUARED_HOME"], prefix: "rb", common: false) # Local styles
125
- .group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", ref: :ruby, override: {
126
- pathname: {
127
- run: "rake compile" # rake rb:pathname:build
128
- }
129
- })
130
- .with(:python) do # ref=Symbol | group=String
131
- banner([:name, ": ", :version], "path") # chrome-docs: 0.1.0 | /workspaces/chrome-docs
132
- doc("make html") # rake rb:doc:python
133
- run(false) # rake rb:build:python (disabled)
134
- exclude(%i[base git]) # Project::Git.ref (superclass)
135
- add("android-docs", "android") # rake rb:android:doc
136
- add("chrome-docs", "chrome") # rake rb:chrome:doc
137
- end
138
- .style("inline", "bold")
139
- .build
140
- ```
141
-
142
- **NOTE**: The use of "**ref**" (class name) is only necessary when initializing an empty directory (e.g. *rake repo:init*).
143
-
144
- ### Clone
145
-
146
- The task is only active when the project directory is empty or does not exist.
147
-
148
- ```ruby
149
- Workspace::Application
150
- .new(main: "squared")
151
- .git(
152
- "emc": "https://github.com/anpham6/e-mc", # rake emc:clone
153
- "pir": { # rake pir:clone
154
- uri: "https://github.com/anpham6/pi-r", #
155
- options: { #
156
- "origin": "github", # --origin='github'
157
- "recurse-submodules": false, # --no-recurse-submodules
158
- "shallow-exclude": ["v0.0.1", "v0.0.2"] # --shallow-exclude='v0.0.1' --shallow-exclude='v0.0.2'
121
+ REPO_ROOT=/tmp/123 NODE_INSTALL=pnpm repo:init
122
+ # OR - without Repo
123
+ rake clone:node # master
124
+ rake clone # node + docs
125
+ ```
126
+
127
+ > [!TIP]
128
+ > Use the supplied Rakefile inside the **squared** project folder once the source has been downloaded.
129
+
130
+ #### Docker
131
+
132
+ ```sh
133
+ # NODE_TAG=latest
134
+ # RUBY_VERSION=2.4.0-3.3.0
135
+ # MANIFEST=nightly,staging,prod,android
136
+ # BUILD={dev,prod}
137
+ # DEV={0,1,local}
138
+ # DOCS=any
139
+ # PIPE_FAIL={0,1}
140
+ # PORT=3000
141
+ docker build -t squared --build-arg MANIFEST=prod --build-arg SQUARED=prod .
142
+ docker build -t node --build-arg NODE_TAG=20 --build-arg NODE_INSTALL=pnpm -f Dockerfile.slim . # no docs
143
+ docker buildx bake node
144
+ # OR
145
+ # RUBY_TAG=latest
146
+ # NODE_VERSION=22.x
147
+ docker build -t ruby --build-arg RUBY_TAG=3.0.0 --build-arg NODE_VERSION=20.x --build-arg PIPE_FAIL=0 -f Dockerfile.ruby .
148
+ docker buildx bake ruby
149
+ # OR
150
+ # NGINX_VERSION=1.27
151
+ # NGINX_VARIANT=bookworm
152
+ docker build -t nginx --build-arg NGINX_VERSION=1.27 --build-arg PORT=3000 --build-arg NODE_VERSION=18.x -f Dockerfile.nginx .
153
+ docker buildx bake nginx
154
+
155
+ # Express
156
+ docker run -it --name express --rm -p 3000:3000 \
157
+ --mount type=bind,src=${PWD},dst=/workspaces/squared/.config \
158
+ --mount type=bind,src=${PWD}/html,dst=/workspaces/squared/www \
159
+ squared
160
+
161
+ docker run -it --name express --rm -p 443:443 --build-arg PORT=443 squared \
162
+ serve --access-all --https --env=production
163
+
164
+ # Terminal
165
+ docker run -it --name debian squared /bin/bash # irb
166
+ ```
167
+
168
+ ### Browser
169
+
170
+ * Download (squared@version): https://unpkg.com/squared
171
+ * Global JS variable: **squared**
172
+ * ES2018
173
+
174
+ > - https://unpkg.com/squared/dist/squared.min.js
175
+ > - https://unpkg.com/squared/dist/squared.base-dom.min.js
176
+ > - https://unpkg.com/squared/dist/vdom.framework.min.js
177
+
178
+ > * https://unpkg.com/squared/dist/squared.min.js
179
+ > * https://unpkg.com/squared/dist/vdom-lite.framework.min.js
180
+
181
+ ## Usage
182
+
183
+ Library files are in the `/dist` folder. A minimum of **two** files are required to run *squared*.
184
+
185
+ 1. squared
186
+ 2. squared-base - *required: except vdom-lite*
187
+ 3. **squared-svg** - *optional*
188
+ 4. framework (e.g. **android** | **chrome** | vdom | vdom-lite)
189
+ 5. extensions - *optional*
190
+
191
+ Usable combinations: 1-2-4 + 1-2-4-5 + 1-2-3-4-5 + 1-vdom-lite
192
+
193
+ File bundles for common combinations are available in the `/dist/bundles` folder and do not require a call to **setFramework**.
194
+
195
+ > [!WARNING]
196
+ > Libraries in bold are transpiled with **ES2020**.
197
+
198
+ ### Example: android
199
+
200
+ The primary function `parseDocument` can be called on multiple elements and multiple times per session. The application will continuously and progressively build the layout files into a single entity with combined shared resources.
201
+
202
+ * ES2020
203
+
204
+ > [!CAUTION]
205
+ > Using `parseDocumentSync` is not recommended when your page has images or fonts.
206
+
207
+ ```html
208
+ <script src="/dist/squared.min.js"></script>
209
+ <script src="/dist/squared.base.min.js"></script>
210
+ <script src="/dist/squared.svg.min.js"></script>
211
+ <script src="/dist/android.framework.min.js"></script>
212
+ <script>
213
+ squared.settings.targetAPI = 35; // Optional
214
+
215
+ document.addEventListener("DOMContentLoaded", async () => {
216
+ squared.setFramework(android, {/* settings */});
217
+
218
+ await squared.parseDocument(): Node // document.body (default)
219
+ // OR
220
+ await squared.parseDocument(/* HTMLElement */, /* "fragment-id" */, /* ...etc */): Node[]
221
+ // OR
222
+ await squared.parseDocument(
223
+ { // Custom settings do not affect other layouts
224
+ element: document.body,
225
+ projectId: "project-1", // Default is "_"
226
+ resourceQualifier: "land", // "res/*" folder suffix
227
+ /* OR */
228
+ resourceQualifier: {
229
+ suffix: "land", // Used for "true" or "undefined" groups (optional)
230
+ layout: true, // Will copy to "res/layout-land" when "suffix" is defined
231
+ string: undefined, // Will copy to default location "res/values" when "suffix" is undefined
232
+ font: false, // Will not copy anything to "res/font" or "res/font-land"
233
+ image: "hdpi", // Will copy to "res/drawable-hdpi"
234
+ video: "w720dp", // Will copy to "res/raw-w720dp"
235
+ audio: "w720dp", // Same as "video" and treated separately
236
+ animation: "v34", // Will copy to "res/anim-v34"
237
+ menu: "" // Will copy to default location "res/menu"
238
+ /* integer + fraction + array + color + dimension + style + theme = Same as "string" */
239
+ },
240
+ enabledMultiline: false,
241
+ enabledSubstitute: true,
242
+ include: ["android.substitute"], // Automatically removed after finalize
243
+ exclude: ["squared.list", "squared.grid"], // Disabled only during parseDocument
244
+ excludeQuery: [{
245
+ selector: "main > article", // Hide elements
246
+ /* OR */
247
+ resource: squared.base.lib.constant.NODE_RESOURCE.BOX_STYLE, // Exclusions during processing
248
+ procedure: squared.base.lib.constant.NODE_PROCEDURE.OPTIMIZATION,
249
+ section: squared.base.lib.constant.APP_SECTION.DOM_TRAVERSE
250
+ }],
251
+ customizationsBaseAPI: -1,
252
+ observe(mutations, observer, settings) { // Uses MutationObserver
253
+ squared.reset(); // Required when calling "parseDocument" after a File action
254
+ squared.parseDocument(settings).then(() => {
255
+ squared.copyTo("/path/project", { modified: true }).then(response => console.log(response));
256
+ });
257
+ },
258
+ afterCascade(sessionId, node) {/* Restore previous state */},
259
+ beforeRender(layout: LayoutUI) {/* Edit initial values */},
260
+ afterFinalize(node: NodeUI) {/* Edit controller values */}
261
+ },
262
+ { // Only "element" is required
263
+ element: "fragment-1",
264
+ projectId: "project-1", // Implicit once projectId is not "_"
265
+ resourceQualifier: "land",
266
+ pathname: "app/src/main/res/layout-hdpi", // Will not be overridden by resourceQualifier "land"
267
+ filename: "fragment.xml",
268
+ baseLayoutAsFragment: {
269
+ name: "androidx.navigation.fragment.NavHostFragment",
270
+ documentId: "main_content",
271
+ app: {
272
+ navGraph: "@navigation/product_list_graph",
273
+ defaultNavHost: "true"
274
+ }
275
+ },
276
+ beforeCascade(sessionId) {
277
+ document.getElementById("fragment-id").style.display = "block"; // Use inline styles
278
+ }
279
+ }
280
+ );
281
+ await squared.parseDocument({
282
+ element: "fragment-2",
283
+ projectId: "sqd2", // Explicit
284
+ resourceQualifier: "port", // Will not conflict with projectId "project-1"
285
+ enabledFragment: true,
286
+ fragmentableElements: [
287
+ {
288
+ selector: "main", // querySelector
289
+ name: "androidx.navigation.fragment.NavHostFragment",
290
+ filename: "navigation.xml",
291
+ documentId: "main_content"
292
+ },
293
+ "main > article" // Declarative double nested fragments are invalid (querySelectorAll)
294
+ ],
295
+ options: {
296
+ "android.resource.fragment": {
297
+ dynamicNestedFragments: true // FragmentContainerView or FrameLayout as the container (name and tag are ignored)
298
+ }
299
+ }
300
+ });
301
+ // OR - Chromium
302
+ squared.prefetch("css").then(() => squared.parseDocument()); // Cross-origin support
303
+ Promise.all(
304
+ squared.prefetch("css", true), // All stylesheets
305
+ squared.prefetch("css", "./undetected.css", element.shadowRoot),
306
+ squared.prefetch("svg", "http://embedded.example.com/icon.svg", "../images/android.svg")
307
+ )
308
+ .then(() => squared.parseDocument());
309
+
310
+ // Modify attributes
311
+
312
+ const body = squared.findDocumentNode(document.body);
313
+ body.android("layout_width", "match_parent");
314
+ body.lockAttr("android", "layout_width");
315
+
316
+ await squared.close(/* projectId */); // Next call to parseDocument will reset project (optional)
317
+
318
+ squared.kill("30s").then(result => {/* killed when result > 0 */}); // Abort next request in 30 seconds
319
+
320
+ // File actions - implicitly calls "close"
321
+
322
+ await squared.save(/* "project-1" */, /* broadcastId | timeout */); // Uses defaults from settings
323
+ // OR
324
+ await squared.saveAs(/* archive filename */, { projectId: "project-1" });
325
+ await squared.saveAs(/* archive filename */, { timeout: 10 }); // Kills request if not complete in 10 seconds
326
+ await squared.saveAs(/* archive filename */, { throwErrors: true }).catch(err => console.log(err)); // Will cancel partial archive download
327
+ // OR
328
+ await squared.copyTo(/* directory */, {/* options */});
329
+ await squared.copyTo(/* directory */, { modified: true }); // Can be used with observe
330
+ // OR
331
+ await squared.appendTo(/* archive location */, {/* options */});
332
+
333
+ // Other features
334
+
335
+ squared.observe();
336
+ // OR
337
+ await squared.observeSrc(
338
+ "link[rel=stylesheet]", // HTMLElement
339
+ (ev, element) => {
340
+ squared.reset();
341
+ squared.parseDocument().then(() => squared.copyTo("/path/project"));
342
+ },
343
+ { port: 8080, secure: false, action: "reload" /* "hot" */, expires: "1h" } // squared.json: "observe"
344
+ );
345
+
346
+ squared.reset(/* projectId */); // Start new "parseDocument" session (optional)
347
+ });
348
+ </script>
349
+ ```
350
+
351
+ > [!CAUTION]
352
+ > Calling `saveAs` or `copyTo` methods before the images have completely loaded can cause them to be excluded from the generated layout. In these cases you should use the asynchronous `parseDocument` method to set a callback for your commands.
353
+
354
+ ### Example: chrome
355
+
356
+ Used primarly for developing single page layouts but can also bundle assets using query selector syntax. It is adequate for most projects and gives you the ability to develop your application as a module in place.
357
+
358
+ * ES2020
359
+
360
+ ```html
361
+ <script src="/dist/squared.min.js"></script>
362
+ <script src="/dist/squared.base.min.js"></script>
363
+ <script src="/dist/chrome.framework.min.js"></script>
364
+ <script>
365
+ document.addEventListener("DOMContentLoaded", async () => {
366
+ squared.setFramework(chrome, {/* settings */});
367
+
368
+ await squared.save(); // Uses defaults from settings
369
+ // OR
370
+ await squared.saveAs(/* archive filename */, {/* options */});
371
+ // OR
372
+ await squared.copyTo(/* directory */, {/* options */});
373
+ // OR
374
+ await squared.appendTo(/* archive location */, {/* options */});
375
+
376
+ // Observe
377
+ await squared.copyTo(/* directory */, { useOriginalHtmlPage: false, observe: /* Same as Android */ | true /* Auto-reload */}).then(() => squared.observe());
378
+ });
379
+ </script>
380
+ ```
381
+
382
+ ### Example: vdom
383
+
384
+ The most minimal framework possible (*55kb gzipped*) and can be useful when debugging through DevTools. The `lite` version is about half the bundle size and is recommended for most browser applications.
385
+
386
+ * ES2018
387
+
388
+ ```html
389
+ <script src="/dist/squared.min.js"></script>
390
+ <script src="/dist/squared.base-dom.min.js"></script>
391
+ <script src="/dist/vdom.framework.min.js"></script>
392
+ <script>
393
+ document.addEventListener("DOMContentLoaded", async () => {
394
+ squared.setFramework(vdom, {/* settings */});
395
+
396
+ const element = squared.querySelector("body", true /* synchronous */);
397
+ // OR
398
+ const elements = await squared.querySelectorAll("*");
399
+ // OR
400
+ const element = squared.fromElement(document.body, true /* synchronous */);
401
+ // OR
402
+ const elements = await squared.getElementById("content-id").querySelectorAll("*");
403
+ });
404
+ </script>
405
+ ```
406
+
407
+ There are **ES2018** minified versions (\*.min.js) and also **ES2018** non-minified versions.
408
+
409
+ ## User Settings
410
+
411
+ These settings are available in the global variable `squared` to customize your desired output structure. Each framework shares a common set of settings and also a subset of their own settings.
412
+
413
+ ### Example: android
414
+
415
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/settings/android.html)
416
+
417
+ ```javascript
418
+ squared.settings = {
419
+ targetAPI: 35,
420
+ supportRTL: true,
421
+ supportNegativeLeftTop: true,
422
+ preloadImages: true,
423
+ preloadFonts: true,
424
+ preloadLocalFonts: true, // Chromium
425
+ preloadCustomElements: true,
426
+ enabledSVG: true,
427
+ enabledMultiline: true,
428
+ enabledViewModel: true,
429
+ enabledIncludes: false,
430
+ enabledFragment: false,
431
+ enabledSubstitute: false,
432
+ enabledCompose: false,
433
+ dataBindableElements: [], // { selector, attr, expression, namespace?, twoWay? } (see Data Binding section)
434
+ includableElements: [], // { selectorStart, selectorEnd, pathname?, filename?, merge?, viewModel? }
435
+ substitutableElements: [], // { selector, tag, tagChild?, renderChildren?, autoLayout? }
436
+ fragmentableElements: [], // selector | ExtensionFragmentElement
437
+ composableElements: [], // selector or property (see Jetpack Compose section)
438
+ baseLayoutAsFragment: false | "fragment-name" | ["fragment-name", "fragment-tag"] | { selector, pathname?, filename?, name?, tag? }, // ExtensionFragmentElement
439
+ baseLayoutToolsIgnore: "", // Android Studio (e.g. "TooManyViews, HardcodedText")
440
+ fontMeasureAdjust: 0.75, // thicker < 0 | thinner > 0 (data-android-font-measure-adjust)
441
+ lineHeightAdjust: 1.1, // shorter < 1 | taller > 1 (data-android-line-height-adjust)
442
+ preferMaterialDesign: false | "MaterialComponents" | "Material3", // Default is "Material3"
443
+ createDownloadableFonts: true,
444
+ createElementMap: false,
445
+ pierceShadowRoot: true,
446
+ adaptStyleMap: true, // Use rendered values for output
447
+ lockElementSettings: true,
448
+ customizationsBaseAPI: 0, // 0 - All | -1 - None
449
+ customizationsBaseAPI: [0, 33, 34], // Multiple
450
+ removeDeprecatedAttributes: true, // Remove all
451
+ removeDeprecatedAttributes: ["enabled", "singleLine"], // Remove all except "enabled" + "singleLine"
452
+ removeUnusedResourceViewId: false,
453
+ idNamingStyle: "android",
454
+ idNamingStyle: "html", // Use element tagName
455
+ idNamingStyle: {
456
+ "__default__": "html", // Optional
457
+ "DIV": "comments", // HTML is uppercase (comments_1 then comments_2)
458
+ "svg": ["vector", 0], // SVG elements areis lowercase (vector_0 then vector_1)
459
+ "#text": "text", // Plain text
460
+ "::first-letter": "dropcap", // Pseudo element
461
+ "main > section": ["content", 1, 2], // content_1 then content_3
462
+ "form input[type=submit]": function(node) {
463
+ return "submit_" + node.id;
464
+ }
465
+ },
466
+ customizationsOverwritePrivilege: true,
467
+ outputMainFileName: "activity_main.xml",
468
+ outputFragmentFileName: "fragment_main.xml",
469
+ /* Project - parseDocument (first only) */
470
+ resourceQualifier: "", // "land" -> "res/layout-land" | "port" -> "res/layout-port" (appended to every "res" folder)
471
+ resourceSystemColors: {
472
+ "system_accent1_100": "white", // Will be converted to ARGB
473
+ "system_accent1_200": ['#ff0000', 0.75], // opacity
474
+ "system_accent1_300": squared.lib.color.parseColor("#000", 1)
475
+ },
476
+ manifestPackage: "", // OR: RequestData<{ namespace: "android.application.id" }>
477
+ manifestLabelAppName: "android",
478
+ manifestThemeName: "AppTheme",
479
+ manifestParentThemeName: "Theme.AppCompat.Light.NoActionBar",
480
+ manifestActivityName: ".MainActivity",
481
+ outputDocumentEditing: true,
482
+ outputDocumentCSS: [], // CSS properties to be processed at server (e.g. "boxShadow")
483
+ outputDirectory: "app/src/main",
484
+ createManifest: false, // Update AndroidManifest.xml
485
+ createBuildDependencies: false | "ktx" | "baseline-profile" | ["ktx", "baseline-profile"], // Update build.gradle
486
+
487
+ // Not customizable with parseDocument
488
+ builtInExtensions: [
489
+ "squared.accessibility",
490
+ "android.delegate.background",
491
+ "android.delegate.negative-x",
492
+ "android.delegate.positive-x",
493
+ "android.delegate.max-width-height",
494
+ "android.delegate.percent",
495
+ "android.delegate.content",
496
+ "android.delegate.scrollbar",
497
+ "android.delegate.radiogroup",
498
+ "android.delegate.multiline",
499
+ "squared.relative",
500
+ "squared.css-grid",
501
+ "squared.flexbox",
502
+ "squared.table",
503
+ "squared.column",
504
+ "squared.list",
505
+ "squared.grid",
506
+ "squared.sprite",
507
+ "squared.whitespace",
508
+ "android.resource.background",
509
+ "android.resource.svg",
510
+ "android.resource.strings",
511
+ "android.resource.fonts",
512
+ "android.resource.dimens",
513
+ "android.resource.styles",
514
+ "android.resource.data",
515
+
516
+ /* EXCLUDED (breaks layout) */
517
+ "android.resource.includes", // enabledIncludes
518
+ "android.substitute", // enabledSubstitute
519
+ "android.resource.fragment", // enabledFragment
520
+ "jetpack.compose.view" // enabledCompose
521
+ ],
522
+ compressImages: false, // TinyPNG API Key <https://tinypng.com/developers>
523
+ compressImages: "****************", // API key
524
+ convertImages: "", // png | jpeg | webp | gif | bmp
525
+ showAttributes: true,
526
+ showAttributes: {
527
+ "hyphenationFrequency": "full", // Replace all ("android" is the default namespace)
528
+ "android:fontFeatureSettings": null, // Delete all
529
+ "app:menu": [
530
+ "@menu/menu_1", "@menu/menu_2", // Replace with "@menu/menu_2" when value is "@menu/menu_1"
531
+ "@menu/menu_3", null // Delete attribute when value is "@menu/menu_3"
532
+ ],
533
+ /* OR */
534
+ "app:menu": {
535
+ "@menu/menu_1": "@menu/menu_2",
536
+ "@menu/menu_3": null
159
537
  }
538
+ },
539
+ showComments: false | ["boxShadow"] | { self: ["boxShadow"], nextSibling: ["marginBottom"], previousSibling: ["marginTop"], parent: ["position", "top", "left"] }, // TODO in layout.xml
540
+ showComments: { include: { tagName: true | ["button"], attributes: true | ["style"], dataset: false, bounds: true }, self: ["boxShadow", ".className"] },
541
+ showErrorMessages: false,
542
+ convertPixels: "dp", // "sp" | "pt" | "in" | "mm"
543
+ convertLineHeight: "sp", // "dp" | "pt" | "in" | "mm"
544
+ convertEntities: ["numeric"], // v5.4
545
+ convertEntities: ["codepoints", {/* JSON (last) */}], // https://html.spec.whatwg.org/entities.json
546
+ insertSpaces: 4,
547
+ outputDocumentHandler: "android",
548
+ outputEmptyCopyDirectory: false, // Sub directories within target directory (OR: RequestData<{ emptyDir: false }>)
549
+ outputSummaryModal: false | "path/summary.css" | ".status-4 { color: purple; }",
550
+ outputTasks: {
551
+ "**/drawable/*.xml": { handler: "gulp", task: "minify" }
552
+ },
553
+ outputWatch: {
554
+ "**/drawable/*.png": true,
555
+ "**/drawable/*.jpg": { interval: 1000, expires: "2h" }
556
+ },
557
+ outputArchiveName: "android-xml",
558
+ outputArchiveFormat: "zip", // tar | 7z | gz
559
+ outputArchiveCache: false // Downloadable URL in ResponseData<downloadUrl>
560
+ };
561
+
562
+ // Optional
563
+ squared.settings = {
564
+ resolutionDPI: 160, // 320dpi = 2560x1600
565
+ resolutionScreenWidth: 1280,
566
+ resolutionScreenHeight: 800,
567
+ framesPerSecond: 60,
568
+ useShapeGeometryBox: true, // Bounding box uses native SVG method getBbox
569
+ formatUUID: "8-4-4-4-12", // UUID: 8-4-[12345]3-[89ab]3-12
570
+ formatDictionary: "0123456789abcdef",
571
+ outputConfigName: "sqd.config",
572
+ observePort: 8080,
573
+ observeSecurePort: 8443,
574
+ observeExpires: "1h", // Server defaults will be used
575
+ broadcastPort: 3080,
576
+ broadcastSecurePort: 3443
577
+ };
578
+ ```
579
+
580
+ ### Example: chrome
581
+
582
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/settings/chrome.html)
583
+
584
+ ```javascript
585
+ squared.settings = {
586
+ preloadImages: false,
587
+ preloadFonts: false,
588
+ preloadLocalFonts: false,
589
+ preloadCustomElements: false,
590
+ excludePlainText: true,
591
+ createElementMap: true,
592
+ pierceShadowRoot: true,
593
+ adaptStyleMap: false,
594
+ builtInExtensions: [],
595
+ showErrorMessages: false,
596
+ webSocketPort: 80,
597
+ webSocketSecurePort: 443,
598
+ outputDocumentHandler: "chrome",
599
+ outputEmptyCopyDirectory: false,
600
+ outputSummaryModal: false,
601
+ outputTasks: {
602
+ "*.js": [{ handler: "gulp", task: "minify" }, { handler: "gulp", task: "beautify" }]
603
+ },
604
+ outputWatch: { "*": true },
605
+ outputArchiveName: "chrome-data",
606
+ outputArchiveFormat: "zip",
607
+ outputArchiveCache: false
608
+ };
609
+
610
+ // Optional (Same as Android)
611
+
612
+ ```
613
+
614
+ ### Example: vdom
615
+
616
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/settings/vdom.html)
617
+
618
+ ```javascript
619
+ squared.settings = {
620
+ createElementMap: true,
621
+ pierceShadowRoot: false,
622
+ adaptStyleMap: false,
623
+ builtInExtensions: [],
624
+ showErrorMessages: false
625
+ };
626
+ ```
627
+
628
+ ## Local Storage
629
+
630
+ Custom named user settings per framework can be saved to local storage as JSON and reused across all pages in the same domain. Extensions are configured using the same procedure.
631
+
632
+ ```javascript
633
+ // Save
634
+ squared.setFramework(android, { compressImages: true }, "android-example");
635
+
636
+ // Load
637
+ squared.setFramework(android, "android-example");
638
+ ```
639
+
640
+ ```javascript
641
+ // Save
642
+ await squared.copyTo("/path/project", {/* options will be saved */}, "copy-example", true); // Will overwrite and not merge with previously saved settings
643
+
644
+ // Load
645
+ await squared.copyTo("/path/project", {/* takes precedence */}, "http://localhost:3000/copy-to/base-config.json"); // Object.assign({ base-config.json }, options)
646
+ await squared.copyTo("/path/project", {/* takes precedence */}, "copy-example"); // Object.assign({ copy-example }, options)
647
+
648
+ await squared.copyTo("/path/project", "http://localhost:3000/copy-to/base-config.json"); // options = { base-config.json }
649
+ await squared.copyTo("/path/project", "copy-example"); // options = { copy_example }
650
+ ```
651
+
652
+ ## Public Properties and Methods
653
+
654
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/methods/squared.html)
655
+
656
+ ```javascript
657
+ .settings // See user preferences section
658
+
659
+ setFramework(app: {}, options?: PlainObject, setting?: string, cache?: boolean) // Install application interpreter
660
+ setFramework(app: {}, loadName: string, cache?: boolean) // Load settings from local storage
661
+
662
+ // http - hostname(:port)? | https - hostname:443
663
+ setHostname(value: string /* http(s)://hostname(:port) */) // Use another cors-enabled server for processing files (--cors <origin>)
664
+
665
+ setEndpoint(name: string, value: string) // Set alternate pathname for API v1 functions (ASSETS_COPY | ASSETS_ARCHIVE | LOADER_DATA | THREADS_KILL | WEBSOCKET_OBSERVE)
666
+ setLocalAddress(...values: (string | URL | Location)[]) // Additional hostnames which are interpreted as localhost (e.g. http://127.0.0.1)
667
+
668
+ prefetch(type: "css" | "javascript" | "image" | "svg", all?: boolean, ...targets: unknown[]) // Cross-origin support for CSS
669
+
670
+ parseDocument(...elements: (HTMLElement | string | ElementSettings)[]) // See installation section (Promise)
671
+ parseDocumentSync(...elements: (HTMLElement | string | ElementSettings)[]) // Skips preloadImages and preloadFonts (synchronous)
672
+
673
+ latest(count?: number) // Most recent parseDocument session ids (1 newest / -1 oldest: string, other: string[])
674
+
675
+ auth(token: string) // Set JWT authorization token for all requests
676
+
677
+ save(projectId?: string) // Save current session to a new archive using default settings
678
+ save(projectId?: string, broadcastId?: string)
679
+ save(projectId?: string, timeout?: number)
680
+
681
+ close(projectId?: string) // Close current session
682
+ reset(projectId?: string) // Clear cache and reopen new session
683
+ clear() // Clear all data stored in memory
684
+
685
+ toString() // Current framework loaded
686
+ toString(projectId: string) // await squared.close(projectId) (required)
687
+
688
+ add(...names: (string | Extension | ExtensionRequestObject)[]) // See extension configuration section
689
+ remove(...names: (string | Extension)[]) // Remove extensions by namespace or control
690
+ get(...names: string[]) // Retrieve extensions by namespace
691
+ attr(name: string | Extension, attrName: string, value?: unknown) // Set or get extension options attribute value
692
+ apply(name: string | Extension, options: PlainObject, setting?: string) // See extension configuration section
693
+
694
+ extend(functionMap: {}, framework?: /* 0 - ALL | 1 - vdom | 2 - android | 4 - chrome */) // Add extension functions and properties to Node prototype
695
+
696
+ observe(value?: boolean | MutationObserverInit) // Start after DOM and third-party libraries initialization
697
+ broadcast(callback: BroadcastMessageCallback, options: FileBroadcastOptions | string) // Redirect stdout messages to DevTools console
698
+
699
+ // Promise (Recommended "cache": createElementMap - true)
700
+
701
+ getElementById(value: string, sync?: boolean, cache?: boolean) // sync - false | cache - true (default)
702
+ querySelector(value: string, sync?: boolean, cache?: boolean)
703
+ querySelectorAll(value: string, sync?: boolean, cache?: boolean)
704
+
705
+ fromElement(element: HTMLElement | string, sync?: boolean, cache?: boolean) // sync - false | cache - false (default)
706
+ fromNode(node: Node, sync?: boolean, cache?: boolean)
707
+ findDocumentNode(element: HTMLElement | string /* querySelector | elementId | controlId */, all?: boolean) // Use before saving to modify internal Node attributes
708
+
709
+ observeSrc(element: HTMLElement | string /* querySelector */, callback: WebSocketMessageChange, options?: FileObserveOptions) // Can be used to watch any element with externally hosted files (src/href)
710
+ observeSrc(element: HTMLElement | string, options: FileObserveOptions) // Uses location.reload (reload - true)
711
+ ```
712
+
713
+ Packaging methods will return a Promise and requires a squared-express installation. These features are not supported when the framework is VDOM.
714
+
715
+ ```javascript
716
+ saveAs(filename: string, options?: {}, setting?: string, overwrite?: boolean) // Save current session as a new archive
717
+ saveFiles(filename: string, options: {}, setting?: string, overwrite?: boolean) // Create new archive from FileAsset[]
718
+
719
+ // Required (local archives): --disk-read | --unc-read | --access-all (command-line)
720
+
721
+ appendTo(pathname: string, options?: {}, setting?: string, overwrite?: boolean) // Create new archive from a preexisting archive and current session
722
+ appendFiles(pathname: string, options: {}, setting?: string, overwrite?: boolean) // Create new archive from a preexisting archive and FileAsset[]
723
+
724
+ // Required (all): --disk-write | --unc-write | --access-all (command-line)
725
+
726
+ copyTo(pathname: string | string[], options?: {}, setting?: string, overwrite?: boolean) // Copy current session to local
727
+ copyFiles(pathname: string | string[], options: {}, setting?: string, overwrite?: boolean) // Copy FileAsset[] to local
728
+
729
+ kill(pid: number, timeout?: number) // Use -1 or options.pid (set by system) + seconds
730
+ kill(timeout: string)
731
+ kill() // Terminate previous request
732
+ kill(0) // By username (auth required)
733
+ kill(-1, 10) // Terminate previous request in 10 seconds
734
+ kill(NaN, 10) // Terminate in 10 seconds (next request) ("timeout" is required)
735
+ kill("10s") // Only "s" + "ms" (next request)
736
+ ```
737
+
738
+ ## Extending Node object
739
+
740
+ You can add functions and initial variables to the Node object including overwriting preexisting class definitions per framework. Accessor properties are also supported using the *get/set* object syntax.
741
+
742
+ ```javascript
743
+ squared.extend({
744
+ _id: 1,
745
+ altId: {
746
+ get() {
747
+ return this._id;
748
+ },
749
+ set(value) {
750
+ this._id += value;
751
+ }
752
+ },
753
+ customId: {
754
+ value: 2,
755
+ configurable: false,
756
+ enumerable: false
757
+ },
758
+ addEvent(eventName, callback) {
759
+ this.element.addEventListener(eventName, callback);
760
+ }
761
+ });
762
+ squared.setFramework(vdom);
763
+
764
+ const body = await squared.fromElement(document.body);
765
+ body.altId = 2; // body.altId: 3
766
+ body.addEvent("click", function (ev) {
767
+ this.classList.toggle("example");
768
+ });
769
+ ```
770
+
771
+ ## Forwarding Request
772
+
773
+ Using another identical remote server to build the project when performing a `saveAs` or `copyTo` request can be achieved by changing only the origin address.
774
+
775
+ ```javascript
776
+ squared.setHostname("http://hostname:8000");
777
+ // OR
778
+ squared.setHostname(); // Reset to window.location (e.g. localhost:3000)
779
+
780
+ await squared.saveAs("chrome.zip"); // Current browser
781
+ // OR
782
+ await squared.copyTo("/path/project"); // Remote server
783
+ ```
784
+
785
+ ## Broadcasting
786
+
787
+ Console messages (stdout) can be sent to the browser console instead through DevTools.
788
+
789
+ ```javascript
790
+ squared.broadcast(result => { console.log(result.value); }, "111-111-111"); // System messages from squared-express
791
+ squared.broadcast(result => { console.log(result.value); }, "222-222-222"); // Messages from "project-1" project
792
+ squared.broadcast(result => { console.log(result.value); }, { socketId: "333-333-333", socketKey: "socket_id" }); // Messages sent from another channel (default is "socketId")
793
+
794
+ await squared.copyTo("/path/project/project-1", {
795
+ projectId: "project-1",
796
+ log: { useColor: true }, // Chromium
797
+ broadcastId: "222-222-222" // Specific use alias for "socketId"
798
+ });
799
+ ```
800
+
801
+ ## Extension Configuration
802
+
803
+ Layout rendering can be customized using extensions as the program was built to be nearly completely modular. Some of the common layouts already have built-in extensions which you can load or unload based on your preference.
804
+
805
+ ```typescript
806
+ // Create an extension
807
+ class Sample extends squared.base.Extension {
808
+ options = {
809
+ attributeName: [];
810
+ };
811
+
812
+ constructor(name, framework = 0, options = {}) { // 0 - ALL | 1 - vdom | 2 - android | 4 - chrome (framework)
813
+ super(name, framework, options);
814
+ }
815
+
816
+ processNode(node: NodeUI) {
817
+ const data = this.project.get(node.element, node.localSettings.projectId);
818
+ if (data) {
819
+ node.each((child, index) => child.element.title = data[index]);
820
+ }
160
821
  }
161
- )
162
- .git("squared", "/path/to/squared", options: { local: true }) # Relative paths resolve from workspace root
163
- .git(
164
- {
165
- emc: { uri: "e-mc", options: { "depth": 2 } }, # https://github.com/anpham6/e-mc
166
- pir: "pi-r" # Maps task alias to repository folder
822
+ }
823
+
824
+ // Install an extension
825
+ const sample = new Sample("widget.example.com", 0, {/* Same as configure */});
826
+ squared.add(sample);
827
+ // OR
828
+ squared.add([sample, {/* config */}]);
829
+
830
+ // Configure an extension
831
+ squared.attr("widget.example.com", "attributeName", ["width", "height"]); // typeof is enforced and will only set existing attributes
832
+
833
+ // Add project data
834
+ const ext = squared.get("widget.example.com");
835
+
836
+ ext.project.set(element, await fetch(url?id=1)); // Map interface with optional "projectId" argument
837
+ ext.project.set(element, await fetch(url?id=2), "project-1");
838
+
839
+ const data = ext.project.get(element, "project-2"); // Returns data from default project (id=1)
840
+ ```
841
+
842
+ Some extensions have a few settings which can be configured. The default settings usually achieve the best overall rendering accuracy without noticeably affecting performance.
843
+
844
+ ## ANDROID
845
+
846
+ ### Public Methods
847
+
848
+ - [Read the Docs](https://squared.readthedocs.io/en/latest/methods/android.html)
849
+
850
+ ```javascript
851
+ android.setViewModel(data: {}, sessionId?: string) // Object data for layout bindings
852
+ android.setViewModelByProject(data: {}, projectId?: string)
853
+ android.removeObserver(element: HTMLElement) // Disconnect an observed element from "parseDocument"
854
+ android.addXmlNs(name: string, uri: string) // Add global namespaces for third-party controls
855
+ android.addDependency(group: string, name: string, version?: string, type?: number) // Add application dependency implementation (build.gradle)
856
+ android.addDependencyByProject(projectId: string, group: string, name: string, version?: string, type?: number) // DEPENDENCY_TYPE: 0 - implementation 1 - api 2 - compileOnly 3 - compileOnlyApi 4 - runtimeOnly 5 - testImplementation 8 - androidTestImplementation
857
+ android.customize(build: number, tagNameOrWidget: string, options: {}) // Global attributes applied to specific views
858
+ android.loadCustomizations(name: string) // Load customizations from Local Storage
859
+ android.saveCustomizations(name: string) // Save "customize" data into Local Storage (includes xmlns)
860
+ android.resetCustomizations() // All session customizations are deleted
861
+ android.addFontProvider(authority: string, package: string, certs: string[], webFonts: string | {}) // Add additional Web fonts (Google Fonts already included)
862
+ android.setResolutionByDeviceName(value: string) // Settings prefixed with "resolution" (e.g. Pixel C)
863
+ android.getLocalSettings() // Modify controller styles and parsing rules
864
+ ```
865
+
866
+ ```javascript
867
+ // NOTE: squared.settings.targetAPI is always parsed (Except: customizationsBaseAPI = -1)
868
+
869
+ android.customize(android.lib.constant.BUILD_VERSION.ALL /* 0 */, "Button", {
870
+ android: {
871
+ minWidth: "35px",
872
+ minHeight: "25px"
167
873
  },
168
- base: "https://github.com/anpham6", # Required
169
- repo: ["squared", "android-docs", "chrome-docs"], # https://github.com/anpham6/squared
170
- options: { # Only "repo"
171
- "depth": 1,
172
- "quiet": true
874
+ "_": { // Non-namespaced attributes
875
+ style: "@style/Widget.Material3.Button.TextButton"
173
876
  }
174
- )
175
- .with(:node) do # rake clone:node
176
- add("e-mc", "emc") # https://github.com/anpham6/e-mc
177
- add("pi-r", "pir") # https://github.com/anpham6/pi-r
178
- add("squared") # https://github.com/anpham6/squared
179
- end
180
- .with(:python) do # rake clone:python
181
- add("android-docs")
182
- add("chrome-docs")
183
- end
184
- .git("https://github.com/anpham6") # Uses already defined root projects
185
- .git("https://github.com/anpham6", ["emc", "pir"]) # Targets any defined project
186
- .build(parallel: ["clone"]) # rake clone + rake clone:sync
187
- ```
188
-
189
- ### Graph
190
-
191
- ```ruby
192
- Workspace::Application
193
- .new(main: "squared")
194
- .graph(["depend"], ref: :git) # Optional
195
- .with(:python) do
196
- add("android-docs", "android")
197
- add("chrome-docs", "chrome", graph: "android")
198
- end
199
- .with(:node) do
200
- graph(["build", "copy"], on: { # Overrides "git"
201
- first: proc { puts "1" },
202
- last: proc { puts "2" }
203
- })
204
- script("build:dev") # npm run build:dev
205
- # OR
206
- run([nil, "build:dev", { "PATH" => "~/.bin" }, "--workspace", "--silent"]) # PATH="~/.bin" npm run build:dev --workspace -- --silent
207
-
208
- add("e-mc", "emc") do
209
- first("build", "emc:clean", "emc:depend") # rake emc:clean && rake emc:depend && rake emc:build && echo "123"
210
- last("build", out: "123") { |out: nil| puts out } #
211
- error("build") { |err: nil| log.debug err } #
212
- end
213
- add("pi-r", "pir", graph: "emc", first: {
214
- build: proc { puts self.name } # puts "pir"
215
- })
216
- add("squared-express", "express", graph: "pir")
217
- add("squared", graph: ["chrome", "express"]) do
218
- first("git:ls-files") { puts path }
219
- last("git:ls-files") { puts workspace.root }
220
- end
221
- end
222
- .with(:ruby) do
223
- run("gem build") # gem build
224
- # OR
225
- run(["gem build", "--force", { "RUBY_VERSION" => "3.4.0" }]) # RUBY_VERSION="3.4.0" gem build --force
226
- # OR
227
- run(["gem pristine", ["gem build", "gem cleanup"], nil, "--debug"]) # gem pristine --debug && gem build --debug && gem cleanup --debug
228
- # OR
229
- run([ # PATH="~/.bin" GEM_HOME="~/.gems/ruby-3.4.0" (merged)
230
- ["gem pristine", "--all", { "PATH" => "~/.bin" }, "--silent"], # gem pristine --silent --all
231
- ["gem build", { strict: true }, { "GEM_HOME" => "~/.gems/ruby-3.4.0" }] # gem build --strict
232
- ])
233
-
234
- add("pathname", test: ["rake test", { jobs: ENV["RAKE_JOBS"] }]) # rake test --jobs 4
235
- add("fileutils", graph: "pathname")
236
- add("optparse", run: "gem build", env: { "PATH" => "~/.bin" }, opts: "-v") # PATH="~/.bin" gem build -v
237
- add("rake", graph: ["fileutils", "optparse"])
238
- banner(command: false) # Always hide banner
239
- end
240
- .build
877
+ });
878
+
879
+ android.customize(android.lib.constant.BUILD_VERSION.KITKAT /* 19 */, "svg", {
880
+ android: {
881
+ "[src]": "app:srcCompat" // Change namespace to "app"
882
+ }
883
+ });
884
+
885
+ // Local Storage
886
+ android.saveCustomizations("customize-example"); // Save at least once in one layout
887
+
888
+ android.loadCustomizations("customize-example"); // Load in any other layout
241
889
  ```
242
890
 
243
- ```sh
244
- rake pir:graph # emc + pir
245
- rake express:graph # emc + pir + express
246
- rake chrome:graph # android + chrome
247
- rake graph:python # same
248
- rake squared:graph # android + chrome + emc + pir + express + squared
249
- rake graph:node # same
250
- rake rake:graph # pathname + fileutils + optparse + rake
251
- rake graph:ruby # same
252
- rake graph # graph:node + graph:ruby
891
+ ```javascript
892
+ android.addXmlNs("tools", "http://schemas.android.com/tools");
253
893
 
254
- rake squared:graph:run[express,pir] # emc + pir + express + squared
255
- rake squared:graph:run[node,-emc] # pir + express + squared
894
+ android.customize(16 /* Jelly Bean */, "ImageView", {
895
+ tools: {
896
+ ignore: "ContentDescription",
897
+ targetApi: "16"
898
+ }
899
+ });
256
900
  ```
257
901
 
258
- ### Batch
902
+ ### Static Methods
903
+
904
+ Project resources can include additional values that are required during compilation. TypeScript definitions are available in the `types/android` directory.
259
905
 
260
- ```ruby
261
- Workspace::Series.batch(:ruby, :node, {
262
- stage: %i[graph test],
263
- reset: %i[stash pull]
264
- })
906
+ ```javascript
907
+ squared.parseDocument().then(node => {
908
+ const resourceId = node.localSettings.resourceId;
909
+ android.base.Resource.addString(resourceId, value, /* name */);
910
+ android.base.Resource.addArray(resourceId, name, items);
911
+ android.base.Resource.addColor(resourceId, color);
912
+ android.base.Resource.addDimen(resourceId, name, value);
913
+ android.base.Resource.addTheme(resourceId, theme);
914
+ squared.save();
915
+ });
265
916
  ```
266
917
 
267
- ### Rename
918
+ ### Data Binding
919
+
920
+ View model data can be applied to most HTML elements using the dataset attribute. Different view models can be used for every `parseDocument` session.
921
+
922
+ Leaving the `sessionId` empty uses the default view model which is searched last for all projects when attempting a bind.
923
+
924
+ ```javascript
925
+ // NOTE: latest(undefined = 1): string (1: most recent sessionId | -1: first sessionId)
926
+
927
+ squared.parseDocument("id-1", "id-2", "id-3").then(nodes => {
928
+ const sessions = squared.latest(2); // ["00001", "00002", "00003"] => ["00002", "00003"]
929
+ android.setViewModel(
930
+ {
931
+ import: ["java.util.Map", "java.util.List"],
932
+ variable: [
933
+ { name: "user", type: "com.example.User" },
934
+ { name: "list", type: "List&lt;String>" },
935
+ { name: "map", type: "Map&lt;String, String>" },
936
+ { name: "index", type: "int" },
937
+ { name: "key", type: "String" }
938
+ ]
939
+ },
940
+ sessions[0] // nodes[1].sessionId
941
+ );
942
+ android.setViewModel(
943
+ {
944
+ import: ["java.util.Map"],
945
+ variable: [
946
+ { name: "map", type: "Map&lt;String, String>" }
947
+ ]
948
+ },
949
+ sessions[1] // nodes[2].sessionId
950
+ );
951
+ });
952
+
953
+ squared.parseDocument({
954
+ element: "main",
955
+ enabledViewModel: true,
956
+ dataBindableElements: [
957
+ {
958
+ selector: "#first_name",
959
+ namespace: "android", // "android" is default
960
+ attr: "text",
961
+ expression: "user.firstName"
962
+ },
963
+ {
964
+ selector: "#last_name",
965
+ attr: "text",
966
+ expression: "user.lastName"
967
+ },
968
+ {
969
+ selector: "#remember_me",
970
+ attr: "checked",
971
+ expression: "user.rememberMe",
972
+ twoWay: true
973
+ }
974
+ ],
975
+ data: {
976
+ viewModel: {
977
+ import: ["java.util.Map"],
978
+ variable: [
979
+ { name: "map", type: "Map&lt;String, String>" }
980
+ ]
981
+ }
982
+ }
983
+ });
268
984
 
269
- ```ruby
270
- Workspace::Series.rename("depend", "install")
985
+ squared.save();
271
986
  ```
272
987
 
273
- ## Usage
988
+ Inlining is also supported and might be more convenient for simple layouts. JavaScript is recommended when you are calling `parseDocument` multiple times.
274
989
 
275
- ```sh
276
- rake -T # List tasks
277
- rake # rake status (usually "build")
990
+ ```
991
+ data-viewmodel-{namespace}-{attribute} -> data-viewmodel-android-text
992
+ ```
278
993
 
279
- # GIT_OPTIONS=rebase
280
- rake pull # All except "default" + "app"
281
- rake pull:ruby # pathname + optparse + logger
282
- rake pull:default # pathname + optparse
283
- rake pull:app # squared
284
- rake pull:node # emc + pir + squared
994
+ These two additional output parameters are required when using the "**data-viewmodel**" prefix.
285
995
 
286
- rake build # All except "android"
287
- rake doc # optparse + android
288
- rake depend # All except "default"
996
+ ```html
997
+ <div id="main">
998
+ <label>Name:</label>
999
+ <input id="first_name" type="text" data-viewmodel-android-text="user.firstName" />
1000
+ <input id="last_name" type="text" data-viewmodel-android-text="user.lastName" />
1001
+ <input id="remember_me" type="checkbox" data-viewmodel-android-checked="=user.rememberMe" /> <!-- "=" for two-way binding -->
1002
+ </div>
1003
+ ```
289
1004
 
290
- rake build:ruby # rake compile + rake install + rake install
1005
+ ```xml
1006
+ <layout>
1007
+ <data>
1008
+ <import type="java.util.Map" />
1009
+ <import type="java.util.List" />
1010
+ <variable name="user" type="com.example.User" />
1011
+ <variable name="list" type="List&lt;String&gt;" />
1012
+ <variable name="map" type="Map&lt;String, String&gt;" />
1013
+ <variable name="index" type="int" />
1014
+ <variable name="key" type="String" />
1015
+ </data>
1016
+ <LinearLayout android:id="@+id/main">
1017
+ <TextView android:text="Name:" />
1018
+ <EditText
1019
+ android:id="@+id/first_name"
1020
+ android:inputType="text"
1021
+ android:text="@{user.firstName}" />
1022
+ <EditText
1023
+ android:id="@+id/last_name"
1024
+ android:inputType="text"
1025
+ android:text="@{user.lastName}" />
1026
+ <CheckBox
1027
+ android:id="@+id/remember_me"
1028
+ android:checked="@={user.rememberMe}" />
1029
+ </LinearLayout>
1030
+ </layout>
1031
+ ```
1032
+
1033
+ ### Layout Includes / Merge Tag
291
1034
 
292
- rake clean # All except "default" + "app"
293
- rake clean:ruby # rake clean + rake clean + ["tmp/"]
294
- rake clean:default # rake clean + rake clean + skip
295
- rake clean:app # none + skip + ["build/"]
296
- rake clean:node # none + ["publish/**/*.js", "tmp/"] + ["build/"]
1035
+ Some applications can benefit from using includes or merge tags to share common templates. Nested includes is supported.
297
1036
 
298
- rake squared:run[#] # List scripts (node)
299
- rake squared:rake[#] # List tasks (ruby)
1037
+ ```html
1038
+ <div>
1039
+ <div id="item1">Item 1</div>
1040
+ <div id="item2" data-android-include-start="true" data-android-include-merge="true" data-pathname-android="app/src/main/res/layout-land" data-filename-android="filename1.xml">Item 2</div>
1041
+ <div id="item3">Item 3</div>
1042
+ <div id="item4" data-android-include-end="true">Item 4</div>
1043
+ <div id="item5" data-android-include="filename2" data-android-include-end="true" data-android-include-viewmodel="exampleData">Item 5</div> <!-- viewModel -->
1044
+ </div>
300
1045
  ```
301
1046
 
302
- ```sh
303
- rake build:app # squared + cli + sqd-serve
304
- rake squared:build:workspace # cli + sqd-serve
305
- rake pull:sqd # sqd-admin
306
- rake squared:pull:workspace # sqd-serve + sqd-admin
307
- rake squared:outdated:workspace # cli + sqd-serve + sqd-admin
1047
+ ```javascript
1048
+ android.setViewModelByProject({ variable: [{ name: "exampleData", type: "com.example.ExampleData" }] }, "project-1"); // Default is "_"
1049
+
1050
+ squared.parseDocument({
1051
+ element: document.body,
1052
+ projectId: "project-1", // Affects all layouts in same project
1053
+ enabledIncludes: true,
1054
+ includableElements: [
1055
+ {
1056
+ selectorStart: "#item2",
1057
+ selectorEnd: "#item4",
1058
+ pathname: "app/src/main/res/layout-land",
1059
+ filename: "filename1.xml",
1060
+ merge: true // Multiple elements will auto-merge
1061
+ },
1062
+ {
1063
+ selectorStart: "#item5",
1064
+ selectorEnd: "#item5",
1065
+ filename: "filename2",
1066
+ viewModel: "exampleData" // One element only (merge=false)
1067
+ }
1068
+ ]
1069
+ });
1070
+ ```
1071
+ > [!NOTE]
1072
+ > By sessionId has precedence when associating a view model.
1073
+
1074
+ ```xml
1075
+ <LinearLayout>
1076
+ <TextView>Item 1</TextView>
1077
+ <include layout="@layout/filename1" />
1078
+ <include layout="@layout/filename2" app:exampleData="@{exampleData}" />
1079
+ </LinearLayout>
1080
+ <!-- res/layout/activity_main.xml -->
1081
+
1082
+ <merge>
1083
+ <TextView>Item 2</TextView>
1084
+ <TextView>Item 3</TextView>
1085
+ <TextView>Item 4</TextView>
1086
+ </merge>
1087
+ <!-- res/layout-land/filename1.xml -->
1088
+
1089
+ <layout>
1090
+ <data>
1091
+ <variable name="exampleData" type="com.example.ExampleData" />
1092
+ </data>
1093
+ <TextView>Item 5</TextView>
1094
+ </layout>
1095
+ <!-- res/layout/filename2.xml -->
1096
+ ```
1097
+
1098
+ The attributes "**data-android-include-start**" and "**data-android-include-end**" can only be applied to elements which share the same parent container. See `/demos/gradient.html` for usage instructions.
1099
+
1100
+ > [!TIP]
1101
+ > "**data-pathname-android**" AND "**data-filename-android**" can also be used with any `parseDocument` base element.
1102
+
1103
+ ### Redirecting Output Location
1104
+
1105
+ Sometimes it is necessary to extract elements and append them into other containers for it to look identical on the Android device. Redirection will fail if the *target location* is not a block/container element.
1106
+
1107
+ ```html
1108
+ <div>
1109
+ <span>Item 1</span>
1110
+ <span data-android-target="location">Item 2</span>
1111
+ <span data-android-target="location" data-android-target-index="1">Item 3</span>
1112
+ <div>
1113
+ <ul id="location">
1114
+ <li>Item 4</li>
1115
+ <li>Item 5</li>
1116
+ <!-- span -->
1117
+ </ul>
1118
+ ```
1119
+
1120
+ ```xml
1121
+ <LinearLayout>
1122
+ <TextView>Item 1</TextView>
1123
+ </LinearLayout>
1124
+ <LinearLayout>
1125
+ <TextView>Item 4</TextView>
1126
+ <TextView>Item 3</TextView>
1127
+ <TextView>Item 5</TextView>
1128
+ <TextView>Item 2</TextView>
1129
+ </LinearLayout>
308
1130
  ```
309
1131
 
310
- ## Methods
1132
+ Using `target` into a ConstraintLayout or RelativeLayout container will not include automatic positioning.
311
1133
 
312
- Task:
1134
+ ### Custom Attributes
313
1135
 
314
- * run
315
- * script
316
- * depend
317
- * graph
318
- * doc
319
- * lint
320
- * test
321
- * clean
1136
+ System or extension generated attributes can be overridden preceding finalization. They will only be visible on the declared framework.
322
1137
 
323
- Non-task:
1138
+ ```
1139
+ data-android-attr-{namespace}? -> Default is "android"
1140
+ ```
324
1141
 
325
- * log
326
- * exclude
1142
+ ```html
1143
+ <div id="customId"
1144
+ data-android-attr="layout_width::match_parent;layout_height::match_parent"
1145
+ data-android-attr-app="layout_scrollFlags::scroll|exitUntilCollapsed">
1146
+ </div>
1147
+ ```
327
1148
 
328
- ## Styles
1149
+ ```xml
1150
+ <LinearLayout
1151
+ android:id="@+id/customId"
1152
+ android:layout_width="match_parent"
1153
+ android:layout_height="match_parent"
1154
+ app:layout_scrollFlags="scroll|exitUntilCollapsed" />
1155
+ ```
329
1156
 
330
- * banner
331
- * border
332
- * header
333
- * active
334
- * inline
335
- * current
336
- * major
337
- * red
338
- * yellow
339
- * green
1157
+ ```javascript
1158
+ const node = squared.findDocumentNode("customId"); // querySelector is supported
1159
+ node.android("layout_width", "match_parent");
1160
+ node.android("layout_height", "match_parent");
1161
+ node.app("layout_scrollFlags", "scroll|exitUntilCollapsed");
1162
+ ```
340
1163
 
341
- ## Environment
1164
+ ### SVG animations
342
1165
 
343
- ### Path
1166
+ Only the XML based layout and resource files can be viewed on the Android device/emulator without any Java/Kotlin backend code. To play animations in the emulator you also have to `start` the animation in *MainActivity.java*.
344
1167
 
345
- All project executable programs can have their binary path set to a non-global alias.
1168
+ ```java
1169
+ import android.graphics.drawable.Animatable;
346
1170
 
347
- ```ruby
348
- Common::PATH.merge!({
349
- GIT: '/usr/bin/git',
350
- GEM: '~/.rvm/rubies/ruby-3.4.0/bin/gem',
351
- BUNDLE: '~/.rvm/gems/ruby-3.4.0/bin/bundle',
352
- RAKE: '~/.rvm/gems/ruby-3.4.0/bin/rake',
353
- NPM: '/opt/node/v22.0.0/bin/npm',
354
- PYTHON: "#{ENV['PYTHONPATH']}/bin/python"
355
- })
1171
+ android.widget.ImageView imageView1 = findViewById(R.id.imageview_1);
1172
+ if (imageView1 != null) {
1173
+ Animatable animatable = (Animatable) imageView1.getDrawable();
1174
+ animatable.start();
1175
+ }
356
1176
  ```
357
1177
 
358
- ### Build
1178
+ ### Jetpack Compose
359
1179
 
360
- ```ruby
361
- Workspace::Application
362
- .new
363
- .add("squared", run: "gcc a.c -o a.o", opts: { __debug__: { g: true, O2: true, c: nil }, c: true, j: 4 }) # gcc a.c -o a.o -c -j4
1180
+ Most mobile applications do not have a deeply nested hierarchy and are generally better to implement using declarative programming.
364
1181
 
365
- BUILD_TYPE # global
1182
+ ```javascript
1183
+ squared.settings.composableElements = ["main", "#content", "--boxShadow", "--height=300px", {
1184
+ selector: "main",
1185
+ android: {
1186
+ layout_height: "match_parent"
1187
+ },
1188
+ tools: {
1189
+ composableName: "com.example.compose.Preview"
1190
+ }
1191
+ }];
1192
+ squared.settings.createBuildDependencies = true; // Optional
1193
+ ```
1194
+
1195
+ You can also do it using the "**android.substitute**" extension directly inside the HTML element.
366
1196
 
367
- # :env :run :opts :type
368
- # LD_LIBRARY_PATH="path/to/lib" CFLAGS="-Wall" gcc a.c -o a.o -g -O2
369
- BUILD_${NAME} # gcc a.c -o a.o
370
- BUILD_${NAME}_OPTS # -g
371
- BUILD_${NAME}_ENV # {"LD_LIBRARY_PATH":"path/to/lib","CFLAGS":"-Wall"} (hash/json)
372
- BUILD_${NAME}_TYPE # debug
1197
+ ```javascript
1198
+ // android.substitute is only used here to demonstrate using extensions
373
1199
 
374
- # :env :opts :script :args
375
- # NODE_ENV="production" NO_COLOR="1" npm run --loglevel=error --workspaces=false build:dev -- --quiet
376
- BUILD_${NAME} # build:dev
377
- BUILD_${NAME}_OPTS # --loglevel=error --workspaces=false
378
- BUILD_${NAME}_ENV # {"NODE_ENV":"production","NO_COLOR":"1"} (hash/json)
379
- BUILD_${NAME}_DEV # pattern,0,1 (:dev)
380
- BUILD_${NAME}_PROD # pattern,0,1 (:prod)
381
- SCRIPT_${NAME}_OPTS # --quiet
1200
+ squared.add(["android.substitute", {
1201
+ element: {
1202
+ content: { android: { layout_width: "match_parent" } }
1203
+ }
1204
+ }]);
1205
+
1206
+ const items = squared.attr("android.substitute", "viewAttributes");
1207
+ items.push("hint", "buttonTint");
1208
+ /* OR */
1209
+ squared.attr("android.substitute", "viewAttributes", items.concat(["hint", "buttonTint"])); // Attributes to preserve (default is "android.view.View")
1210
+ squared.attr("android.substitute", "attributeMapping", { "android:src": "app:srcCompat", "icon": "navigationIcon" /* android */});
1211
+
1212
+ squared.parseDocument({
1213
+ element: document.body,
1214
+ substitutableElements: [{
1215
+ selector: "#content",
1216
+ tag: "androidx.compose.ui.platform.ComposeView",
1217
+ renderChildren: false
1218
+ }],
1219
+
1220
+ // Some extensions have convenience properties
1221
+ enabledSubstitute: true,
1222
+ /* OR */
1223
+ include: ["android.substitute"]
1224
+ });
1225
+ ```
382
1226
 
383
- BUILD_${NAME}=0 # skip project
1227
+ ```html
1228
+ <body>
1229
+ <header style="height: 100px"></header>
1230
+ <main id="content"
1231
+ data-use="android.substitute"
1232
+ data-android-substitute-tag="androidx.compose.ui.platform.ComposeView"
1233
+ style="height: 300px; box-shadow: 10px 5px 5px black;">
1234
+ <!-- Interior elements are not rendered -->
1235
+ </main>
1236
+ <footer style="height: 80px"></footer>
1237
+ </body>
384
1238
  ```
385
1239
 
386
- ### Graph
1240
+ Compose will remove child elements by default. You can preserve them by explictly using the renderChildren property. (data-android-substitute-render-children="true")
387
1241
 
388
- ```ruby
389
- GRAPH_${NAME} # depend,build => squared:depend + squared:build
390
- GRAPH_${NAME}_PASS # -emc,pir,express => pir + express
1242
+ ```html
1243
+ <div id="fragment"
1244
+ data-use="android.substitute"
1245
+ data-android-substitute-tag="androidx.fragment.app.FragmentContainerView"
1246
+ data-android-substitute-render-children="false"
1247
+ data-android-attr="name::com.github.fragment;tag::example">
1248
+ <!-- Interior elements are not rendered -->
1249
+ </div>
391
1250
  ```
392
1251
 
393
- ### Logger
1252
+ You can also use "**android.substitute**" to create fragments within a layout similar to Compose.
1253
+
1254
+ Usually you do not render child elements when using Compose View. There are some cases where it can be used effectively to reproduce the desired layout.
1255
+
1256
+ ```javascript
1257
+ squared.parseDocument({
1258
+ element: document.body,
1259
+ include: ["android.substitute"], // OR: settings.enabledSubstitute
1260
+ substitutableElements: [{
1261
+ selector: "#navigation",
1262
+ tag: "com.google.android.material.tabs.TabLayout",
1263
+ tagChild: "com.google.android.material.tabs.TabItem",
1264
+ tagChildAttr: {
1265
+ android: {
1266
+ layout_height: "match_parent"
1267
+ }
1268
+ },
1269
+ renderChildren: true,
1270
+ autoLayout: true
1271
+ }]
1272
+ });
1273
+ ```
394
1274
 
395
- These global options also can target the project suffix `${NAME}`. (e.g. LOG_FILE_EMC)
1275
+ ```html
1276
+ <ul id="navigation"
1277
+ data-use-android="android.substitute"
1278
+ data-android-attr="layout_height::match_parent"
1279
+ data-android-substitute-tag="com.google.android.material.tabs.TabLayout"
1280
+ data-android-substitute-tag-child="com.google.android.material.tabs.TabItem"
1281
+ data-android-substitute-tag-child-attr="layout_height::match_parent"
1282
+ data-android-substitute-auto-layout="true">
1283
+ <li>TAB 1</li>
1284
+ <li>TAB 2</li>
1285
+ <li>TAB 3</li>
1286
+ </ul>
1287
+ ```
396
1288
 
397
- ```ruby
398
- LOG_FILE # %Y-%m-%d.log
399
- # OR
400
- LOG_AUTO # year,y,month,m,day,d,1
401
- # Optional
402
- LOG_DIR # exist?
403
- LOG_LEVEL # See gem "logger"
404
- LOG_COLUMNS # terminal width (default: 80)
1289
+ ```xml
1290
+ <com.google.android.material.tabs.TabLayout
1291
+ android:id="@+id/navigation"
1292
+ android:layout_height="match_parent"
1293
+ android:layout_width="wrap_content">
1294
+ <com.google.android.material.tabs.TabItem
1295
+ android:layout_height="match_parent"
1296
+ android:layout_width="wrap_content"
1297
+ android:text="@string/tab_1" />
1298
+ <com.google.android.material.tabs.TabItem
1299
+ android:layout_height="match_parent"
1300
+ android:layout_width="wrap_content"
1301
+ android:text="@string/tab_2" />
1302
+ <com.google.android.material.tabs.TabItem
1303
+ android:layout_height="match_parent"
1304
+ android:layout_width="wrap_content"
1305
+ android:text="@string/tab_3" />
1306
+ </com.google.android.material.tabs.TabLayout>
405
1307
  ```
406
1308
 
407
- ### Repo
1309
+ ### Downloadable Fonts
1310
+
1311
+ Google Fonts are pre-installed and can be used without any additional configuration.
1312
+
1313
+ * [Guide](https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts)
1314
+
1315
+ ```xml
1316
+ <!-- build.gradle -->
1317
+ dependencies {
1318
+ implementation 'androidx.appcompat:appcompat:1.6.0' <!-- createBuildDependencies = true -->
1319
+ <!-- OR -->
1320
+ implementation 'com.android.support:appcompat-v7:28.0.0'
1321
+ }
1322
+
1323
+ <!-- AndroidManifest.xml -->
1324
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <!-- createManifest = true -->
1325
+ <application android:theme="@style/AppTheme">
1326
+ <meta-data android:name="preloaded_fonts" android:resource="@array/preloaded_fonts" />
1327
+ </application>
1328
+ </manifest>
1329
+ ```
1330
+
1331
+ ```javascript
1332
+ // https://developers.google.com/fonts/docs/developer_api
1333
+
1334
+ await android.addFontProvider(
1335
+ "com.google.android.gms.fonts",
1336
+ "com.google.android.gms",
1337
+ ["MIIEqDCCA5CgAwIBAgIJANWFuGx9007...", "MIIEQzCCAyugAwIBAgIJAMLgh0Zk..."],
1338
+ "https://www.googleapis.com/webfonts/v1/webfonts?key=1234567890" // JSON object is synchronous
1339
+ );
1340
+ /* OR */
1341
+ squared.attr("android.resource.fonts", "installGoogleFonts", false); // Use browser and local fonts only
1342
+ ```
1343
+
1344
+ ### Excluding Procedures / Applied Attributes
1345
+
1346
+ Most attributes can be excluded from the generated XML using the dataset feature in HTML. One or more can be applied to any tag using the OR "**|**" operator. These may cause warnings when you compile your project and should only be used when an extension has their custom attributes overwritten.
1347
+
1348
+ NOTE: Defining an element "**id**" will prevent it from being removed during the optimization phase.
408
1349
 
409
- These global options also can target the application main suffix `${NAME}`. (e.g. REPO_ROOT_SQUARED)
410
-
411
- ```ruby
412
- REPO_ROOT # parent dir
413
- REPO_HOME # project dir (main)
414
- REPO_BUILD # run,script
415
- REPO_GROUP # string
416
- REPO_REF # e.g. ruby,node
417
- REPO_DEV # pattern,0,1
418
- REPO_PROD # pattern,0,1
419
- REPO_WARN # 0,1
420
- REPO_SYNC # 0,1
421
- REPO_MANIFEST # e.g. latest,nightly,prod
422
- REPO_TIMEOUT # confirm dialog (seconds)
423
- ```
424
-
425
- ## Git
426
-
427
- Most project classes will inherit from `Git` which enables these tasks:
428
-
429
- | Task | Git | Command |
430
- | :--------- | :--------------- | :-------------------------------------------- |
431
- | branch | branch | create set delete move copy list edit current |
432
- | checkout | checkout | commit branch track detach path |
433
- | commit | commit | add all amend amend-orig |
434
- | diff | diff | head cached branch files between contain |
435
- | fetch | fetch | origin remote |
436
- | files | ls-files | cached modified deleted others |
437
- | pull | pull | origin remote |
438
- | rebase | rebase | branch onto send |
439
- | refs | ls-remote --refs | heads tags remote |
440
- | reset | reset | commit index patch mode |
441
- | restore | restore | source worktree staged overlay |
442
- | rev | rev | commit branch output parseopt |
443
- | show | show | format oneline |
444
- | stash | stash | push pop apply drop list |
445
- | tag | tag | add delete list |
446
-
447
- You can disable all of them at once using the `exclude` property.
448
-
449
- ```ruby
450
- Workspace::Application
451
- .new
452
- .add("squared", exclude: :git)
453
- ```
454
-
455
- You can disable one or more of them using the `pass` property as a *string*.
456
-
457
- ```ruby
458
- Workspace::Application
459
- .new
460
- .add("squared", pass: ["pull"], ref: :node)
461
- .pass("pull", ref: :node) { read_packagemanager(:private) }
1350
+ ```html
1351
+ <div data-exclude-section="DOM_TRAVERSE | EXTENSION | RENDER | ALL"
1352
+ data-exclude-procedure="CONSTRAINT | LAYOUT | ALIGNMENT | ACCESSIBILITY | LOCALIZATION | CUSTOMIZATION | OPTIMIZATION | ALL"
1353
+ data-exclude-resource="BOX_STYLE | BOX_SPACING | FONT_STYLE | VALUE_STRING | IMAGE_SOURCE | ASSET | ALL"
1354
+ data-exclude-optimization="EXCLUDE | INHERIT | ALIGNMENT | POSITION | DIMENSION | MARGIN | PADDING | BASELINE | WHITESPACE | TRANSLATE | TRANSFORM | SCALING">
1355
+ </div>
1356
+ <div>
1357
+ <span data-exclude-resource="FONT_STYLE">content</span>
1358
+ <input id="cb1" type="checkbox" data-exclude-procedure="ACCESSIBILITY"><label for="cb1">checkbox text</label>
1359
+ </div>
462
1360
  ```
463
1361
 
464
1362
  ## LICENSE