squared 0.0.1

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