@keycloakify/angular-email 0.0.7 → 1.0.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.
Files changed (27) hide show
  1. package/README.md +70 -11
  2. package/bin/build.mjs +1 -154
  3. package/fesm2022/keycloakify-angular-email-tailwindcss-preset-email-css-processor.mjs +70 -0
  4. package/fesm2022/keycloakify-angular-email-tailwindcss-preset-email-css-processor.mjs.map +1 -0
  5. package/fesm2022/keycloakify-angular-email.mjs +95 -81
  6. package/fesm2022/keycloakify-angular-email.mjs.map +1 -1
  7. package/index.d.ts +1 -3
  8. package/node/index.cjs +1 -1
  9. package/node/index.d.ts +1 -0
  10. package/node/index.js +1 -1
  11. package/package.json +57 -20
  12. package/tailwindcss-preset-email/clients.css +44 -0
  13. package/tailwindcss-preset-email/css-processor/css-processor.ts +72 -0
  14. package/tailwindcss-preset-email/css-processor/index.d.ts +3 -0
  15. package/tailwindcss-preset-email/css-processor/index.ts +1 -0
  16. package/tailwindcss-preset-email/css-processor/ng-package.json +6 -0
  17. package/tailwindcss-preset-email/index.css +25 -0
  18. package/tailwindcss-preset-email/mso.css +433 -0
  19. package/tailwindcss-preset-email/theme/borders.css +15 -0
  20. package/tailwindcss-preset-email/theme/colors.css +249 -0
  21. package/tailwindcss-preset-email/theme/filters.css +13 -0
  22. package/tailwindcss-preset-email/theme/shadows.css +23 -0
  23. package/tailwindcss-preset-email/theme/spacing.css +21 -0
  24. package/tailwindcss-preset-email/theme/text.css +28 -0
  25. package/fesm2022/keycloakify-angular-email-tailwindcss-preset-email.mjs +0 -1229
  26. package/fesm2022/keycloakify-angular-email-tailwindcss-preset-email.mjs.map +0 -1
  27. package/tailwindcss-preset-email/index.d.ts +0 -191
package/README.md CHANGED
@@ -14,6 +14,11 @@ yarn add @keycloakify/angular-email
14
14
 
15
15
  ## Usage
16
16
 
17
+ | Angular | Tailwind CSS | @keycloakify/angular-email | Maintained |
18
+ | ------- | ------------ | -------------------------- | ------------- |
19
+ | 20 | 4.x+ | 1.x+ | Yes |
20
+ | 20 | 3.x+ | 0.x+ | Bugfixes only |
21
+
17
22
  ### Creating an Email Component
18
23
 
