@tamagui/static 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/compilerHost.cjs +170 -6
- package/dist/esm/compilerHost.mjs +171 -7
- package/dist/esm/compilerHost.mjs.map +1 -1
- package/package.json +19 -19
- package/src/compilerHost.ts +220 -10
- package/types/compilerHost.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/static",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"gitHead": "a49cc7ea6b93ba384e77a4880ae48ac4a5635c14",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"source": "src/index.ts",
|
|
@@ -45,22 +45,22 @@
|
|
|
45
45
|
"clean:build": "tamagui-build clean:build"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@tamagui/animation-helpers": "3.0.
|
|
49
|
-
"@tamagui/cli-color": "3.0.
|
|
50
|
-
"@tamagui/compiler-core": "3.0.
|
|
51
|
-
"@tamagui/config-default": "3.0.
|
|
52
|
-
"@tamagui/core": "3.0.
|
|
53
|
-
"@tamagui/dom": "3.0.
|
|
54
|
-
"@tamagui/fake-react-native": "3.0.
|
|
55
|
-
"@tamagui/generate-themes": "3.0.
|
|
56
|
-
"@tamagui/helpers": "3.0.
|
|
57
|
-
"@tamagui/helpers-node": "3.0.
|
|
58
|
-
"@tamagui/react-native-web-internals": "3.0.
|
|
59
|
-
"@tamagui/react-native-web-lite": "3.0.
|
|
60
|
-
"@tamagui/shorthands": "3.0.
|
|
61
|
-
"@tamagui/style-grammar": "3.0.
|
|
62
|
-
"@tamagui/types": "3.0.
|
|
63
|
-
"@tamagui/web": "3.0.
|
|
48
|
+
"@tamagui/animation-helpers": "3.0.4",
|
|
49
|
+
"@tamagui/cli-color": "3.0.4",
|
|
50
|
+
"@tamagui/compiler-core": "3.0.4",
|
|
51
|
+
"@tamagui/config-default": "3.0.4",
|
|
52
|
+
"@tamagui/core": "3.0.4",
|
|
53
|
+
"@tamagui/dom": "3.0.4",
|
|
54
|
+
"@tamagui/fake-react-native": "3.0.4",
|
|
55
|
+
"@tamagui/generate-themes": "3.0.4",
|
|
56
|
+
"@tamagui/helpers": "3.0.4",
|
|
57
|
+
"@tamagui/helpers-node": "3.0.4",
|
|
58
|
+
"@tamagui/react-native-web-internals": "3.0.4",
|
|
59
|
+
"@tamagui/react-native-web-lite": "3.0.4",
|
|
60
|
+
"@tamagui/shorthands": "3.0.4",
|
|
61
|
+
"@tamagui/style-grammar": "3.0.4",
|
|
62
|
+
"@tamagui/types": "3.0.4",
|
|
63
|
+
"@tamagui/web": "3.0.4",
|
|
64
64
|
"browserslist": "^4.28.1",
|
|
65
65
|
"check-dependency-version-consistency": "^4.1.0",
|
|
66
66
|
"esbuild": "0.28.1",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"react-native-web": "~0.21.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@tamagui/build": "3.0.
|
|
76
|
+
"@tamagui/build": "3.0.4",
|
|
77
77
|
"@types/find-root": "^1.1.2",
|
|
78
78
|
"@types/node": "^22.1.0",
|
|
79
79
|
"@types/webpack": "^4.41.26",
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"url": "git+https://github.com/tamagui/tamagui.git",
|
|
95
95
|
"directory": "code/compiler/static"
|
|
96
96
|
},
|
|
97
|
-
"releaseSourceCommit": "
|
|
97
|
+
"releaseSourceCommit": "c92cf0fea7c8cc8c33eda1f2a95670c66846d251"
|
|
98
98
|
}
|
package/src/compilerHost.ts
CHANGED
|
@@ -12,9 +12,15 @@ import type {
|
|
|
12
12
|
ZeroRule,
|
|
13
13
|
} from '@tamagui/compiler-core'
|
|
14
14
|
import {
|
|
15
|
+
childNode,
|
|
16
|
+
childNodes,
|
|
15
17
|
collectLeaves,
|
|
18
|
+
findAstNode,
|
|
19
|
+
identifierName,
|
|
20
|
+
parseModuleAst,
|
|
16
21
|
zeroRuleMessage,
|
|
17
22
|
zeroThemeBoundaryMessage,
|
|
23
|
+
type AstNode,
|
|
18
24
|
} from '@tamagui/compiler-core'
|
|
19
25
|
import {
|
|
20
26
|
StyleObjectIdentifier,
|
|
@@ -828,18 +834,199 @@ const VIEW_WRAPPER_PROPS =
|
|
|
828
834
|
* Whether an element can render RN's host component directly instead of
|
|
829
835
|
* <View>. A child is disqualifying because <View> resets TextAncestorContext
|
|
830
836
|
* for its descendants when it sits inside a <Text>, and no per-element
|
|
831
|
-
* analysis can rule that out; with no children nothing can observe it.
|
|
832
|
-
*
|
|
837
|
+
* analysis can rule that out; with no children nothing can observe it. The
|
|
838
|
+
* native style wrapper performs that reset itself, so children are allowed
|
|
839
|
+
* through it (`resetsTextAncestor`). A spread is disqualifying because it can
|
|
840
|
+
* carry the aria props this checks for.
|
|
833
841
|
*/
|
|
834
|
-
function isBareHostView(
|
|
842
|
+
function isBareHostView(
|
|
843
|
+
element: MaterializedElement,
|
|
844
|
+
resetsTextAncestor: boolean
|
|
845
|
+
): boolean {
|
|
835
846
|
return !element.entries.some(
|
|
836
847
|
(entry) =>
|
|
837
|
-
entry.kind === 'child' ||
|
|
848
|
+
(entry.kind === 'child' && !resetsTextAncestor) ||
|
|
838
849
|
entry.kind === 'spread' ||
|
|
839
850
|
(entry.kind === 'prop' && VIEW_WRAPPER_PROPS.test(entry.name))
|
|
840
851
|
)
|
|
841
852
|
}
|
|
842
853
|
|
|
854
|
+
/**
|
|
855
|
+
* props RN's <Text> remaps, or that make it pressable, selectable or virtual;
|
|
856
|
+
* a <Text> without them hands its host a fixed set the native style wrapper
|
|
857
|
+
* reproduces
|
|
858
|
+
*/
|
|
859
|
+
const TEXT_WRAPPER_PROPS =
|
|
860
|
+
/^(aria-|accessib|on[A-Z]|disabled$|id$|nativeID$|numberOfLines$|ellipsizeMode$|selectable$|selectionColor$|suppressHighlighting$|pressRetentionOffset$|role$|allowFontScaling$)/
|
|
861
|
+
|
|
862
|
+
function isBareHostText(element: MaterializedElement): boolean {
|
|
863
|
+
return !element.entries.some(
|
|
864
|
+
(entry) =>
|
|
865
|
+
entry.kind === 'spread' ||
|
|
866
|
+
(entry.kind === 'prop' && TEXT_WRAPPER_PROPS.test(entry.name))
|
|
867
|
+
)
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/** ancestors that render their children in the calling component's theme scope */
|
|
871
|
+
const SCOPE_PRESERVING_PARENTS: Record<string, readonly string[]> = {
|
|
872
|
+
react: ['Fragment'],
|
|
873
|
+
'react-native': ['View', 'ScrollView', 'SafeAreaView'],
|
|
874
|
+
'@tamagui/core': ['View', 'Stack', 'XStack', 'YStack', 'ZStack'],
|
|
875
|
+
tamagui: ['View', 'Stack', 'XStack', 'YStack', 'ZStack'],
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const SCOPE_PROPS = new Set(['theme', 'themeInverse', 'themeShallow', 'componentName'])
|
|
879
|
+
|
|
880
|
+
// every fast-path element of one module asks about the same source
|
|
881
|
+
let inlineModule: {
|
|
882
|
+
source: string
|
|
883
|
+
program: AstNode
|
|
884
|
+
imports: Map<string, string>
|
|
885
|
+
} | null = null
|
|
886
|
+
|
|
887
|
+
function inlineModuleOf(input: LoweringCandidateInput) {
|
|
888
|
+
if (inlineModule?.source === input.source) return inlineModule
|
|
889
|
+
const program = parseModuleAst(input.source, input.id)
|
|
890
|
+
// local name -> 'source#imported'
|
|
891
|
+
const imports = new Map<string, string>()
|
|
892
|
+
for (const statement of childNodes(program, 'body')) {
|
|
893
|
+
if (statement.type !== 'ImportDeclaration' || statement.importKind === 'type')
|
|
894
|
+
continue
|
|
895
|
+
const from = childNode(statement, 'source')?.value
|
|
896
|
+
for (const specifier of childNodes(statement, 'specifiers')) {
|
|
897
|
+
if (specifier.type !== 'ImportSpecifier') continue
|
|
898
|
+
const local = identifierName(childNode(specifier, 'local'))
|
|
899
|
+
const imported = identifierName(childNode(specifier, 'imported'))
|
|
900
|
+
if (local && imported) imports.set(local, `${from}#${imported}`)
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
inlineModule = { source: input.source, program, imports }
|
|
904
|
+
return inlineModule
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function keepsThemeScope(element: AstNode, imports: Map<string, string>): boolean {
|
|
908
|
+
if (element.type === 'JSXFragment') return true
|
|
909
|
+
const opening = childNode(element, 'openingElement')
|
|
910
|
+
if (!opening) return false
|
|
911
|
+
for (const attribute of childNodes(opening, 'attributes')) {
|
|
912
|
+
if (attribute.type !== 'JSXAttribute') return false
|
|
913
|
+
const name = identifierName(childNode(attribute, 'name'))
|
|
914
|
+
if (!name || SCOPE_PROPS.has(name)) return false
|
|
915
|
+
}
|
|
916
|
+
const [from, imported] = (
|
|
917
|
+
imports.get(identifierName(childNode(opening, 'name')) ?? '') ?? ''
|
|
918
|
+
).split('#')
|
|
919
|
+
return !!SCOPE_PRESERVING_PARENTS[from!]?.includes(imported!)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function isComponentFunction(fn: AstNode, parent: AstNode | undefined): boolean {
|
|
923
|
+
const pascal = (name: string | null) => !!name && /^[A-Z]/.test(name)
|
|
924
|
+
if (fn.type === 'FunctionDeclaration')
|
|
925
|
+
return pascal(identifierName(childNode(fn, 'id')))
|
|
926
|
+
if (!parent) return false
|
|
927
|
+
if (parent.type === 'VariableDeclarator') {
|
|
928
|
+
return (
|
|
929
|
+
childNode(parent, 'init') === fn && pascal(identifierName(childNode(parent, 'id')))
|
|
930
|
+
)
|
|
931
|
+
}
|
|
932
|
+
if (parent.type === 'CallExpression' && childNodes(parent, 'arguments')[0] === fn) {
|
|
933
|
+
const callee = childNode(parent, 'callee')
|
|
934
|
+
const name =
|
|
935
|
+
callee?.type === 'MemberExpression'
|
|
936
|
+
? identifierName(childNode(callee, 'property'))
|
|
937
|
+
: identifierName(callee)
|
|
938
|
+
return name === 'memo' || name === 'forwardRef'
|
|
939
|
+
}
|
|
940
|
+
return false
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/** `.map(fn)` and `Array.from(x, fn)` call their callback during the caller's render */
|
|
944
|
+
function isRenderCallback(fn: AstNode, parent: AstNode | undefined): boolean {
|
|
945
|
+
if (parent?.type !== 'CallExpression') return false
|
|
946
|
+
const callee = childNode(parent, 'callee')
|
|
947
|
+
if (callee?.type !== 'MemberExpression') return false
|
|
948
|
+
const args = childNodes(parent, 'arguments')
|
|
949
|
+
const method = identifierName(childNode(callee, 'property'))
|
|
950
|
+
if (method === 'map') return args[0] === fn
|
|
951
|
+
return (
|
|
952
|
+
method === 'from' &&
|
|
953
|
+
identifierName(childNode(callee, 'object')) === 'Array' &&
|
|
954
|
+
args[1] === fn
|
|
955
|
+
)
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const FUNCTION_TYPES = new Set([
|
|
959
|
+
'FunctionDeclaration',
|
|
960
|
+
'FunctionExpression',
|
|
961
|
+
'ArrowFunctionExpression',
|
|
962
|
+
])
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Whether a fast-path element can render inline in its component's render
|
|
966
|
+
* (`Fast.inline(<Fast />)`), and whether that call needs JSX braces. Only when
|
|
967
|
+
* the component's theme scope is provably the element's own: the element sits
|
|
968
|
+
* in its returned JSX, possibly through conditionals and `.map` callbacks,
|
|
969
|
+
* under nothing but fragments and plain views that never open a theme scope.
|
|
970
|
+
*/
|
|
971
|
+
function nativeInlinePlacement(
|
|
972
|
+
input: LoweringCandidateInput
|
|
973
|
+
): { braces: boolean } | null {
|
|
974
|
+
if (input.element.form !== 'jsx') return null
|
|
975
|
+
const { program, imports } = inlineModuleOf(input)
|
|
976
|
+
const { start, end } = input.element.span
|
|
977
|
+
const path: { node: AstNode; key: string | null }[] = []
|
|
978
|
+
const element = findAstNode(
|
|
979
|
+
program,
|
|
980
|
+
(node, _parent, key) => {
|
|
981
|
+
path.push({ node, key })
|
|
982
|
+
return node.type === 'JSXElement' && node.start === start && node.end === end
|
|
983
|
+
},
|
|
984
|
+
{ start, end }
|
|
985
|
+
)
|
|
986
|
+
if (!element) return null
|
|
987
|
+
const parentType = path.at(-2)?.node.type
|
|
988
|
+
const braces = parentType === 'JSXElement' || parentType === 'JSXFragment'
|
|
989
|
+
|
|
990
|
+
let index = path.length - 2
|
|
991
|
+
while (index >= 0) {
|
|
992
|
+
const { node } = path[index]!
|
|
993
|
+
const childKey = path[index + 1]!.key
|
|
994
|
+
switch (node.type) {
|
|
995
|
+
case 'JSXElement':
|
|
996
|
+
case 'JSXFragment':
|
|
997
|
+
if (childKey !== 'children' || !keepsThemeScope(node, imports)) return null
|
|
998
|
+
break
|
|
999
|
+
case 'ConditionalExpression':
|
|
1000
|
+
if (childKey === 'test') return null
|
|
1001
|
+
break
|
|
1002
|
+
case 'JSXExpressionContainer':
|
|
1003
|
+
case 'LogicalExpression':
|
|
1004
|
+
case 'ParenthesizedExpression':
|
|
1005
|
+
case 'ChainExpression':
|
|
1006
|
+
break
|
|
1007
|
+
case 'ReturnStatement':
|
|
1008
|
+
case 'ArrowFunctionExpression': {
|
|
1009
|
+
if (node.type === 'ArrowFunctionExpression' && childKey !== 'body') return null
|
|
1010
|
+
// a return belongs to its nearest enclosing function
|
|
1011
|
+
let fnIndex = index
|
|
1012
|
+
while (fnIndex >= 0 && !FUNCTION_TYPES.has(path[fnIndex]!.node.type)) fnIndex--
|
|
1013
|
+
if (fnIndex < 0) return null
|
|
1014
|
+
const fn = path[fnIndex]!.node
|
|
1015
|
+
const fnParent = path[fnIndex - 1]?.node
|
|
1016
|
+
if (isComponentFunction(fn, fnParent)) return { braces }
|
|
1017
|
+
if (!isRenderCallback(fn, fnParent)) return null
|
|
1018
|
+
// continue from the call, which is an expression in the outer render
|
|
1019
|
+
index = fnIndex - 1
|
|
1020
|
+
break
|
|
1021
|
+
}
|
|
1022
|
+
default:
|
|
1023
|
+
return null
|
|
1024
|
+
}
|
|
1025
|
+
index--
|
|
1026
|
+
}
|
|
1027
|
+
return null
|
|
1028
|
+
}
|
|
1029
|
+
|
|
843
1030
|
function entrySource(input: LoweringCandidateInput, entry: DOMPropEntry): string {
|
|
844
1031
|
return entry.value.kind === 'static'
|
|
845
1032
|
? JSON.stringify(entry.value.value)
|
|
@@ -2833,7 +3020,7 @@ export function createTamaguiCompilerHost(
|
|
|
2833
3020
|
!input.element.entries.some((entry) => entry.kind === 'spread')
|
|
2834
3021
|
const nativeStyleImports = [
|
|
2835
3022
|
{
|
|
2836
|
-
content: `\nfunction ${nativeStyleLocal}() { return ${nativeStyleLocal}._ ?? (${nativeStyleLocal}._ = ${JSON.stringify(
|
|
3023
|
+
content: `\nfunction ${nativeStyleLocal}() { return ${nativeStyleLocal}._ ?? (${nativeStyleLocal}._ = ${JSON.stringify(collapseNativeShorthands(nativeStyle) ?? {})}); } ${nativeStyleLocal}();`,
|
|
2837
3024
|
origin: input.element.component.span,
|
|
2838
3025
|
},
|
|
2839
3026
|
]
|
|
@@ -3126,12 +3313,18 @@ export function createTamaguiCompilerHost(
|
|
|
3126
3313
|
// saves a context read plus a React element per view per render.
|
|
3127
3314
|
// Only when the call site needs nothing the wrapper adds: the aria
|
|
3128
3315
|
// remapping (visible in the static props) and the TextAncestorContext
|
|
3129
|
-
// reset, which only a child can observe. <Text>
|
|
3130
|
-
//
|
|
3316
|
+
// reset, which only a child can observe. Text keeps <Text> as the
|
|
3317
|
+
// component, and the native style wrapper renders its host itself
|
|
3318
|
+
// when the call site passes none of the props <Text> remaps.
|
|
3131
3319
|
const useHostView =
|
|
3132
3320
|
options.experimentalNativeFastPath &&
|
|
3133
3321
|
nativeName === 'View' &&
|
|
3134
|
-
isBareHostView(input.element)
|
|
3322
|
+
isBareHostView(input.element, !!nativeFastPath)
|
|
3323
|
+
const useHostText =
|
|
3324
|
+
options.experimentalNativeFastPath &&
|
|
3325
|
+
nativeName === 'Text' &&
|
|
3326
|
+
!!nativeFastPath &&
|
|
3327
|
+
isBareHostText(input.element)
|
|
3135
3328
|
const nativeExport = useHostView ? 'unstable_NativeView' : nativeName
|
|
3136
3329
|
// the local is named after the BINDING, not the component: a file can
|
|
3137
3330
|
// hold both kinds at once, and a shared name would emit two different
|
|
@@ -3188,9 +3381,26 @@ export function createTamaguiCompilerHost(
|
|
|
3188
3381
|
`Compiled ${input.element.form} call has no editable props argument`
|
|
3189
3382
|
)
|
|
3190
3383
|
}
|
|
3384
|
+
const inline = nativeInlinePlacement(input)
|
|
3385
|
+
const inlineEdits = inline
|
|
3386
|
+
? [
|
|
3387
|
+
{
|
|
3388
|
+
start: input.element.span.start,
|
|
3389
|
+
end: input.element.span.start,
|
|
3390
|
+
content: `${inline.braces ? '{' : ''}${fastLocal}.inline(`,
|
|
3391
|
+
origin: input.element.span,
|
|
3392
|
+
},
|
|
3393
|
+
{
|
|
3394
|
+
start: input.element.span.end,
|
|
3395
|
+
end: input.element.span.end,
|
|
3396
|
+
content: `)${inline.braces ? '}' : ''}`,
|
|
3397
|
+
origin: input.element.span,
|
|
3398
|
+
},
|
|
3399
|
+
]
|
|
3400
|
+
: []
|
|
3191
3401
|
return {
|
|
3192
3402
|
ok: true,
|
|
3193
|
-
edits: [...tagEdits, ...styleEdits],
|
|
3403
|
+
edits: [...inlineEdits, ...tagEdits, ...styleEdits],
|
|
3194
3404
|
css: [],
|
|
3195
3405
|
imports: [
|
|
3196
3406
|
...nativeStyleImports,
|
|
@@ -3200,7 +3410,7 @@ export function createTamaguiCompilerHost(
|
|
|
3200
3410
|
origin: input.element.component.span,
|
|
3201
3411
|
},
|
|
3202
3412
|
{
|
|
3203
|
-
content: `\nconst ${fastLocal} = require('@tamagui/core')._withNativeStyle(${nativeLocal}, ${nativeStyleSource}, ${nativeFastPath.mappingLocal});`,
|
|
3413
|
+
content: `\nconst ${fastLocal} = require('@tamagui/core')._withNativeStyle(${nativeLocal}, ${nativeStyleSource}, ${nativeFastPath.mappingLocal}${useHostView ? `, 'view'` : useHostText ? `, 'text'` : ''});`,
|
|
3204
3414
|
origin: input.element.component.span,
|
|
3205
3415
|
},
|
|
3206
3416
|
],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compilerHost.d.ts","sourceRoot":"","sources":["../src/compilerHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,oBAAoB,EACpB,cAAc,EASf,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"compilerHost.d.ts","sourceRoot":"","sources":["../src/compilerHost.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,oBAAoB,EACpB,cAAc,EASf,MAAM,wBAAwB,CAAA;AA0C/B,OAAO,KAAK,EAAiC,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAExF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAIhE,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAAyB;IACxC,+EAA+E;IAC/E,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAClD;AAED,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,gBAAgB,EAAE,EACvC,gBAAgB,EAAE,SAAS,uBAAuB,EAAE,GACnD,yBAAyB,CAS3B;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,cAAc,CAAA;IACtB,aAAa,EAAE,qBAAqB,CAAA;IACpC,UAAU,EAAE,gBAAgB,EAAE,CAAA;IAC9B,gBAAgB,EAAE,uBAAuB,EAAE,CAAA;IAC3C,qFAAqF;IACrF,QAAQ,CAAC,EAAE,yBAAyB,CAAA;IACpC,wEAAwE;IACxE,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,mEAAmE;IACnE,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAmoCD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,0BAA0B,GAClC,oBAAoB,CAk3FtB"}
|