From 1612736f4165b29dffc72536d8160579879441bf Mon Sep 17 00:00:00 2001 From: Carson Holgate Date: Mon, 5 Jan 2026 11:36:52 -0800 Subject: [PATCH 1/5] Local changes to support ayab-valdi --- bin/BUILD.bazel | 6 +- bzl/prebuilt_tools.bzl | 10 + bzl/valdi/valdi_exported_library.bzl | 23 -- .../Processors/PrependWebJsProcessor.swift | 18 ++ compiler/compiler/scripts/update_compiler.sh | 17 +- npm_modules/cli/src/commands/projectsync.ts | 109 ++++++++- .../src/valdi/coreutils/web/tsconfig.json | 4 + .../src/valdi/drawing/web/tsconfig.json | 6 +- .../src/valdi/file_system/web/tsconfig.json | 4 + .../src/valdi/persistence/web/tsconfig.json | 4 + .../src/valdi/valdi_core/src/ModuleLoader.ts | 10 +- .../src/valdi/valdi_core/web/tsconfig.json | 2 +- .../src/valdi/valdi_http/web/tsconfig.json | 4 + .../valdi/valdi_protobuf/web/tsconfig.json | 4 +- .../src/valdi/valdi_tsx/web/tsconfig.json | 4 + .../src/ValdiWebRendererDelegate.ts | 102 +++++++- .../web_renderer/src/WebAnimationManager.ts | 225 ++++++++++++++++++ .../web_renderer/src/styles/ValdiWebStyles.ts | 2 +- .../web_renderer/src/views/WebValdiLayout.ts | 115 ++++++++- 19 files changed, 620 insertions(+), 49 deletions(-) create mode 100644 src/valdi_modules/src/valdi/web_renderer/src/WebAnimationManager.ts diff --git a/bin/BUILD.bazel b/bin/BUILD.bazel index 48d4df70..3f66d7cf 100644 --- a/bin/BUILD.bazel +++ b/bin/BUILD.bazel @@ -6,6 +6,8 @@ load( "pngquant_linux", "pngquant_macos", "valdi_compiler_companion_files", + "valdi_compiler_linux", + "valdi_compiler_macos", ) filegroup( @@ -18,8 +20,8 @@ native_binary( name = "valdi_compiler", src = select( { - "@bazel_tools//src/conditions:darwin": "@valdi_compiler_macos//:valdi_compiler", - "@bazel_tools//src/conditions:linux_x86_64": "@valdi_compiler_linux//:valdi_compiler", + "@bazel_tools//src/conditions:darwin": valdi_compiler_macos(), + "@bazel_tools//src/conditions:linux_x86_64": valdi_compiler_linux(), }, ), out = "valdi_compiler", diff --git a/bzl/prebuilt_tools.bzl b/bzl/prebuilt_tools.bzl index c04b5be0..d6533cec 100644 --- a/bzl/prebuilt_tools.bzl +++ b/bzl/prebuilt_tools.bzl @@ -12,6 +12,16 @@ def pngquant_macos(): return "@valdi_pngquant_macos//:pngquant" return "pngquant/macos/pngquant" +def valdi_compiler_macos(): + if INTERNAL_BUILD: + pass + return "compiler/macos/valdi_compiler" + +def valdi_compiler_linux(): + if INTERNAL_BUILD: + pass + return "compiler/linux/valdi_compiler" + def valdi_compiler_companion_files(): if INTERNAL_BUILD: return ["@valdi_compiler_companion//:all_files"] diff --git a/bzl/valdi/valdi_exported_library.bzl b/bzl/valdi/valdi_exported_library.bzl index f232b55c..ad419bf7 100644 --- a/bzl/valdi/valdi_exported_library.bzl +++ b/bzl/valdi/valdi_exported_library.bzl @@ -1,6 +1,5 @@ load("@build_bazel_rules_apple//apple:apple.bzl", "apple_xcframework") load("//bzl:expand_template.bzl", "expand_template") -load("//bzl/android:collect_android_assets.bzl", "collect_android_assets") load("//bzl/valdi:rewrite_hdrs.bzl", "rewrite_hdrs") load("//bzl/valdi:suffixed_deps.bzl", "get_suffixed_deps") load("//bzl/valdi:valdi_collapse_web_paths.bzl", "collapse_native_paths", "collapse_web_paths", "generate_register_native_modules") @@ -77,28 +76,6 @@ def valdi_exported_library( java_deps = java_deps + get_suffixed_deps(deps, "_kt") - collect_android_assets( - name = "{}_android_assets".format(name), - valdi_deps = deps, - deps = java_deps, - output_target = source_set_select( - debug = "debug", - release = "release", - ), - ) - - valdi_android_aar( - name = "{}_android".format(name), - java_deps = java_deps, - native_deps = [ - "@valdi//valdi", - ] + get_suffixed_deps(deps, "_native"), - additional_assets = [":{}_android_assets".format(name)], - excluded_class_path_patterns = android_excluded_class_path_patterns, - so_name = "lib{}.so".format(name), - tags = ["valdi_android_exported_library"], - ) - package_name = web_package_name if npm_scope: package_name = npm_scope + "/" + package_name diff --git a/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift b/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift index e1bb88e9..2fca0ccf 100644 --- a/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift +++ b/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift @@ -53,8 +53,24 @@ class PrependWebJSProcessor: CompilationProcessor { relativePath = String(relativePath.dropLast(3)) } + logger.debug("PrependWebJSProcessor: ========== Processing Web JS File ==========") + logger.debug("PrependWebJSProcessor: Output file path: \(finalFileOutput)") + logger.debug("PrependWebJSProcessor: relativeProjectPath (original): '\(item.relativeProjectPath)'") + logger.debug("PrependWebJSProcessor: relativeProjectPath (adjusted): '\(relativePath)'") + logger.debug("PrependWebJSProcessor: relativeProjectPath length: \(relativePath.count)") + logger.debug("PrependWebJSProcessor: sourceURL: \(item.sourceURL.absoluteString)") + logger.debug("PrependWebJSProcessor: bundleInfo.name: \(item.bundleInfo.name)") + logger.debug("PrependWebJSProcessor: outputURL.lastPathComponent: \(finalFile.outputURL.lastPathComponent)") + var newFile = finalFile.file var contents: String? = try? newFile.readString() + let contentsLength = contents?.count ?? 0 + logger.debug("PrependWebJSProcessor: File contents length: \(contentsLength)") + + // Count how many require( calls are in the file + let requireCount = contents?.components(separatedBy: "require(").count ?? 1 + logger.debug("PrependWebJSProcessor: Found \(requireCount - 1) 'require(' occurrences") + // Transform require( to customRequire( - this must happen for all web JS files // Note: TypeScript with module: "commonjs" already transforms import() to Promise.resolve().then(() => require(...)), // so we only need to transform require( to customRequire( and the import() transformation is handled automatically. @@ -66,6 +82,8 @@ class PrependWebJSProcessor: CompilationProcessor { // Note: We use the adjusted relativePath (without .tsx/.ts extension) so module resolution works correctly let moduleSetup = "module.path = \"\(relativePath)\";\n" let prefix = "\(moduleSetup)var customRequire = globalThis.moduleLoader.resolveRequire(\"\(relativePath)\");\n" + logger.debug("PrependWebJSProcessor: Generated prefix (first 100 chars): \(String(prefix.prefix(100)))") + logger.debug("PrependWebJSProcessor: ==========================================") if let data = (prefix + (contents ?? "" )).data(using: .utf8) { newFile = .data(data) } diff --git a/compiler/compiler/scripts/update_compiler.sh b/compiler/compiler/scripts/update_compiler.sh index 5b7ae7b1..3a7128eb 100755 --- a/compiler/compiler/scripts/update_compiler.sh +++ b/compiler/compiler/scripts/update_compiler.sh @@ -60,6 +60,14 @@ if [ -z "$bin_output_path" ]; then usage fi +# Convert to absolute path before changing directories +# This ensures the path remains correct after we cd to $BASE_PATH +ORIGINAL_PWD=$(pwd) +if [[ "$bin_output_path" != /* ]]; then + # Relative path - make it absolute from the original working directory + bin_output_path="$ORIGINAL_PWD/$bin_output_path" +fi + # Main cd "$BASE_PATH" VARIANT="release" @@ -142,4 +150,11 @@ source src/composer/jenkins/jenkins_helpers.sh mkdir -p "$OUT_DIR" rm -f "$OUT_DIR/valdi_compiler" cp "$OUTPUT_FILE_PATH" "$OUT_DIR/valdi_compiler" -) + +# Verify the copy succeeded +if [ ! -f "$OUT_DIR/valdi_compiler" ]; then + echo "Error: Failed to copy compiler binary to $OUT_DIR/valdi_compiler" >&2 + exit 1 +fi + +echo "All done." diff --git a/npm_modules/cli/src/commands/projectsync.ts b/npm_modules/cli/src/commands/projectsync.ts index 0319ab7e..34d4ef56 100644 --- a/npm_modules/cli/src/commands/projectsync.ts +++ b/npm_modules/cli/src/commands/projectsync.ts @@ -192,14 +192,44 @@ async function collectTsConfigDirs( }; const tsConfigDirs = new Map(); + let consolidatedModulesPath: string | undefined; + // First pass: detect if consolidated setup exists by checking for modules/tsconfig.json + // Look for any target under a modules/ directory and check if modules/tsconfig.json exists + for (const projectSyncOutput of projectSyncOutputs) { + if (projectSyncOutput.target.repo) { + continue; + } + + const targetPath = bazelLabelToAbsolutePath(workspaceInfo, projectSyncOutput.target); + const modulesMatch = targetPath.match(/(.+[/\\]modules)[/\\]/); + if (modulesMatch) { + const potentialModulesPath = modulesMatch[1]!; + const consolidatedTsConfigPath = path.join(potentialModulesPath, 'tsconfig.json'); + if (fsSync.existsSync(consolidatedTsConfigPath)) { + consolidatedModulesPath = potentialModulesPath; + break; + } + } + } + + // Second pass: group modules into consolidated dir if detected for (const projectSyncOutput of projectSyncOutputs) { if (projectSyncOutput.target.repo) { // Ignore external repo deps continue; } - const tsConfigDirPath = bazelLabelToAbsolutePath(workspaceInfo, projectSyncOutput.target); + const targetPath = bazelLabelToAbsolutePath(workspaceInfo, projectSyncOutput.target); + + // If using consolidated setup and target is under modules/, use consolidated dir + let tsConfigDirPath: string; + if (consolidatedModulesPath && targetPath.startsWith(consolidatedModulesPath + path.sep)) { + tsConfigDirPath = consolidatedModulesPath; + } else { + tsConfigDirPath = targetPath; + } + let tsConfigDir = tsConfigDirs.get(tsConfigDirPath); if (!tsConfigDir) { tsConfigDir = { dir: tsConfigDirPath, matchedTargets: [] }; @@ -239,9 +269,19 @@ function computeTsCompilerOptions( compilerOptions = {}; } + // Preserve jsx and lib if they exist, or set defaults + if (!compilerOptions.jsx) { + compilerOptions.jsx = 'preserve'; + } + if (!compilerOptions.lib) { + compilerOptions.lib = ['dom', 'ES2019']; + } + compilerOptions.paths = {}; const rootDirs: string[] = []; let valdiCoreTarget: TargetDescription | undefined; + const seenDependencies = new Set(); + for (const matchedTarget of matchedTargets) { const targetRootDirs = matchedTarget.target.paths.map(p => relativePathTo(tsConfigDir, path.dirname(p))); @@ -254,13 +294,36 @@ function computeTsCompilerOptions( const selfName = matchedTarget.target.label.name ?? ''; const selfInclude = `${selfName}/*`; - const selfImportPaths = matchedTarget.target.paths.map(p => `${relativePathTo(tsConfigDir, p)}/*`); + // For consolidated setup, paths should be relative to modules/ directory + // For individual module setup, paths are relative to module directory + const selfImportPaths: string[] = []; + for (const targetPath of matchedTarget.target.paths) { + const relativePath = relativePathTo(tsConfigDir, targetPath); + selfImportPaths.push(`${relativePath}/*`); + + // Add projectsync-generated paths if they exist + const targetDir = path.dirname(targetPath); + const projectsyncGeneratedDir = path.join(targetDir, '.valdi_build/projectsync/generated_ts', selfName); + if (fsSync.existsSync(projectsyncGeneratedDir)) { + const relativeProjectsyncPath = relativePathTo(tsConfigDir, projectsyncGeneratedDir); + if (!selfImportPaths.includes(`${relativeProjectsyncPath}/*`)) { + selfImportPaths.push(`${relativeProjectsyncPath}/*`); + } + } + } compilerOptions.paths[selfInclude] = selfImportPaths; for (const dependency of matchedTarget.dependencies) { - if (!dependency.label.name || compilerOptions.paths[dependency.label.name]) { - // Already present + if (!dependency.label.name) { + continue; + } + + const dependencyKey = `${dependency.label.name}/*`; + + // For consolidated setup, merge paths from all modules + // For individual setup, skip if already present + if (seenDependencies.has(dependencyKey) && !compilerOptions.paths[dependencyKey]) { continue; } @@ -268,10 +331,38 @@ function computeTsCompilerOptions( valdiCoreTarget = dependency; } - const importPaths = dependency.paths.map(p => `${relativePathTo(tsConfigDir, p)}/*`); + const importPaths: string[] = []; + for (const depPath of dependency.paths) { + const relativePath = relativePathTo(tsConfigDir, depPath); + importPaths.push(`${relativePath}/*`); + + // Add projectsync-generated paths for external dependencies if they exist + const depDir = path.dirname(depPath); + const depName = dependency.label.name; + if (depName) { + const projectsyncGeneratedDir = path.join(depDir, '.valdi_build/projectsync/generated_ts', depName); + if (fsSync.existsSync(projectsyncGeneratedDir)) { + const relativeProjectsyncPath = relativePathTo(tsConfigDir, projectsyncGeneratedDir); + if (!importPaths.includes(`${relativeProjectsyncPath}/*`)) { + importPaths.push(`${relativeProjectsyncPath}/*`); + } + } + } + } - const key = `${dependency.label.name}/*`; - compilerOptions.paths[key] = importPaths; + if (compilerOptions.paths[dependencyKey]) { + // Merge with existing paths + const existing = compilerOptions.paths[dependencyKey]; + if (Array.isArray(existing)) { + compilerOptions.paths[dependencyKey] = [...new Set([...existing, ...importPaths])]; + } else { + compilerOptions.paths[dependencyKey] = importPaths; + } + } else { + compilerOptions.paths[dependencyKey] = importPaths; + } + + seenDependencies.add(dependencyKey); } } @@ -284,6 +375,10 @@ function computeTsCompilerOptions( compilerOptions.types = baseTsFiles.map(p => relativePathTo(tsConfigDir, removeTsFileExtension(p))); + // Ensure rootDirs includes current directory for consolidated setup + if (!rootDirs.includes('.')) { + rootDirs.unshift('.'); + } compilerOptions.rootDirs = rootDirs; return compilerOptions; diff --git a/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json b/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json index 7867fa77..13d41048 100644 --- a/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json @@ -7,4 +7,8 @@ "composite": true, "allowJs": true }, + "exclude": [ + "debug/**", + "release/**" + ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/drawing/web/tsconfig.json b/src/valdi_modules/src/valdi/drawing/web/tsconfig.json index 8dbd2898..13d41048 100644 --- a/src/valdi_modules/src/valdi/drawing/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/drawing/web/tsconfig.json @@ -5,6 +5,10 @@ "strict": true, "skipLibCheck": true, "composite": true, - "allowJs": true, + "allowJs": true }, + "exclude": [ + "debug/**", + "release/**" + ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/file_system/web/tsconfig.json b/src/valdi_modules/src/valdi/file_system/web/tsconfig.json index 7867fa77..13d41048 100644 --- a/src/valdi_modules/src/valdi/file_system/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/file_system/web/tsconfig.json @@ -7,4 +7,8 @@ "composite": true, "allowJs": true }, + "exclude": [ + "debug/**", + "release/**" + ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/persistence/web/tsconfig.json b/src/valdi_modules/src/valdi/persistence/web/tsconfig.json index 7867fa77..13d41048 100644 --- a/src/valdi_modules/src/valdi/persistence/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/persistence/web/tsconfig.json @@ -7,4 +7,8 @@ "composite": true, "allowJs": true }, + "exclude": [ + "debug/**", + "release/**" + ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts b/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts index 13c20637..133ff48d 100644 --- a/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts +++ b/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts @@ -66,7 +66,9 @@ function resolveAbsoluteImport(normalizedPathEntries: string[]): ResolvedPath { } function resolveAbsoluteImportFromPath(path: string): ResolvedPath { - return resolveAbsoluteImport(normalizePath(path.split('/'))); + const normalized = normalizePath(path.split('/')); + const result = resolveAbsoluteImport(normalized); + return result; } function resolvePath(path: string, fromResolvedPath: ResolvedPath): ResolvedPath { @@ -77,7 +79,8 @@ function resolvePath(path: string, fromResolvedPath: ResolvedPath): ResolvedPath const combinedPath = fromResolvedPath.directoryPaths.slice(); combinedPath.push(...importPathEntries); const normalized = normalizePath(combinedPath); - return resolveAbsoluteImport(normalized); + const result = resolveAbsoluteImport(normalized); + return result; } else { // Absolute import const normalized = normalizePath(importPathEntries); @@ -373,7 +376,8 @@ export class ModuleLoader implements IModuleLoader { module = this.modules[resolvedPath.absolutePath]!; } - return this.makeRequire(module); + const requireFunc = this.makeRequire(module); + return requireFunc; } getOrCreateSourceMap = (path: string, sourceMapFactory: SourceMapFactory): ISourceMap | undefined => { diff --git a/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json index 7867fa77..bc4d8782 100644 --- a/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json @@ -6,5 +6,5 @@ "skipLibCheck": true, "composite": true, "allowJs": true - }, + } } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json index 2257b14f..057e4020 100644 --- a/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json @@ -7,4 +7,8 @@ "composite": true, "allowJs": true }, + "exclude": [ + "debug/**", + "release/**" + ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json index 62419ccc..ae73b62a 100644 --- a/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json @@ -28,6 +28,8 @@ "**/*.js", "**/node_modules/**", "node_modules", - "src_symlink" + "src_symlink", + "debug/**", + "release/**" ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json index 7867fa77..13d41048 100644 --- a/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json @@ -7,4 +7,8 @@ "composite": true, "allowJs": true }, + "exclude": [ + "debug/**", + "release/**" + ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts b/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts index b00273a5..8234ab75 100644 --- a/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts +++ b/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts @@ -3,6 +3,7 @@ import { FrameObserver, IRendererDelegate, VisibilityObserver } from 'valdi_core import { Style } from 'valdi_core/src/Style'; import { NativeNode } from 'valdi_tsx/src/NativeNode'; import { NativeView } from 'valdi_tsx/src/NativeView'; +import { CancelToken } from 'valdi_core/src/CancellableAnimation'; import { changeAttributeOnElement, createElement, @@ -12,6 +13,7 @@ import { registerElements, setAllElementsAttributeDelegate, } from './HTMLRenderer'; +import { WebAnimationManager } from './WebAnimationManager'; export interface UpdateAttributeDelegate { updateAttribute(elementId: number, attributeName: string, attributeValue: any): void; @@ -19,9 +21,13 @@ export interface UpdateAttributeDelegate { export class ValdiWebRendererDelegate implements IRendererDelegate { private attributeDelegate?: UpdateAttributeDelegate; + private animationManager: WebAnimationManager; constructor(private htmlRoot: HTMLElement | ShadowRoot) { registerElements(); + this.animationManager = new WebAnimationManager(); + // Make animation manager globally accessible for elements + (window as any).__valdiAnimationManager = this.animationManager; } setAttributeDelegate(delegate: UpdateAttributeDelegate) { this.attributeDelegate = delegate; @@ -61,8 +67,67 @@ export class ValdiWebRendererDelegate implements IRendererDelegate { } onElementAttributeChangeStyle(id: number, attributeName: string, style: Style): void { const attributes = style.attributes ?? {}; + + // Process left/right together to avoid conflicts + // Note: On web, we swap left/right values for compatibility + const hasLeft = 'left' in attributes; + const hasRight = 'right' in attributes; + const leftValue = attributes.left; + const rightValue = attributes.right; + + if (hasLeft && hasRight) { + // Both are present - process removals first, then set the new value + // Swap: original left becomes web right, original right becomes web left + const leftIsDefined = leftValue !== undefined && leftValue !== null && leftValue !== ''; + const rightIsDefined = rightValue !== undefined && rightValue !== null && rightValue !== ''; + + if (rightIsDefined && !leftIsDefined) { + // Original right becomes web left - remove web right first, then set web left + changeAttributeOnElement(id, 'right', undefined); + changeAttributeOnElement(id, 'left', rightValue); + } else if (leftIsDefined && !rightIsDefined) { + // Original left becomes web right - remove web left first, then set web right + changeAttributeOnElement(id, 'left', undefined); + changeAttributeOnElement(id, 'right', leftValue); + } else if (leftIsDefined && rightIsDefined) { + // Both have values - this shouldn't happen, but prioritize original left (web right) + changeAttributeOnElement(id, 'left', undefined); + changeAttributeOnElement(id, 'right', leftValue); + } else { + // Both are undefined/null - remove both + changeAttributeOnElement(id, 'left', undefined); + changeAttributeOnElement(id, 'right', undefined); + } + } else if (hasLeft) { + // Only left is present - original left becomes web right + const leftIsDefined = leftValue !== undefined && leftValue !== null && leftValue !== ''; + if (leftIsDefined) { + // When setting original left (web right), also clear web left + changeAttributeOnElement(id, 'left', undefined); + changeAttributeOnElement(id, 'right', leftValue); + } else { + changeAttributeOnElement(id, 'right', undefined); + } + } else if (hasRight) { + // Only right is present - original right becomes web left + const rightIsDefined = rightValue !== undefined && rightValue !== null && rightValue !== ''; + if (rightIsDefined) { + // When setting original right (web left), also clear web right + changeAttributeOnElement(id, 'right', undefined); + changeAttributeOnElement(id, 'left', rightValue); + } else { + changeAttributeOnElement(id, 'left', undefined); + } + } + + // Process all other attributes Object.keys(attributes).forEach(key => { - changeAttributeOnElement(id, key, attributes[key]); + if (key === 'left' || key === 'right') { + // Already handled above + return; + } + const value = attributes[key]; + changeAttributeOnElement(id, key, value); }); } onElementAttributeChangeFunction(id: number, attributeName: string, fn: () => void): void { @@ -77,12 +142,37 @@ export class ValdiWebRendererDelegate implements IRendererDelegate { // TODO(mgharmalkar) // console.log('onRenderEnd'); } - onAnimationStart(options: AnimationOptions, token: number): void { - // TODO: no animation support on web yet, so just call completion with cancelled = false. - options.completion?.(false); + private tokenMap: Map = new Map(); + + onAnimationStart(options: AnimationOptions, token: CancelToken): void { + const animationToken = this.animationManager.startAnimation(options); + this.tokenMap.set(token, animationToken); + } + + onAnimationEnd(): void { + // End the most recently started animation + // In practice, animations are nested, so we end the last one + if (this.tokenMap.size > 0) { + const tokens = Array.from(this.tokenMap.values()); + const lastToken = tokens[tokens.length - 1]; + this.animationManager.endAnimation(lastToken); + // Remove from map + for (const [rendererToken, animationToken] of this.tokenMap.entries()) { + if (animationToken === lastToken) { + this.tokenMap.delete(rendererToken); + break; + } + } + } + } + + onAnimationCancel(token: CancelToken): void { + const animationToken = this.tokenMap.get(token); + if (animationToken !== undefined) { + this.animationManager.cancelAnimation(animationToken); + this.tokenMap.delete(token); + } } - onAnimationEnd(): void {} - onAnimationCancel(token: number): void {} registerVisibilityObserver(observer: VisibilityObserver): void { // TODO(mgharmalkar) // console.log('registerVisibilityObserver'); diff --git a/src/valdi_modules/src/valdi/web_renderer/src/WebAnimationManager.ts b/src/valdi_modules/src/valdi/web_renderer/src/WebAnimationManager.ts new file mode 100644 index 00000000..dcb0022f --- /dev/null +++ b/src/valdi_modules/src/valdi/web_renderer/src/WebAnimationManager.ts @@ -0,0 +1,225 @@ +import { AnimationOptions, AnimationCurve, SpringAnimationOptions, PresetCurveAnimationOptions, CustomCurveAnimationOptions } from 'valdi_core/src/AnimationOptions'; +import { CancelToken } from 'valdi_core/src/CancellableAnimation'; + +/** + * Manages animations for web elements by tracking active animations + * and applying CSS transitions/animations to style changes. + */ +export class WebAnimationManager { + private activeAnimations: Map = new Map(); + private animatedElements: Map> = new Map(); // elementId -> set of animation tokens + private currentAnimationToken: CancelToken | null = null; // Currently active animation during a block + private nextToken: CancelToken = 1; + + /** + * Start an animation context + */ + startAnimation(options: AnimationOptions): CancelToken { + const token = this.nextToken++; + const context: AnimationContext = { + options, + token, + animatedProperties: new Set(), + }; + this.activeAnimations.set(token, context); + this.currentAnimationToken = token; // Set as current active animation + return token; + } + + /** + * End an animation context + */ + endAnimation(token: CancelToken): void { + const context = this.activeAnimations.get(token); + if (context) { + // Clean up element tracking + this.animatedElements.forEach((tokens, elementId) => { + tokens.delete(token); + if (tokens.size === 0) { + this.animatedElements.delete(elementId); + } + }); + this.activeAnimations.delete(token); + + if (this.currentAnimationToken === token) { + this.currentAnimationToken = null; + } + + // Call completion callback + if (context.options.completion) { + context.options.completion(false); + } + } + } + + /** + * Cancel an animation + */ + cancelAnimation(token: CancelToken): void { + const context = this.activeAnimations.get(token); + if (context) { + // Clean up element tracking + this.animatedElements.forEach((tokens, elementId) => { + tokens.delete(token); + if (tokens.size === 0) { + this.animatedElements.delete(elementId); + } + }); + this.activeAnimations.delete(token); + + // Call completion callback with cancelled = true + if (context.options.completion) { + context.options.completion(true); + } + } + } + + /** + * Check if an element is currently being animated + */ + isElementAnimated(elementId: number): boolean { + return this.animatedElements.has(elementId); + } + + /** + * Get animation context for an element (if any) + * During an animation block, returns the current animation context + */ + getAnimationContext(elementId: number): AnimationContext | undefined { + // If we're in an active animation block, return that context + if (this.currentAnimationToken !== null) { + const context = this.activeAnimations.get(this.currentAnimationToken); + if (context) { + return context; + } + } + + // Otherwise check if element was previously marked as animated + const tokens = this.animatedElements.get(elementId); + if (tokens && tokens.size > 0) { + // Return the first active animation context + const token = Array.from(tokens)[0]; + return this.activeAnimations.get(token); + } + return undefined; + } + + /** + * Mark an element as being animated + */ + markElementAnimated(elementId: number, token: CancelToken): void { + if (!this.animatedElements.has(elementId)) { + this.animatedElements.set(elementId, new Set()); + } + this.animatedElements.get(elementId)!.add(token); + } + + /** + * Convert AnimationOptions to CSS transition string + */ + getCSSTransition(property: string, options: AnimationOptions): string { + if ('stiffness' in options) { + // Spring animation - use a JavaScript-based approach or approximate with CSS + // For now, approximate spring with a CSS cubic-bezier + const duration = this.estimateSpringDuration(options as SpringAnimationOptions); + const timingFunction = this.springToCubicBezier(options as SpringAnimationOptions); + return `${property} ${duration}s ${timingFunction}`; + } else { + // Regular animation + const duration = options.duration; + const timingFunction = this.getCSSTimingFunction(options); + return `${property} ${duration}s ${timingFunction}`; + } + } + + /** + * Get CSS transition for all properties + */ + getAllPropertiesTransition(options: AnimationOptions): string { + if ('stiffness' in options) { + const duration = this.estimateSpringDuration(options as SpringAnimationOptions); + const timingFunction = this.springToCubicBezier(options as SpringAnimationOptions); + return `all ${duration}s ${timingFunction}`; + } else { + const duration = options.duration; + const timingFunction = this.getCSSTimingFunction(options); + return `all ${duration}s ${timingFunction}`; + } + } + + /** + * Convert AnimationCurve to CSS timing function + */ + private getCSSTimingFunction(options: PresetCurveAnimationOptions | CustomCurveAnimationOptions): string { + if ('controlPoints' in options && options.controlPoints && options.controlPoints.length === 4) { + // Custom cubic-bezier + const [x1, y1, x2, y2] = options.controlPoints; + return `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`; + } else if ('curve' in options) { + // Preset curve + switch (options.curve ?? AnimationCurve.EaseInOut) { + case AnimationCurve.Linear: + return 'linear'; + case AnimationCurve.EaseIn: + return 'ease-in'; + case AnimationCurve.EaseOut: + return 'ease-out'; + case AnimationCurve.EaseInOut: + return 'ease-in-out'; + } + } + // Default to ease-in-out + return 'ease-in-out'; + } + + /** + * Convert spring animation to CSS cubic-bezier approximation + * This is an approximation - for true spring physics, we'd need JavaScript animation + */ + private springToCubicBezier(options: SpringAnimationOptions): string { + // Approximate spring with a bouncy cubic-bezier + // Higher stiffness = faster, higher damping = less bouncy + const stiffness = options.stiffness ?? 381.47; + const damping = options.damping ?? 20.1; + + // Normalize to reasonable ranges + const normalizedStiffness = Math.min(stiffness / 500, 1); + const normalizedDamping = Math.min(damping / 30, 1); + + // Create a bouncy curve based on spring parameters + // More damping = less bounce (closer to ease-out) + // More stiffness = faster initial acceleration + const bounce = 1 - normalizedDamping; + const x1 = 0.25; + const y1 = 0.1 + bounce * 0.3; + const x2 = 0.25 + normalizedStiffness * 0.2; + const y2 = 1; + + return `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`; + } + + /** + * Estimate duration for spring animation + * Spring animations don't have a fixed duration, but we need one for CSS + */ + private estimateSpringDuration(options: SpringAnimationOptions): number { + const stiffness = options.stiffness ?? 381.47; + const damping = options.damping ?? 20.1; + + // Estimate duration based on spring parameters + // Higher stiffness = shorter duration + // Higher damping = shorter duration (less oscillation) + const baseDuration = 0.5; // Base duration in seconds + const stiffnessFactor = Math.max(0.3, 1 - (stiffness / 1000)); + const dampingFactor = Math.max(0.5, 1 - (damping / 50)); + + return baseDuration * stiffnessFactor * dampingFactor; + } +} + +interface AnimationContext { + options: AnimationOptions; + token: CancelToken; + animatedProperties: Set; +} + diff --git a/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts b/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts index 99c35bee..bb05f914 100644 --- a/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts +++ b/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts @@ -107,7 +107,6 @@ export function generateStyles(attribute: string, value: any): Partial 0, + }); + } + + // Get animation manager if available + const animationManager = (window as any).__valdiAnimationManager as WebAnimationManager | undefined; + const animationContext = animationManager?.getAnimationContext(this.id); + + // Apply all styles + Object.keys(generatedStyles).forEach(key => { + const value = generatedStyles[key as keyof CSSStyleDeclaration]; + if (attributeName === 'boxShadow' || attributeName === 'borderRadius') { + console.log('[WebValdiLayout] Applying style:', { key, value, elementId: this.id }); + } + if (value === '' || value === null || value === undefined) { + this.htmlElement.style.removeProperty(key); + } else { + (this.htmlElement.style as any)[key] = value; + if (attributeName === 'boxShadow' || attributeName === 'borderRadius') { + const computedStyle = window.getComputedStyle(this.htmlElement); + console.log('[WebValdiLayout] Style applied, element:', { + elementId: this.id, + styleKey: key, + styleValue: this.htmlElement.style[key as any], + computedValue: computedStyle[key as any], + width: computedStyle.width, + height: computedStyle.height, + element: this.htmlElement, + }); + } + } + }); + + // If justifyContent is set (either now or already on element), ensure flexbox is properly configured + // Check position after styles are applied (it might be set in this same update) + const hasJustifyContent = 'justifyContent' in generatedStyles || + (this.htmlElement.style.justifyContent !== '' && this.htmlElement.style.justifyContent !== 'normal'); + const isAbsolute = this.htmlElement.style.position === 'absolute' || generatedStyles.position === 'absolute'; + + if (hasJustifyContent && isAbsolute) { + // Ensure display: flex is set for flexbox to work + this.htmlElement.style.display = 'flex'; + // Ensure flexDirection is column for vertical centering (justifyContent centers along main axis) + // Only set if not already explicitly set + if (!generatedStyles.flexDirection) { + this.htmlElement.style.flexDirection = 'column'; + } + // With position: absolute, top: 0, bottom: 0, the element should stretch to parent height + // This is needed for justify-content to work properly + const hasTop = this.htmlElement.style.top !== '' && this.htmlElement.style.top !== 'auto'; + const hasBottom = this.htmlElement.style.bottom !== '' && this.htmlElement.style.bottom !== 'auto'; + if (hasTop && hasBottom) { + // Element should automatically have height from top/bottom + // Ensure no conflicting height is set + if (this.htmlElement.style.height && this.htmlElement.style.height !== 'auto' && this.htmlElement.style.height !== '100%') { + // If height is explicitly set to a fixed value, remove it to let top/bottom control the height + this.htmlElement.style.height = ''; + } + } + } + + // For child View elements inside a flex container with justifyContent, ensure they don't interfere + // Only apply to View elements (not Layout) that are relatively positioned with explicit dimensions + // This targets elements like thumbInner that should be simple block elements, not flex containers + if (this.type === 'view' && this.parent && + (!this.htmlElement.style.position || this.htmlElement.style.position === 'relative')) { + const hasExplicitWidth = this.htmlElement.style.width !== '' && this.htmlElement.style.width !== 'auto'; + const hasExplicitHeight = this.htmlElement.style.height !== '' && this.htmlElement.style.height !== 'auto'; + const parentComputedStyle = window.getComputedStyle(this.parent.htmlElement); + const parentHasJustifyContent = parentComputedStyle.justifyContent !== 'normal' && + parentComputedStyle.justifyContent !== ''; + + // Only override if: View element, explicit dimensions, parent has justifyContent, and not already block + if (hasExplicitWidth && hasExplicitHeight && parentHasJustifyContent && + this.htmlElement.style.display === 'flex') { + // Simple View element with explicit dimensions inside a flex container with justifyContent + // Use block display to prevent flex properties from interfering with centering + this.htmlElement.style.display = 'block'; + } + } + + // Apply animation transitions if element is being animated + if (animationContext && animationManager) { + const transition = animationManager.getAllPropertiesTransition(animationContext.options); + this.htmlElement.style.transition = transition; + animationManager.markElementAnimated(this.id, animationContext.token); + } + return; } @@ -301,6 +397,9 @@ export class WebValdiLayout { } return; case 'slowClipping': + // slowClipping enables proper clipping with border-radius by using overflow: hidden + this.htmlElement.style.overflow = attributeValue ? 'hidden' : 'visible'; + return; case 'touchEnabled': case 'hitTest': this.htmlElement.style.pointerEvents = attributeValue ? 'auto' : 'none'; @@ -583,10 +682,20 @@ export class WebValdiLayout { console.log('WebValdiLayout not implemented: ', attributeName, attributeValue); return; case 'width': - this.htmlElement.style.width = attributeValue; + // Ensure width values are converted to pixels if they're numbers + if (typeof attributeValue === 'number') { + this.htmlElement.style.width = `${attributeValue}px`; + } else { + this.htmlElement.style.width = attributeValue; + } return; case 'height': - this.htmlElement.style.height = attributeValue; + // Ensure height values are converted to pixels if they're numbers + if (typeof attributeValue === 'number') { + this.htmlElement.style.height = `${attributeValue}px`; + } else { + this.htmlElement.style.height = attributeValue; + } return; } From de54ae3fa398d396a6ec678727000664385abc68 Mon Sep 17 00:00:00 2001 From: Carson Holgate Date: Fri, 6 Feb 2026 10:42:26 -0800 Subject: [PATCH 2/5] Revert build changes --- bin/BUILD.bazel | 6 ++---- bzl/prebuilt_tools.bzl | 10 ---------- bzl/valdi/valdi_exported_library.bzl | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/bin/BUILD.bazel b/bin/BUILD.bazel index 3f66d7cf..48d4df70 100644 --- a/bin/BUILD.bazel +++ b/bin/BUILD.bazel @@ -6,8 +6,6 @@ load( "pngquant_linux", "pngquant_macos", "valdi_compiler_companion_files", - "valdi_compiler_linux", - "valdi_compiler_macos", ) filegroup( @@ -20,8 +18,8 @@ native_binary( name = "valdi_compiler", src = select( { - "@bazel_tools//src/conditions:darwin": valdi_compiler_macos(), - "@bazel_tools//src/conditions:linux_x86_64": valdi_compiler_linux(), + "@bazel_tools//src/conditions:darwin": "@valdi_compiler_macos//:valdi_compiler", + "@bazel_tools//src/conditions:linux_x86_64": "@valdi_compiler_linux//:valdi_compiler", }, ), out = "valdi_compiler", diff --git a/bzl/prebuilt_tools.bzl b/bzl/prebuilt_tools.bzl index d6533cec..c04b5be0 100644 --- a/bzl/prebuilt_tools.bzl +++ b/bzl/prebuilt_tools.bzl @@ -12,16 +12,6 @@ def pngquant_macos(): return "@valdi_pngquant_macos//:pngquant" return "pngquant/macos/pngquant" -def valdi_compiler_macos(): - if INTERNAL_BUILD: - pass - return "compiler/macos/valdi_compiler" - -def valdi_compiler_linux(): - if INTERNAL_BUILD: - pass - return "compiler/linux/valdi_compiler" - def valdi_compiler_companion_files(): if INTERNAL_BUILD: return ["@valdi_compiler_companion//:all_files"] diff --git a/bzl/valdi/valdi_exported_library.bzl b/bzl/valdi/valdi_exported_library.bzl index ad419bf7..f232b55c 100644 --- a/bzl/valdi/valdi_exported_library.bzl +++ b/bzl/valdi/valdi_exported_library.bzl @@ -1,5 +1,6 @@ load("@build_bazel_rules_apple//apple:apple.bzl", "apple_xcframework") load("//bzl:expand_template.bzl", "expand_template") +load("//bzl/android:collect_android_assets.bzl", "collect_android_assets") load("//bzl/valdi:rewrite_hdrs.bzl", "rewrite_hdrs") load("//bzl/valdi:suffixed_deps.bzl", "get_suffixed_deps") load("//bzl/valdi:valdi_collapse_web_paths.bzl", "collapse_native_paths", "collapse_web_paths", "generate_register_native_modules") @@ -76,6 +77,28 @@ def valdi_exported_library( java_deps = java_deps + get_suffixed_deps(deps, "_kt") + collect_android_assets( + name = "{}_android_assets".format(name), + valdi_deps = deps, + deps = java_deps, + output_target = source_set_select( + debug = "debug", + release = "release", + ), + ) + + valdi_android_aar( + name = "{}_android".format(name), + java_deps = java_deps, + native_deps = [ + "@valdi//valdi", + ] + get_suffixed_deps(deps, "_native"), + additional_assets = [":{}_android_assets".format(name)], + excluded_class_path_patterns = android_excluded_class_path_patterns, + so_name = "lib{}.so".format(name), + tags = ["valdi_android_exported_library"], + ) + package_name = web_package_name if npm_scope: package_name = npm_scope + "/" + package_name From ae6cbcdc753a647b915796a39e9c3731bfa62e57 Mon Sep 17 00:00:00 2001 From: Carson Holgate Date: Fri, 20 Feb 2026 10:27:01 -0800 Subject: [PATCH 3/5] Fixing some things --- .../Processors/PrependWebJsProcessor.swift | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift b/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift index 2fca0ccf..9f552b1f 100644 --- a/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift +++ b/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift @@ -52,25 +52,10 @@ class PrependWebJSProcessor: CompilationProcessor { } else if relativePath.hasSuffix(".ts") { relativePath = String(relativePath.dropLast(3)) } - - logger.debug("PrependWebJSProcessor: ========== Processing Web JS File ==========") - logger.debug("PrependWebJSProcessor: Output file path: \(finalFileOutput)") - logger.debug("PrependWebJSProcessor: relativeProjectPath (original): '\(item.relativeProjectPath)'") - logger.debug("PrependWebJSProcessor: relativeProjectPath (adjusted): '\(relativePath)'") - logger.debug("PrependWebJSProcessor: relativeProjectPath length: \(relativePath.count)") - logger.debug("PrependWebJSProcessor: sourceURL: \(item.sourceURL.absoluteString)") - logger.debug("PrependWebJSProcessor: bundleInfo.name: \(item.bundleInfo.name)") - logger.debug("PrependWebJSProcessor: outputURL.lastPathComponent: \(finalFile.outputURL.lastPathComponent)") - + var newFile = finalFile.file var contents: String? = try? newFile.readString() - let contentsLength = contents?.count ?? 0 - logger.debug("PrependWebJSProcessor: File contents length: \(contentsLength)") - - // Count how many require( calls are in the file - let requireCount = contents?.components(separatedBy: "require(").count ?? 1 - logger.debug("PrependWebJSProcessor: Found \(requireCount - 1) 'require(' occurrences") - + // Transform require( to customRequire( - this must happen for all web JS files // Note: TypeScript with module: "commonjs" already transforms import() to Promise.resolve().then(() => require(...)), // so we only need to transform require( to customRequire( and the import() transformation is handled automatically. @@ -82,8 +67,6 @@ class PrependWebJSProcessor: CompilationProcessor { // Note: We use the adjusted relativePath (without .tsx/.ts extension) so module resolution works correctly let moduleSetup = "module.path = \"\(relativePath)\";\n" let prefix = "\(moduleSetup)var customRequire = globalThis.moduleLoader.resolveRequire(\"\(relativePath)\");\n" - logger.debug("PrependWebJSProcessor: Generated prefix (first 100 chars): \(String(prefix.prefix(100)))") - logger.debug("PrependWebJSProcessor: ==========================================") if let data = (prefix + (contents ?? "" )).data(using: .utf8) { newFile = .data(data) } From b42e69681dbecab71beb49cb3ae96660edd6ef86 Mon Sep 17 00:00:00 2001 From: Carson Holgate Date: Fri, 20 Feb 2026 10:29:49 -0800 Subject: [PATCH 4/5] Fix it all --- .../Processors/PrependWebJsProcessor.swift | 3 +- compiler/compiler/scripts/update_compiler.sh | 17 +-- npm_modules/cli/src/commands/projectsync.ts | 109 ++--------------- .../src/valdi/coreutils/web/tsconfig.json | 4 - .../src/valdi/drawing/web/tsconfig.json | 6 +- .../src/valdi/file_system/web/tsconfig.json | 4 - .../src/valdi/persistence/web/tsconfig.json | 4 - .../src/valdi/valdi_core/src/ModuleLoader.ts | 10 +- .../src/valdi/valdi_core/web/tsconfig.json | 2 +- .../src/valdi/valdi_http/web/tsconfig.json | 4 - .../valdi/valdi_protobuf/web/tsconfig.json | 4 +- .../src/valdi/valdi_tsx/web/tsconfig.json | 4 - .../src/ValdiWebRendererDelegate.ts | 102 +--------------- .../web_renderer/src/styles/ValdiWebStyles.ts | 2 +- .../web_renderer/src/views/WebValdiLayout.ts | 115 +----------------- 15 files changed, 25 insertions(+), 365 deletions(-) diff --git a/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift b/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift index 9f552b1f..e1bb88e9 100644 --- a/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift +++ b/compiler/compiler/Compiler/Sources/Processors/PrependWebJsProcessor.swift @@ -52,10 +52,9 @@ class PrependWebJSProcessor: CompilationProcessor { } else if relativePath.hasSuffix(".ts") { relativePath = String(relativePath.dropLast(3)) } - + var newFile = finalFile.file var contents: String? = try? newFile.readString() - // Transform require( to customRequire( - this must happen for all web JS files // Note: TypeScript with module: "commonjs" already transforms import() to Promise.resolve().then(() => require(...)), // so we only need to transform require( to customRequire( and the import() transformation is handled automatically. diff --git a/compiler/compiler/scripts/update_compiler.sh b/compiler/compiler/scripts/update_compiler.sh index 3a7128eb..5b7ae7b1 100755 --- a/compiler/compiler/scripts/update_compiler.sh +++ b/compiler/compiler/scripts/update_compiler.sh @@ -60,14 +60,6 @@ if [ -z "$bin_output_path" ]; then usage fi -# Convert to absolute path before changing directories -# This ensures the path remains correct after we cd to $BASE_PATH -ORIGINAL_PWD=$(pwd) -if [[ "$bin_output_path" != /* ]]; then - # Relative path - make it absolute from the original working directory - bin_output_path="$ORIGINAL_PWD/$bin_output_path" -fi - # Main cd "$BASE_PATH" VARIANT="release" @@ -150,11 +142,4 @@ source src/composer/jenkins/jenkins_helpers.sh mkdir -p "$OUT_DIR" rm -f "$OUT_DIR/valdi_compiler" cp "$OUTPUT_FILE_PATH" "$OUT_DIR/valdi_compiler" - -# Verify the copy succeeded -if [ ! -f "$OUT_DIR/valdi_compiler" ]; then - echo "Error: Failed to copy compiler binary to $OUT_DIR/valdi_compiler" >&2 - exit 1 -fi - -echo "All done." +) diff --git a/npm_modules/cli/src/commands/projectsync.ts b/npm_modules/cli/src/commands/projectsync.ts index 34d4ef56..0319ab7e 100644 --- a/npm_modules/cli/src/commands/projectsync.ts +++ b/npm_modules/cli/src/commands/projectsync.ts @@ -192,44 +192,14 @@ async function collectTsConfigDirs( }; const tsConfigDirs = new Map(); - let consolidatedModulesPath: string | undefined; - // First pass: detect if consolidated setup exists by checking for modules/tsconfig.json - // Look for any target under a modules/ directory and check if modules/tsconfig.json exists - for (const projectSyncOutput of projectSyncOutputs) { - if (projectSyncOutput.target.repo) { - continue; - } - - const targetPath = bazelLabelToAbsolutePath(workspaceInfo, projectSyncOutput.target); - const modulesMatch = targetPath.match(/(.+[/\\]modules)[/\\]/); - if (modulesMatch) { - const potentialModulesPath = modulesMatch[1]!; - const consolidatedTsConfigPath = path.join(potentialModulesPath, 'tsconfig.json'); - if (fsSync.existsSync(consolidatedTsConfigPath)) { - consolidatedModulesPath = potentialModulesPath; - break; - } - } - } - - // Second pass: group modules into consolidated dir if detected for (const projectSyncOutput of projectSyncOutputs) { if (projectSyncOutput.target.repo) { // Ignore external repo deps continue; } - const targetPath = bazelLabelToAbsolutePath(workspaceInfo, projectSyncOutput.target); - - // If using consolidated setup and target is under modules/, use consolidated dir - let tsConfigDirPath: string; - if (consolidatedModulesPath && targetPath.startsWith(consolidatedModulesPath + path.sep)) { - tsConfigDirPath = consolidatedModulesPath; - } else { - tsConfigDirPath = targetPath; - } - + const tsConfigDirPath = bazelLabelToAbsolutePath(workspaceInfo, projectSyncOutput.target); let tsConfigDir = tsConfigDirs.get(tsConfigDirPath); if (!tsConfigDir) { tsConfigDir = { dir: tsConfigDirPath, matchedTargets: [] }; @@ -269,19 +239,9 @@ function computeTsCompilerOptions( compilerOptions = {}; } - // Preserve jsx and lib if they exist, or set defaults - if (!compilerOptions.jsx) { - compilerOptions.jsx = 'preserve'; - } - if (!compilerOptions.lib) { - compilerOptions.lib = ['dom', 'ES2019']; - } - compilerOptions.paths = {}; const rootDirs: string[] = []; let valdiCoreTarget: TargetDescription | undefined; - const seenDependencies = new Set(); - for (const matchedTarget of matchedTargets) { const targetRootDirs = matchedTarget.target.paths.map(p => relativePathTo(tsConfigDir, path.dirname(p))); @@ -294,36 +254,13 @@ function computeTsCompilerOptions( const selfName = matchedTarget.target.label.name ?? ''; const selfInclude = `${selfName}/*`; - // For consolidated setup, paths should be relative to modules/ directory - // For individual module setup, paths are relative to module directory - const selfImportPaths: string[] = []; - for (const targetPath of matchedTarget.target.paths) { - const relativePath = relativePathTo(tsConfigDir, targetPath); - selfImportPaths.push(`${relativePath}/*`); - - // Add projectsync-generated paths if they exist - const targetDir = path.dirname(targetPath); - const projectsyncGeneratedDir = path.join(targetDir, '.valdi_build/projectsync/generated_ts', selfName); - if (fsSync.existsSync(projectsyncGeneratedDir)) { - const relativeProjectsyncPath = relativePathTo(tsConfigDir, projectsyncGeneratedDir); - if (!selfImportPaths.includes(`${relativeProjectsyncPath}/*`)) { - selfImportPaths.push(`${relativeProjectsyncPath}/*`); - } - } - } + const selfImportPaths = matchedTarget.target.paths.map(p => `${relativePathTo(tsConfigDir, p)}/*`); compilerOptions.paths[selfInclude] = selfImportPaths; for (const dependency of matchedTarget.dependencies) { - if (!dependency.label.name) { - continue; - } - - const dependencyKey = `${dependency.label.name}/*`; - - // For consolidated setup, merge paths from all modules - // For individual setup, skip if already present - if (seenDependencies.has(dependencyKey) && !compilerOptions.paths[dependencyKey]) { + if (!dependency.label.name || compilerOptions.paths[dependency.label.name]) { + // Already present continue; } @@ -331,38 +268,10 @@ function computeTsCompilerOptions( valdiCoreTarget = dependency; } - const importPaths: string[] = []; - for (const depPath of dependency.paths) { - const relativePath = relativePathTo(tsConfigDir, depPath); - importPaths.push(`${relativePath}/*`); - - // Add projectsync-generated paths for external dependencies if they exist - const depDir = path.dirname(depPath); - const depName = dependency.label.name; - if (depName) { - const projectsyncGeneratedDir = path.join(depDir, '.valdi_build/projectsync/generated_ts', depName); - if (fsSync.existsSync(projectsyncGeneratedDir)) { - const relativeProjectsyncPath = relativePathTo(tsConfigDir, projectsyncGeneratedDir); - if (!importPaths.includes(`${relativeProjectsyncPath}/*`)) { - importPaths.push(`${relativeProjectsyncPath}/*`); - } - } - } - } + const importPaths = dependency.paths.map(p => `${relativePathTo(tsConfigDir, p)}/*`); - if (compilerOptions.paths[dependencyKey]) { - // Merge with existing paths - const existing = compilerOptions.paths[dependencyKey]; - if (Array.isArray(existing)) { - compilerOptions.paths[dependencyKey] = [...new Set([...existing, ...importPaths])]; - } else { - compilerOptions.paths[dependencyKey] = importPaths; - } - } else { - compilerOptions.paths[dependencyKey] = importPaths; - } - - seenDependencies.add(dependencyKey); + const key = `${dependency.label.name}/*`; + compilerOptions.paths[key] = importPaths; } } @@ -375,10 +284,6 @@ function computeTsCompilerOptions( compilerOptions.types = baseTsFiles.map(p => relativePathTo(tsConfigDir, removeTsFileExtension(p))); - // Ensure rootDirs includes current directory for consolidated setup - if (!rootDirs.includes('.')) { - rootDirs.unshift('.'); - } compilerOptions.rootDirs = rootDirs; return compilerOptions; diff --git a/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json b/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json index 13d41048..7867fa77 100644 --- a/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/coreutils/web/tsconfig.json @@ -7,8 +7,4 @@ "composite": true, "allowJs": true }, - "exclude": [ - "debug/**", - "release/**" - ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/drawing/web/tsconfig.json b/src/valdi_modules/src/valdi/drawing/web/tsconfig.json index 13d41048..8dbd2898 100644 --- a/src/valdi_modules/src/valdi/drawing/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/drawing/web/tsconfig.json @@ -5,10 +5,6 @@ "strict": true, "skipLibCheck": true, "composite": true, - "allowJs": true + "allowJs": true, }, - "exclude": [ - "debug/**", - "release/**" - ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/file_system/web/tsconfig.json b/src/valdi_modules/src/valdi/file_system/web/tsconfig.json index 13d41048..7867fa77 100644 --- a/src/valdi_modules/src/valdi/file_system/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/file_system/web/tsconfig.json @@ -7,8 +7,4 @@ "composite": true, "allowJs": true }, - "exclude": [ - "debug/**", - "release/**" - ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/persistence/web/tsconfig.json b/src/valdi_modules/src/valdi/persistence/web/tsconfig.json index 13d41048..7867fa77 100644 --- a/src/valdi_modules/src/valdi/persistence/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/persistence/web/tsconfig.json @@ -7,8 +7,4 @@ "composite": true, "allowJs": true }, - "exclude": [ - "debug/**", - "release/**" - ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts b/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts index 133ff48d..13c20637 100644 --- a/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts +++ b/src/valdi_modules/src/valdi/valdi_core/src/ModuleLoader.ts @@ -66,9 +66,7 @@ function resolveAbsoluteImport(normalizedPathEntries: string[]): ResolvedPath { } function resolveAbsoluteImportFromPath(path: string): ResolvedPath { - const normalized = normalizePath(path.split('/')); - const result = resolveAbsoluteImport(normalized); - return result; + return resolveAbsoluteImport(normalizePath(path.split('/'))); } function resolvePath(path: string, fromResolvedPath: ResolvedPath): ResolvedPath { @@ -79,8 +77,7 @@ function resolvePath(path: string, fromResolvedPath: ResolvedPath): ResolvedPath const combinedPath = fromResolvedPath.directoryPaths.slice(); combinedPath.push(...importPathEntries); const normalized = normalizePath(combinedPath); - const result = resolveAbsoluteImport(normalized); - return result; + return resolveAbsoluteImport(normalized); } else { // Absolute import const normalized = normalizePath(importPathEntries); @@ -376,8 +373,7 @@ export class ModuleLoader implements IModuleLoader { module = this.modules[resolvedPath.absolutePath]!; } - const requireFunc = this.makeRequire(module); - return requireFunc; + return this.makeRequire(module); } getOrCreateSourceMap = (path: string, sourceMapFactory: SourceMapFactory): ISourceMap | undefined => { diff --git a/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json index bc4d8782..7867fa77 100644 --- a/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_core/web/tsconfig.json @@ -6,5 +6,5 @@ "skipLibCheck": true, "composite": true, "allowJs": true - } + }, } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json index 057e4020..2257b14f 100644 --- a/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_http/web/tsconfig.json @@ -7,8 +7,4 @@ "composite": true, "allowJs": true }, - "exclude": [ - "debug/**", - "release/**" - ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json index ae73b62a..62419ccc 100644 --- a/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_protobuf/web/tsconfig.json @@ -28,8 +28,6 @@ "**/*.js", "**/node_modules/**", "node_modules", - "src_symlink", - "debug/**", - "release/**" + "src_symlink" ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json b/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json index 13d41048..7867fa77 100644 --- a/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json +++ b/src/valdi_modules/src/valdi/valdi_tsx/web/tsconfig.json @@ -7,8 +7,4 @@ "composite": true, "allowJs": true }, - "exclude": [ - "debug/**", - "release/**" - ] } \ No newline at end of file diff --git a/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts b/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts index 8234ab75..b00273a5 100644 --- a/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts +++ b/src/valdi_modules/src/valdi/web_renderer/src/ValdiWebRendererDelegate.ts @@ -3,7 +3,6 @@ import { FrameObserver, IRendererDelegate, VisibilityObserver } from 'valdi_core import { Style } from 'valdi_core/src/Style'; import { NativeNode } from 'valdi_tsx/src/NativeNode'; import { NativeView } from 'valdi_tsx/src/NativeView'; -import { CancelToken } from 'valdi_core/src/CancellableAnimation'; import { changeAttributeOnElement, createElement, @@ -13,7 +12,6 @@ import { registerElements, setAllElementsAttributeDelegate, } from './HTMLRenderer'; -import { WebAnimationManager } from './WebAnimationManager'; export interface UpdateAttributeDelegate { updateAttribute(elementId: number, attributeName: string, attributeValue: any): void; @@ -21,13 +19,9 @@ export interface UpdateAttributeDelegate { export class ValdiWebRendererDelegate implements IRendererDelegate { private attributeDelegate?: UpdateAttributeDelegate; - private animationManager: WebAnimationManager; constructor(private htmlRoot: HTMLElement | ShadowRoot) { registerElements(); - this.animationManager = new WebAnimationManager(); - // Make animation manager globally accessible for elements - (window as any).__valdiAnimationManager = this.animationManager; } setAttributeDelegate(delegate: UpdateAttributeDelegate) { this.attributeDelegate = delegate; @@ -67,67 +61,8 @@ export class ValdiWebRendererDelegate implements IRendererDelegate { } onElementAttributeChangeStyle(id: number, attributeName: string, style: Style): void { const attributes = style.attributes ?? {}; - - // Process left/right together to avoid conflicts - // Note: On web, we swap left/right values for compatibility - const hasLeft = 'left' in attributes; - const hasRight = 'right' in attributes; - const leftValue = attributes.left; - const rightValue = attributes.right; - - if (hasLeft && hasRight) { - // Both are present - process removals first, then set the new value - // Swap: original left becomes web right, original right becomes web left - const leftIsDefined = leftValue !== undefined && leftValue !== null && leftValue !== ''; - const rightIsDefined = rightValue !== undefined && rightValue !== null && rightValue !== ''; - - if (rightIsDefined && !leftIsDefined) { - // Original right becomes web left - remove web right first, then set web left - changeAttributeOnElement(id, 'right', undefined); - changeAttributeOnElement(id, 'left', rightValue); - } else if (leftIsDefined && !rightIsDefined) { - // Original left becomes web right - remove web left first, then set web right - changeAttributeOnElement(id, 'left', undefined); - changeAttributeOnElement(id, 'right', leftValue); - } else if (leftIsDefined && rightIsDefined) { - // Both have values - this shouldn't happen, but prioritize original left (web right) - changeAttributeOnElement(id, 'left', undefined); - changeAttributeOnElement(id, 'right', leftValue); - } else { - // Both are undefined/null - remove both - changeAttributeOnElement(id, 'left', undefined); - changeAttributeOnElement(id, 'right', undefined); - } - } else if (hasLeft) { - // Only left is present - original left becomes web right - const leftIsDefined = leftValue !== undefined && leftValue !== null && leftValue !== ''; - if (leftIsDefined) { - // When setting original left (web right), also clear web left - changeAttributeOnElement(id, 'left', undefined); - changeAttributeOnElement(id, 'right', leftValue); - } else { - changeAttributeOnElement(id, 'right', undefined); - } - } else if (hasRight) { - // Only right is present - original right becomes web left - const rightIsDefined = rightValue !== undefined && rightValue !== null && rightValue !== ''; - if (rightIsDefined) { - // When setting original right (web left), also clear web right - changeAttributeOnElement(id, 'right', undefined); - changeAttributeOnElement(id, 'left', rightValue); - } else { - changeAttributeOnElement(id, 'left', undefined); - } - } - - // Process all other attributes Object.keys(attributes).forEach(key => { - if (key === 'left' || key === 'right') { - // Already handled above - return; - } - const value = attributes[key]; - changeAttributeOnElement(id, key, value); + changeAttributeOnElement(id, key, attributes[key]); }); } onElementAttributeChangeFunction(id: number, attributeName: string, fn: () => void): void { @@ -142,37 +77,12 @@ export class ValdiWebRendererDelegate implements IRendererDelegate { // TODO(mgharmalkar) // console.log('onRenderEnd'); } - private tokenMap: Map = new Map(); - - onAnimationStart(options: AnimationOptions, token: CancelToken): void { - const animationToken = this.animationManager.startAnimation(options); - this.tokenMap.set(token, animationToken); - } - - onAnimationEnd(): void { - // End the most recently started animation - // In practice, animations are nested, so we end the last one - if (this.tokenMap.size > 0) { - const tokens = Array.from(this.tokenMap.values()); - const lastToken = tokens[tokens.length - 1]; - this.animationManager.endAnimation(lastToken); - // Remove from map - for (const [rendererToken, animationToken] of this.tokenMap.entries()) { - if (animationToken === lastToken) { - this.tokenMap.delete(rendererToken); - break; - } - } - } - } - - onAnimationCancel(token: CancelToken): void { - const animationToken = this.tokenMap.get(token); - if (animationToken !== undefined) { - this.animationManager.cancelAnimation(animationToken); - this.tokenMap.delete(token); - } + onAnimationStart(options: AnimationOptions, token: number): void { + // TODO: no animation support on web yet, so just call completion with cancelled = false. + options.completion?.(false); } + onAnimationEnd(): void {} + onAnimationCancel(token: number): void {} registerVisibilityObserver(observer: VisibilityObserver): void { // TODO(mgharmalkar) // console.log('registerVisibilityObserver'); diff --git a/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts b/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts index bb05f914..99c35bee 100644 --- a/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts +++ b/src/valdi_modules/src/valdi/web_renderer/src/styles/ValdiWebStyles.ts @@ -107,6 +107,7 @@ export function generateStyles(attribute: string, value: any): Partial 0, - }); - } - - // Get animation manager if available - const animationManager = (window as any).__valdiAnimationManager as WebAnimationManager | undefined; - const animationContext = animationManager?.getAnimationContext(this.id); - - // Apply all styles - Object.keys(generatedStyles).forEach(key => { - const value = generatedStyles[key as keyof CSSStyleDeclaration]; - if (attributeName === 'boxShadow' || attributeName === 'borderRadius') { - console.log('[WebValdiLayout] Applying style:', { key, value, elementId: this.id }); - } - if (value === '' || value === null || value === undefined) { - this.htmlElement.style.removeProperty(key); - } else { - (this.htmlElement.style as any)[key] = value; - if (attributeName === 'boxShadow' || attributeName === 'borderRadius') { - const computedStyle = window.getComputedStyle(this.htmlElement); - console.log('[WebValdiLayout] Style applied, element:', { - elementId: this.id, - styleKey: key, - styleValue: this.htmlElement.style[key as any], - computedValue: computedStyle[key as any], - width: computedStyle.width, - height: computedStyle.height, - element: this.htmlElement, - }); - } - } - }); - - // If justifyContent is set (either now or already on element), ensure flexbox is properly configured - // Check position after styles are applied (it might be set in this same update) - const hasJustifyContent = 'justifyContent' in generatedStyles || - (this.htmlElement.style.justifyContent !== '' && this.htmlElement.style.justifyContent !== 'normal'); - const isAbsolute = this.htmlElement.style.position === 'absolute' || generatedStyles.position === 'absolute'; - - if (hasJustifyContent && isAbsolute) { - // Ensure display: flex is set for flexbox to work - this.htmlElement.style.display = 'flex'; - // Ensure flexDirection is column for vertical centering (justifyContent centers along main axis) - // Only set if not already explicitly set - if (!generatedStyles.flexDirection) { - this.htmlElement.style.flexDirection = 'column'; - } - // With position: absolute, top: 0, bottom: 0, the element should stretch to parent height - // This is needed for justify-content to work properly - const hasTop = this.htmlElement.style.top !== '' && this.htmlElement.style.top !== 'auto'; - const hasBottom = this.htmlElement.style.bottom !== '' && this.htmlElement.style.bottom !== 'auto'; - if (hasTop && hasBottom) { - // Element should automatically have height from top/bottom - // Ensure no conflicting height is set - if (this.htmlElement.style.height && this.htmlElement.style.height !== 'auto' && this.htmlElement.style.height !== '100%') { - // If height is explicitly set to a fixed value, remove it to let top/bottom control the height - this.htmlElement.style.height = ''; - } - } - } - - // For child View elements inside a flex container with justifyContent, ensure they don't interfere - // Only apply to View elements (not Layout) that are relatively positioned with explicit dimensions - // This targets elements like thumbInner that should be simple block elements, not flex containers - if (this.type === 'view' && this.parent && - (!this.htmlElement.style.position || this.htmlElement.style.position === 'relative')) { - const hasExplicitWidth = this.htmlElement.style.width !== '' && this.htmlElement.style.width !== 'auto'; - const hasExplicitHeight = this.htmlElement.style.height !== '' && this.htmlElement.style.height !== 'auto'; - const parentComputedStyle = window.getComputedStyle(this.parent.htmlElement); - const parentHasJustifyContent = parentComputedStyle.justifyContent !== 'normal' && - parentComputedStyle.justifyContent !== ''; - - // Only override if: View element, explicit dimensions, parent has justifyContent, and not already block - if (hasExplicitWidth && hasExplicitHeight && parentHasJustifyContent && - this.htmlElement.style.display === 'flex') { - // Simple View element with explicit dimensions inside a flex container with justifyContent - // Use block display to prevent flex properties from interfering with centering - this.htmlElement.style.display = 'block'; - } - } - - // Apply animation transitions if element is being animated - if (animationContext && animationManager) { - const transition = animationManager.getAllPropertiesTransition(animationContext.options); - this.htmlElement.style.transition = transition; - animationManager.markElementAnimated(this.id, animationContext.token); - } - + Object.assign(this.htmlElement.style, generatedStyles); return; } @@ -397,9 +301,6 @@ export class WebValdiLayout { } return; case 'slowClipping': - // slowClipping enables proper clipping with border-radius by using overflow: hidden - this.htmlElement.style.overflow = attributeValue ? 'hidden' : 'visible'; - return; case 'touchEnabled': case 'hitTest': this.htmlElement.style.pointerEvents = attributeValue ? 'auto' : 'none'; @@ -682,20 +583,10 @@ export class WebValdiLayout { console.log('WebValdiLayout not implemented: ', attributeName, attributeValue); return; case 'width': - // Ensure width values are converted to pixels if they're numbers - if (typeof attributeValue === 'number') { - this.htmlElement.style.width = `${attributeValue}px`; - } else { - this.htmlElement.style.width = attributeValue; - } + this.htmlElement.style.width = attributeValue; return; case 'height': - // Ensure height values are converted to pixels if they're numbers - if (typeof attributeValue === 'number') { - this.htmlElement.style.height = `${attributeValue}px`; - } else { - this.htmlElement.style.height = attributeValue; - } + this.htmlElement.style.height = attributeValue; return; } From 2ce1a3d84d2074c38b1c296548ddd759ad5667b4 Mon Sep 17 00:00:00 2001 From: Carson Holgate Date: Fri, 20 Feb 2026 10:32:31 -0800 Subject: [PATCH 5/5] starlark --- bzl/macros/android.bzl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/bzl/macros/android.bzl b/bzl/macros/android.bzl index 31c80fe9..131e0c9f 100644 --- a/bzl/macros/android.bzl +++ b/bzl/macros/android.bzl @@ -1,8 +1,17 @@ load( "@rules_android//rules:rules.bzl", _aar_import = "aar_import", + _android_binary = "android_binary", + _android_library = "android_library", ) +# Used by workspace_init.bzl for maven_install(use_starlark_android_rules = ...) +STARLARK_RULES_ANDROID_ENABLED = True + +# Re-export for valdi_android_application.bzl and other loaders from @android_macros +android_binary = _android_binary +android_library = _android_library + def aar_import(**kwargs): patched_kwargs = dict(kwargs) existing = patched_kwargs.get("deps", [])