19
24
  see this [example](https://github.com/keycloakify/angular-email/blob/main/projects/showcase/src/app/app.component.ts)
@@ -49,14 +54,32 @@ export const renderToHtml: RenderToHtml<EmailComponentProps> = (props) => {
49
54
  ```sh
50
55
  # cmd
51
56
 
52
- export $EMAIL_COMPONENTS_DIR_PATH="src/emails"
53
- npx keycloakify-angular-email build -p $EMAIL_COMPONENTS_DIR_PATH
57
+ export EMAIL_COMPONENTS_DIR_PATH="src/emails"
58
+ export EMAIL_OUTPUT_DIR_PATH="dist/emails"
59
+ export EMAIL_EXTERNAL_PACKAGES="tailwindcss,@tailwindcss/postcss,postcss,postcss-calc,postcss-custom-properties,postcss-preset-env,postcss-logical"
60
+
61
+ npx keycloakify-angular-email build -p "$EMAIL_COMPONENTS_DIR_PATH" -o "$EMAIL_OUTPUT_DIR_PATH" -e "$EMAIL_EXTERNAL_PACKAGES"
54
62
  ```
55
63
 
56
64
  NB: use `keycloakify-angular-email build` when you don't need to pass dynamic inputs to your components, otherwise see [Standalone Dynamic Rendering](#standalone-dynamic-rendering)
57
65
 
58
66
  ### With Keycloakify Emails
59
67
 
68
+ ```json
69
+ // emails/tsconfig.json
70
+
71
+ {
72
+ "extends": "../tsconfig.json",
73
+ "compileOnSave": false,
74
+ "compilerOptions": {
75
+ "outDir": "../out-tsc/emails",
76
+ "types": []
77
+ },
78
+ "files": [],
79
+ "include": ["*.ts", "**/*.ts"]
80
+ }
81
+ ```
82
+
60
83
  ```ts
61
84
  // email.component.ts
62
85
  ...
@@ -101,7 +124,7 @@ export default defineConfig(({ mode }) => ({
101
124
  ...
102
125
  postBuild: async (buildContext) => {
103
126
  await buildEmailTheme({
104
- templatesSrcDirPath: join(import.meta.dirname, '/emails/templates'),
127
+ templatesSrcDirPath: join(import.meta.dirname, 'emails', 'templates'),
105
128
  filterTemplate: (filePath: string) => !!filePath.endsWith('.component.ts'),
106
129
  themeNames: buildContext.themeNames,
107
130
  keycloakifyBuildDirPath: buildContext.keycloakifyBuildDirPath,
@@ -109,10 +132,10 @@ export default defineConfig(({ mode }) => ({
109
132
  cwd: import.meta.dirname,
110
133
  esbuild: {
111
134
  packages: 'bundle',
112
- external: ['juice', 'postcss', 'tailwindcss-v3'],
135
+ external: ['juice', '...other packages you might use to process css'],
113
136
  format: 'esm',
114
137
  outExtension: { '.js': '.mjs' },
115
- plugins: [angularEsbuildPlugin(import.meta.dirname)],
138
+ plugins: [angularEsbuildPlugin(join(import.meta.dirname, 'emails'))],
116
139
  },
117
140
  });
118
141
  },
@@ -133,6 +156,7 @@ import { toHTML } from '@keycloakify/angular-email/node';
133
156
  toHTML({
134
157
  filePath: 'path/to/your.component.ts',
135
158
  props: { foo: 'bar' },
159
+ externals: [],
136
160
  })
137
161
  .then((html) => {
138
162
  console.log(html);
@@ -152,6 +176,7 @@ const { toHTML } = require('@keycloakify/angular-email/node');
152
176
  toHTML({
153
177
  filePath: 'path/to/your.component.ts',
154
178
  props: { foo: 'bar' },
179
+ externals: [],
155
180
  })
156
181
  .then((html) => {
157
182
  console.log(html);
@@ -185,8 +210,8 @@ type Render<Input extends Record<string, any>> = {
185
210
  plainText?: boolean;
186
211
  /** format the html output */
187
212
  pretty?: boolean;
188
- /** tailwind v3 configuration object */
189
- tailwindConfig?: Partial<Config>;
213
+ /** Optional hook to manipulate the extracted CSS. Useful for PostCSS processing */
214
+ cssProcessor?: (css: string) => Promise<string>;
190
215
  /** if you use prefix conventions on signal inputs */
191
216
  signalInputsPrefix?: string;
192
217
  };
@@ -216,18 +241,52 @@ toHTML<Input extends Record<string, any>>(options: {
216
241
  filePath: string;
217
242
  props?: Input;
218
243
  root?: string;
244
+ externals?: string[];
219
245
  }) => Promise<string>
220
246
  ```
221
247
 
222
248
  ### @keycloakify/angular-email/tailwindcss-preset-email
223
249
 
224
- Just a tailwind v3 preset, inspired by [@maizzle/tailwindcss-preset-email](https://github.com/maizzle/tailwindcss-preset-email)
250
+ Just a tailwind v4 preset, inspired by [@maizzle/tailwindcss](https://github.com/maizzle/tailwindcss)
251
+
252
+ ```css
253
+ /* styles.css */
254
+ @import '@keycloakify/angular-email/tailwindcss-preset-email';
255
+ ```
225
256
 
226
- **NB**: tailwind v4 is not supported due to high level css generation and poor support in overriding default utilities
257
+ ```typescript
258
+ // email.component.ts
259
+ ...
260
+ import { Component, ViewEncapsulation } from '@angular/core';
261
+ import { render, RenderToHtml } from '@keycloakify/angular-email';
262
+ // or your custom css processor implementation
263
+ import { cssProcessor } from '@keycloakify/angular-email/tailwindcss-preset-email/css-processor';
227
264
 
228
- [add support for disabling core plugins](https://github.com/tailwindlabs/tailwindcss/discussions/16132)
229
265
 
230
- [Cannot override tailwind utilities](https://github.com/tailwindlabs/tailwindcss/issues/16856)
266
+ ...
267
+ @Component({
268
+ ...
269
+ styleUrls: ['styles.css'],
270
+ encapsulation: ViewEncapsulation.None,
271
+ })
272
+ export class EmailComponent {
273
+ ....
274
+ }
275
+
276
+ type EmailComponentProps = {};
277
+
278
+ export const renderToHtml: RenderToHtml<EmailComponentProps> = (props) => {
279
+ return render({
280
+ component: EmailComponent,
281
+ selector: 'app-root',
282
+ props,
283
+ options: {
284
+ pretty: true,
285
+ cssProcessor,
286
+ },
287
+ });
288
+ };
289
+ ```
231
290
 
232
291
  ## Contributing
233
292
 
package/bin/build.mjs CHANGED
@@ -1,155 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { angularEsbuildPlugin } from '@keycloakify/angular-email/esbuild';
3
- import esbuild from 'esbuild';
4
- import { readdir, rm, writeFile } from 'node:fs/promises';
5
- import { basename, join, resolve } from 'node:path';
6
- import { cwd, exit } from 'node:process';
7
- import { pathToFileURL } from 'node:url';
8
- import { termost } from 'termost';
9
- import pkg from '../package.json' with { type: 'json' };
10
- async function bundle(entryPoints, cwd, outdir) {
11
- // we have to use a bundler to preprocess templates code
12
- // It's better to not use the same bundling configuration used for the
13
- // frontend theme, because for email templates there might be a different
14
- // transpiling settings, such as other jsx-pragma or additional transpilation plugins,
15
- const result = await esbuild.build({
16
- entryPoints: entryPoints,
17
- bundle: true,
18
- splitting: true,
19
- outdir,
20
- absWorkingDir: cwd,
21
- platform: 'node',
22
- sourcemap: true,
23
- packages: 'bundle',
24
- external: ['juice', 'postcss', 'tailwindcss-v3'],
25
- format: 'esm',
26
- outExtension: { '.js': '.mjs' },
27
- target: 'node20',
28
- plugins: [angularEsbuildPlugin(cwd)],
29
- metafile: true,
30
- });
31
- // collect map of entrypoints -> output files
32
- // esbuild return paths relative to the CWD
33
- // https://github.com/evanw/esbuild/issues/338
34
- return Object.entries(result.metafile.outputs).reduce((acc, [filePath, meta]) => {
35
- if (meta.entryPoint) {
36
- // Absolute pathes doesn't work on windows.
37
- // Have to use `pathToFileURL` to convert it to url
38
- acc[resolve(cwd, meta.entryPoint)] = pathToFileURL(resolve(cwd, filePath)).toString();
39
- }
40
- return acc;
41
- }, {});
42
- }
43
- async function getTemplates(dirPath, filterTemplate) {
44
- try {
45
- const recursiveGetFiles = async (path) => {
46
- const items = await readdir(path, { withFileTypes: true });
47
- const dirents = [];
48
- for (const item of items) {
49
- if (item.isFile())
50
- dirents.push(item);
51
- else if (item.isDirectory()) {
52
- dirents.push(...(await recursiveGetFiles(join(item.parentPath, item.name))));
53
- }
54
- }
55
- return dirents;
56
- };
57
- // Read all items in the directory
58
- const items = (await recursiveGetFiles(dirPath))
59
- .map((file) => join(file.parentPath, file.name))
60
- .filter((filePath) => {
61
- if (!filterTemplate)
62
- return true;
63
- return filterTemplate(filePath);
64
- });
65
- return items;
66
- }
67
- catch (err) {
68
- console.error(`Error scanning directory: ${err.message}`);
69
- throw err;
70
- }
71
- }
72
- const build = async (emailFilesPath, outdir = 'dist/emails') => {
73
- if (!emailFilesPath) {
74
- console.error('emailFilesPath is required!');
75
- exit(1);
76
- }
77
- try {
78
- console.log(`building emails to ${outdir}...`);
79
- const tmp = join(cwd(), outdir, '.tmp');
80
- await rm(outdir, { recursive: true, force: true });
81
- const tpls = await getTemplates(resolve(cwd(), emailFilesPath), (path) => path.endsWith('.ts'));
82
- if (!tpls.length) {
83
- console.error('no templates found!');
84
- exit(1);
85
- }
86
- const entryPoints = [...tpls];
87
- const bundled = await bundle(entryPoints, cwd(), tmp);
88
- const promises = tpls.map(async (file) => {
89
- const module = await import(bundled[file]);
90
- console.log(`- ${file}`);
91
- return Object.assign(Object.assign({}, module), { file });
92
- });
93
- const modules = await Promise.all(promises);
94
- for (const module of modules) {
95
- const html = await module.renderToHtml();
96
- await writeFile(`${outdir}/${basename(module.file).split('.')[0]}.html`, html);
97
- }
98
- await rm(tmp, { recursive: true, force: true });
99
- console.log('\x1b[36m%s\x1b[0m', `build complete!`);
100
- exit(0);
101
- }
102
- catch (error) {
103
- console.error(error);
104
- exit(1);
105
- }
106
- };
107
- const program = termost({
108
- name: 'keycloakify-angular-email',
109
- description: 'Keycloakify CLI',
110
- version: pkg.version,
111
- onException: (error) => {
112
- console.error(error);
113
- exit(1);
114
- },
115
- });
116
- program.option({
117
- key: 'emailFilesPath',
118
- name: { long: 'emailFilesPath', short: 'p' },
119
- description: '',
120
- defaultValue: '',
121
- });
122
- program.option({
123
- key: 'outDir',
124
- name: { long: 'outDir', short: 'o' },
125
- description: '',
126
- defaultValue: 'dist/emails',
127
- });
128
- program
129
- .command({
130
- name: 'build',
131
- description: 'build angular components to html',
132
- })
133
- .input({
134
- key: 'emailFilesPath',
135
- type: 'text',
136
- defaultValue: undefined,
137
- label: "what's the path of your email components files?",
138
- skip(context) {
139
- return Boolean(context.emailFilesPath);
140
- },
141
- })
142
- .input({
143
- key: 'outDir',
144
- type: 'text',
145
- defaultValue: 'dist/emails',
146
- label: 'where do you want to output html files?',
147
- skip(context) {
148
- return Boolean(context.outDir);
149
- },
150
- })
151
- .task({
152
- handler: async (context) => {
153
- await build(context.emailFilesPath, context.outDir);
154
- },
155
- });
2
+ import{angularEsbuildPlugin as b}from"@keycloakify/angular-email/esbuild";import k from"esbuild";import{readdir as w,rm as f,writeFile as P}from"node:fs/promises";import{basename as x,join as g,resolve as p}from"node:path";import{cwd as c,exit as m}from"node:process";import{pathToFileURL as F}from"node:url";import{termost as D}from"termost";import v from"../package.json"with{type:"json"};async function E(e,t,i,a=[]){const r=await k.build({entryPoints:e,bundle:!0,splitting:!0,outdir:i,absWorkingDir:t,platform:"node",sourcemap:!0,packages:"bundle",external:["juice",...a],format:"esm",outExtension:{".js":".mjs"},target:"node20",plugins:[b(t)],metafile:!0});return Object.entries(r.metafile.outputs).reduce((l,[s,o])=>(o.entryPoint&&(l[p(t,o.entryPoint)]=F(p(t,s)).toString()),l),{})}async function j(e,t){try{const i=async r=>{const l=await w(r,{withFileTypes:!0}),s=[];for(const o of l)o.isFile()?s.push(o):o.isDirectory()&&s.push(...await i(g(o.parentPath,o.name)));return s};return(await i(e)).map(r=>g(r.parentPath,r.name)).filter(r=>t?t(r):!0)}catch(i){throw console.error(`Error scanning directory: ${i.message}`),i}}const C=async(e,t="dist/emails",i=[])=>{e||(console.error("emailFilesPath is required!"),m(1));try{console.log(`building emails to ${t}...`);const a=g(c(),t,".tmp");await f(t,{recursive:!0,force:!0});const r=await j(p(c(),e),n=>n.endsWith(".ts"));r.length||(console.error("no templates found!"),m(1));const l=[...r],s=await E(l,c(),a,i),o=r.map(async n=>{const u=s[n]??s[p(c(),n)];if(!u)throw new Error(`Failed to locate bundled URL for "${n}". Ensure getTemplates() returns absolute paths and esbuild.metafile entryPoint mapping uses the same base.`);const h=await import(u);return console.log(`- ${n}`),{...h,file:n}}),y=await Promise.all(o);for(const n of y){const u=await n.renderToHtml();await P(`${t}/${x(n.file).split(".")[0]}.html`,u)}await f(a,{recursive:!0,force:!0}),console.log("\x1B[36m%s\x1B[0m","build complete!"),m(0)}catch(a){console.error(a),m(1)}},d=D({name:"keycloakify-angular-email",description:"Keycloakify CLI",version:v.version,onException:e=>{console.error(e),m(1)}});d.option({key:"emailFilesPath",name:{long:"emailFilesPath",short:"p"},description:"input directory for angular email components",defaultValue:""}),d.option({key:"outDir",name:{long:"outDir",short:"o"},description:"output directory for generated html",defaultValue:"dist/emails"}),d.option({key:"externals",name:{long:"externals",short:"e"},description:"Comma-separated packages to mark as external in esbuild (e.g. tailwindcss,postcss,postcss-calc)",defaultValue:""}),d.command({name:"build",description:"build angular components to html"}).input({key:"emailFilesPath",type:"text",defaultValue:void 0,label:"what's the path of your email components files?",skip(e){return!!e.emailFilesPath}}).input({key:"outDir",type:"text",defaultValue:"dist/emails",label:"where do you want to output html files?",skip(e){return!!e.outDir}}).input({key:"externals",type:"text",label:"any additional package to mark as external (comma separated)?",skip(){return!0}}).task({handler:async e=>{let t=[];e.externals&&(t=e.externals.split(",").map(i=>i.trim()).filter(Boolean)),await C(e.emailFilesPath,e.outDir,t)}});
@@ -0,0 +1,70 @@
1
+ import tailwindcss from '@tailwindcss/postcss';
2
+ import postcss from 'postcss';
3
+ import calc from 'postcss-calc';
4
+ import properties from 'postcss-custom-properties';
5
+ import logical from 'postcss-logical';
6
+ import preset from 'postcss-preset-env';
7
+
8
+ const cssProcessor = async (style) => {
9
+ try {
10
+ const result = await postcss(tailwindcss(), properties({ preserve: false }), calc({ preserve: false }), {
11
+ postcssPlugin: 'inline-tw-vars-from-layer',
12
+ Once(root) {
13
+ const defaultVars = new Map();
14
+ // 1. Find the default variables in @layer properties
15
+ root.walkAtRules('layer', (layerRule) => {
16
+ if (layerRule.params === 'properties') {
17
+ layerRule.walkDecls(/^--tw-/, (decl) => {
18
+ defaultVars.set(decl.prop, decl.value);
19
+ });
20
+ }
21
+ });
22
+ // 2. Walk all rules and replace var() usages
23
+ root.walkDecls((decl) => {
24
+ decl.value = decl.value.replace(/var\((--tw-[^,)]+)\)/g, (match, varName) => {
25
+ return defaultVars.has(varName) ? defaultVars.get(varName) : match;
26
+ });
27
+ });
28
+ },
29
+ }, {
30
+ postcssPlugin: 'remove-unused-tw-vars',
31
+ Once(root) {
32
+ root.walkDecls(/^--tw-/, (decl) => {
33
+ decl.remove();
34
+ });
35
+ root.walkAtRules('property', (atRule) => {
36
+ if (atRule.params.startsWith('--tw-')) {
37
+ atRule.remove();
38
+ }
39
+ });
40
+ root.walkAtRules('layer', (atRule) => {
41
+ if (atRule.params === 'properties') {
42
+ atRule.remove();
43
+ }
44
+ });
45
+ },
46
+ }, logical({
47
+ blockDirection: 'top-to-bottom',
48
+ inlineDirection: 'left-to-right',
49
+ }), preset({
50
+ stage: 4,
51
+ minimumVendorImplementations: 3,
52
+ logical: {
53
+ blockDirection: 'top-to-bottom',
54
+ inlineDirection: 'left-to-right',
55
+ },
56
+ })).process(style, { map: false, from: undefined });
57
+ return result.css;
58
+ }
59
+ catch (error) {
60
+ console.error('CSS processing failed:', error);
61
+ throw new Error(`Failed to process CSS: ${error instanceof Error ? error.message : 'Unknown error'}`);
62
+ }
63
+ };
64
+
65
+ /**
66
+ * Generated bundle index. Do not edit.
67
+ */
68
+
69
+ export { cssProcessor };
70
+ //# sourceMappingURL=keycloakify-angular-email-tailwindcss-preset-email-css-processor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keycloakify-angular-email-tailwindcss-preset-email-css-processor.mjs","sources":["../../../projects/angular-email/tailwindcss-preset-email/css-processor/css-processor.ts","../../../projects/angular-email/tailwindcss-preset-email/css-processor/keycloakify-angular-email-tailwindcss-preset-email-css-processor.ts"],"sourcesContent":["import tailwindcss from '@tailwindcss/postcss';\nimport postcss from 'postcss';\nimport calc from 'postcss-calc';\nimport properties from 'postcss-custom-properties';\nimport logical, { type DirectionFlow } from 'postcss-logical';\nimport preset from 'postcss-preset-env';\n\nexport const cssProcessor = async (style: string) => {\n try {\n const result = await postcss(\n tailwindcss(),\n properties({ preserve: false }),\n calc({ preserve: false }),\n {\n postcssPlugin: 'inline-tw-vars-from-layer',\n Once(root) {\n const defaultVars = new Map<string, string>();\n\n // 1. Find the default variables in @layer properties\n root.walkAtRules('layer', (layerRule) => {\n if (layerRule.params === 'properties') {\n layerRule.walkDecls(/^--tw-/, (decl) => {\n defaultVars.set(decl.prop, decl.value);\n });\n }\n });\n\n // 2. Walk all rules and replace var() usages\n root.walkDecls((decl) => {\n decl.value = decl.value.replace(/var\\((--tw-[^,)]+)\\)/g, (match, varName) => {\n return defaultVars.has(varName) ? defaultVars.get(varName)! : match;\n });\n });\n },\n },\n {\n postcssPlugin: 'remove-unused-tw-vars',\n Once(root) {\n root.walkDecls(/^--tw-/, (decl) => {\n decl.remove();\n });\n root.walkAtRules('property', (atRule) => {\n if (atRule.params.startsWith('--tw-')) {\n atRule.remove();\n }\n });\n root.walkAtRules('layer', (atRule) => {\n if (atRule.params === 'properties') {\n atRule.remove();\n }\n });\n },\n },\n logical({\n blockDirection: 'top-to-bottom' as DirectionFlow,\n inlineDirection: 'left-to-right' as DirectionFlow,\n }),\n preset({\n stage: 4,\n minimumVendorImplementations: 3,\n logical: {\n blockDirection: 'top-to-bottom' as DirectionFlow,\n inlineDirection: 'left-to-right' as DirectionFlow,\n },\n }),\n ).process(style, { map: false, from: undefined });\n return result.css;\n } catch (error) {\n console.error('CSS processing failed:', error);\n throw new Error(`Failed to process CSS: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n};\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;MAOa,YAAY,GAAG,OAAO,KAAa,KAAI;AAClD,IAAA,IAAI;QACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAC1B,WAAW,EAAE,EACb,UAAU,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EAC/B,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,EACzB;AACE,YAAA,aAAa,EAAE,2BAA2B;AAC1C,YAAA,IAAI,CAAC,IAAI,EAAA;AACP,gBAAA,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB;;gBAG7C,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,SAAS,KAAI;AACtC,oBAAA,IAAI,SAAS,CAAC,MAAM,KAAK,YAAY,EAAE;wBACrC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,KAAI;4BACrC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC;AACxC,yBAAC,CAAC;;AAEN,iBAAC,CAAC;;AAGF,gBAAA,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,KAAI;AACtB,oBAAA,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,uBAAuB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAI;AAC1E,wBAAA,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAE,GAAG,KAAK;AACrE,qBAAC,CAAC;AACJ,iBAAC,CAAC;aACH;SACF,EACD;AACE,YAAA,aAAa,EAAE,uBAAuB;AACtC,YAAA,IAAI,CAAC,IAAI,EAAA;gBACP,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,IAAI,KAAI;oBAChC,IAAI,CAAC,MAAM,EAAE;AACf,iBAAC,CAAC;gBACF,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,CAAC,MAAM,KAAI;oBACtC,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE;wBACrC,MAAM,CAAC,MAAM,EAAE;;AAEnB,iBAAC,CAAC;gBACF,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,MAAM,KAAI;AACnC,oBAAA,IAAI,MAAM,CAAC,MAAM,KAAK,YAAY,EAAE;wBAClC,MAAM,CAAC,MAAM,EAAE;;AAEnB,iBAAC,CAAC;aACH;AACF,SAAA,EACD,OAAO,CAAC;AACN,YAAA,cAAc,EAAE,eAAgC;AAChD,YAAA,eAAe,EAAE,eAAgC;SAClD,CAAC,EACF,MAAM,CAAC;AACL,YAAA,KAAK,EAAE,CAAC;AACR,YAAA,4BAA4B,EAAE,CAAC;AAC/B,YAAA,OAAO,EAAE;AACP,gBAAA,cAAc,EAAE,eAAgC;AAChD,gBAAA,eAAe,EAAE,eAAgC;AAClD,aAAA;AACF,SAAA,CAAC,CACH,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QACjD,OAAO,MAAM,CAAC,GAAG;;IACjB,OAAO,KAAK,EAAE;AACd,QAAA,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC;AAC9C,QAAA,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,YAAY,KAAK,GAAG,KAAK,CAAC,OAAO,GAAG,eAAe,CAAA,CAAE,CAAC;;AAEzG;;ACvEA;;AAEG;;;;"}