@tileflow/react-native 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
@@ -1,4 +1,4 @@
1
- import { M as MapView, a as MapViewChangeEvent, b as MapInitialViewInputs, c as MapCameraProps } from '../contract-DWs-lDPo.js';
1
+ import { M as MapView, a as MapViewChangeEvent, b as MapInitialViewInputs, c as MapCameraProps } from '../contract-Bwl-gB-I.js';
2
2
  import '@maplibre/maplibre-react-native';
3
3
  import 'react';
4
4
  import 'react-native';
@@ -1,4 +1,4 @@
1
- import { b as MapInitialViewInputs, M as MapView } from '../contract-DWs-lDPo.js';
1
+ import { b as MapInitialViewInputs, M as MapView } from '../contract-Bwl-gB-I.js';
2
2
  import '@maplibre/maplibre-react-native';
3
3
  import 'react';
4
4
  import 'react-native';
@@ -1,5 +1,5 @@
1
1
  import { HostedNativeSessionFetch } from './session.js';
2
- import { N as NativeAdmissionEvent, a as NativeAdmissionResource, b as NativeContextAck, c as NativeAdmissionResult, d as NativeRemovalAck } from '../native-admission-contract-k7LKE1vW.js';
2
+ import { N as NativeAdmissionEvent, a as NativeAdmissionResource, b as NativeContextAck, c as NativeAdmissionResult, d as NativeRemovalAck } from '../native-admission-contract-DFLfUP9X.js';
3
3
 
4
4
  declare function createReactNativeAdmissionTransport(): Readonly<{
5
5
  bridge: Readonly<{
@@ -11,7 +11,7 @@ declare function createReactNativeAdmissionTransport(): Readonly<{
11
11
  mapId: string | null;
12
12
  resources: readonly NativeAdmissionResource[];
13
13
  }>): Promise<NativeContextAck>;
14
- extendContext?(installation: string, context: string, resources: readonly NativeAdmissionResource[]): Promise<Readonly<{
14
+ extendContext(installation: string, context: string, resources: readonly NativeAdmissionResource[]): Promise<Readonly<{
15
15
  resources: number;
16
16
  }>>;
17
17
  retireContext(installation: string, context: string): Promise<Readonly<{
@@ -176,10 +176,7 @@ function createNativeAdmissionWire(native, listen) {
176
176
  return Object.freeze({ installation: ack.installation });
177
177
  },
178
178
  registerContext: (id, registration) => safe(() => native.registerContext(id, registration)),
179
- extendContext: (id, context, resources) => safe(() => {
180
- if (!native.extendContext) throw unavailable();
181
- return native.extendContext(id, context, resources);
182
- }),
179
+ extendContext: (id, context, resources) => safe(() => native.extendContext(id, context, resources)),
183
180
  retireContext(id, context) {
184
181
  if (id === installation?.id) retireContext(context);
185
182
  return safe(() => native.retireContext(id, context));
@@ -326,6 +323,7 @@ function createReactNativeAdmissionTransport() {
326
323
  for (const name of [
327
324
  "install",
328
325
  "registerContext",
326
+ "extendContext",
329
327
  "retireContext",
330
328
  "completeBatch",
331
329
  "remove",
@@ -1,4 +1,4 @@
1
- import { e as NativeAdmissionCode, a as NativeAdmissionResource, f as NativeAdmissionBridge, d as NativeRemovalAck } from '../native-admission-contract-k7LKE1vW.js';
1
+ import { e as NativeAdmissionCode, a as NativeAdmissionResource, f as NativeAdmissionBridge, d as NativeRemovalAck } from '../native-admission-contract-DFLfUP9X.js';
2
2
  import { HostedNativeResourcePolicy, createHostedNativeSessionController, HostedNativeSessionFetch, HostedNativeSessionController } from './session.js';
3
3
 
4
4
  type SessionInput = Parameters<typeof createHostedNativeSessionController>[0];
@@ -69,7 +69,7 @@ declare function createNativeAdmissionOwner(options: {
69
69
  }>>;
70
70
  dispose: () => Promise<NativeRemovalAck>;
71
71
  readonly state: Readonly<{
72
- status: "ready" | "error" | "disposed" | "idle" | "installing";
72
+ status: "disposed" | "error" | "idle" | "installing" | "ready";
73
73
  contexts: number;
74
74
  pendingRegistrations: number;
75
75
  }>;
@@ -1459,10 +1459,11 @@ function createNativeAdmissionOwner(options) {
1459
1459
  if (options.sessionFetch) boundFetch = options.sessionFetch(registered.context);
1460
1460
  let retirement;
1461
1461
  let retirementNotified = false;
1462
- const catalog = createNativeAdmissionCatalog(resources, registered.context, (additions) => {
1463
- if (!bridge.extendContext) return Promise.reject(cancel("NATIVE_ADMISSION_UNAVAILABLE"));
1464
- return bridge.extendContext(ack.installation, registered.context, additions);
1465
- });
1462
+ const catalog = createNativeAdmissionCatalog(
1463
+ resources,
1464
+ registered.context,
1465
+ (additions) => bridge.extendContext(ack.installation, registered.context, additions)
1466
+ );
1466
1467
  const context = {
1467
1468
  id: registered.context,
1468
1469
  generation: registered.generation,
@@ -1,4 +1,4 @@
1
- import { y as MapThemeSelection, f as MapColorScheme } from '../contract-DWs-lDPo.js';
1
+ import { z as MapThemeSelection, f as MapColorScheme } from '../contract-Bwl-gB-I.js';
2
2
  import '@maplibre/maplibre-react-native';
3
3
  import 'react';
4
4
  import 'react-native';
@@ -1,5 +1,5 @@
1
1
  import { TileflowNativeSourceState } from '@tileflow/core/native';
2
- import { v as MapSourceState } from '../contract-DWs-lDPo.js';
2
+ import { v as MapSourceState } from '../contract-Bwl-gB-I.js';
3
3
  import '@maplibre/maplibre-react-native';
4
4
  import 'react';
5
5
  import 'react-native';
@@ -74,7 +74,7 @@ type NativeAdmissionBridge = Readonly<{
74
74
  mapId: string | null;
75
75
  resources: readonly NativeAdmissionResource[];
76
76
  }>): Promise<NativeContextAck>;
77
- extendContext?(installation: string, context: string, resources: readonly NativeAdmissionResource[]): Promise<Readonly<{
77
+ extendContext(installation: string, context: string, resources: readonly NativeAdmissionResource[]): Promise<Readonly<{
78
78
  resources: number;
79
79
  }>>;
80
80
  retireContext(installation: string, context: string): Promise<Readonly<{
@@ -0,0 +1,28 @@
1
+ #import <QuartzCore/QuartzCore.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ NS_ASSUME_NONNULL_BEGIN
5
+
6
+ // Pure helpers for theme transitions and blends on MapLibre iOS. UI-queue use only where noted.
7
+
8
+ /** The projective transform taking four points to four points (h[8] = 1), or NO when degenerate. */
9
+ FOUNDATION_EXPORT BOOL TFThemeHomography(const CGPoint from[_Nonnull 4], const CGPoint to[_Nonnull 4], double h[_Nonnull 9]);
10
+ /** A homography as a layer transform for anchor point (0, 0). */
11
+ FOUNDATION_EXPORT CATransform3D TFThemeTransform(const double h[_Nonnull 9]);
12
+ /** The MapLibre iOS key of a style-specification layer property. */
13
+ FOUNDATION_EXPORT NSString *TFThemeStyleKey(NSString *property);
14
+ /** A hex, rgb(a), hsl(a), or transparent colour, or nil. */
15
+ FOUNDATION_EXPORT UIColor *_Nullable TFThemeColor(NSString *text);
16
+ /** Whether every element is a number (not a boolean). */
17
+ FOUNDATION_EXPORT BOOL TFThemeNumbers(NSArray *array);
18
+ /** Whether a value can be set at run time: constants and expressions that read no feature data. */
19
+ FOUNDATION_EXPORT BOOL TFThemeCameraValue(id _Nullable value, NSUInteger depth);
20
+ /**
21
+ * A style-specification value as the expression MapLibre iOS expects for `key`; nil resets it.
22
+ * `supported` is NO, with nil, for a value MapLibre iOS cannot take at run time.
23
+ */
24
+ FOUNDATION_EXPORT NSExpression *_Nullable TFThemeExpression(id _Nullable value, NSString *key, BOOL *supported);
25
+ /** Pattern artwork mixed in premultiplied pixels: `from` at 1 - t plus `to` at t. */
26
+ FOUNDATION_EXPORT UIImage *TFThemeMixImage(UIImage *from, UIImage *to, double t);
27
+
28
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,198 @@
1
+ #import "TFNativeTheme.h"
2
+ #import <MapLibre/MapLibre.h>
3
+ #import <algorithm>
4
+ #import <cctype>
5
+ #import <cmath>
6
+
7
+ // The projective transform taking four points to four points (h[8] = 1), or NO when degenerate.
8
+ BOOL TFThemeHomography(const CGPoint from[4], const CGPoint to[4], double h[9]) {
9
+ double rows[8][9];
10
+ for (int i = 0; i < 4; i++) {
11
+ double x = from[i].x, y = from[i].y, u = to[i].x, v = to[i].y;
12
+ double first[9] = {x, y, 1, 0, 0, 0, -u * x, -u * y, u};
13
+ double second[9] = {0, 0, 0, x, y, 1, -v * x, -v * y, v};
14
+ for (int k = 0; k < 9; k++) { rows[2 * i][k] = first[k]; rows[2 * i + 1][k] = second[k]; }
15
+ }
16
+ for (int column = 0; column < 8; column++) {
17
+ int pivot = column;
18
+ for (int row = column + 1; row < 8; row++) if (std::fabs(rows[row][column]) > std::fabs(rows[pivot][column])) pivot = row;
19
+ if (std::fabs(rows[pivot][column]) < 1e-12) return NO;
20
+ if (pivot != column) for (int k = 0; k < 9; k++) std::swap(rows[pivot][k], rows[column][k]);
21
+ for (int row = 0; row < 8; row++) {
22
+ if (row == column) continue;
23
+ double factor = rows[row][column] / rows[column][column];
24
+ for (int k = column; k < 9; k++) rows[row][k] -= factor * rows[column][k];
25
+ }
26
+ }
27
+ for (int i = 0; i < 8; i++) { h[i] = rows[i][8] / rows[i][i]; if (!std::isfinite(h[i])) return NO; }
28
+ h[8] = 1;
29
+ return YES;
30
+ }
31
+
32
+ // MapLibre iOS names layer properties after Cocoa conventions; everything else is camel case.
33
+ NSString *TFThemeStyleKey(NSString *property) {
34
+ static NSDictionary<NSString *, NSString *> *renamed;
35
+ static dispatch_once_t once;
36
+ dispatch_once(&once, ^{
37
+ renamed = @{
38
+ @"circle-pitch-scale": @"circleScaleAlignment", @"circle-translate": @"circleTranslation", @"circle-translate-anchor": @"circleTranslationAnchor",
39
+ @"fill-antialias": @"fillAntialiased", @"fill-translate": @"fillTranslation", @"fill-translate-anchor": @"fillTranslationAnchor",
40
+ @"fill-extrusion-translate": @"fillExtrusionTranslation", @"fill-extrusion-translate-anchor": @"fillExtrusionTranslationAnchor",
41
+ @"fill-extrusion-vertical-gradient": @"fillExtrusionHasVerticalGradient", @"icon-allow-overlap": @"iconAllowsOverlap",
42
+ @"icon-ignore-placement": @"iconIgnoresPlacement", @"icon-image": @"iconImageName", @"icon-keep-upright": @"keepsIconUpright",
43
+ @"icon-rotate": @"iconRotation", @"icon-size": @"iconScale", @"icon-translate": @"iconTranslation", @"icon-translate-anchor": @"iconTranslationAnchor",
44
+ @"line-dasharray": @"lineDashPattern", @"line-translate": @"lineTranslation", @"line-translate-anchor": @"lineTranslationAnchor",
45
+ @"raster-brightness-max": @"maximumRasterBrightness", @"raster-brightness-min": @"minimumRasterBrightness",
46
+ @"raster-hue-rotate": @"rasterHueRotation", @"raster-resampling": @"rasterResamplingMode", @"symbol-avoid-edges": @"symbolAvoidsEdges",
47
+ @"text-allow-overlap": @"textAllowsOverlap", @"text-field": @"text", @"text-font": @"textFontNames", @"text-ignore-placement": @"textIgnoresPlacement",
48
+ @"text-justify": @"textJustification", @"text-keep-upright": @"keepsTextUpright", @"text-max-angle": @"maximumTextAngle",
49
+ @"text-max-width": @"maximumTextWidth", @"text-rotate": @"textRotation", @"text-size": @"textFontSize", @"text-translate": @"textTranslation",
50
+ @"text-translate-anchor": @"textTranslationAnchor", @"text-writing-mode": @"textWritingModes",
51
+ };
52
+ });
53
+ NSString *key = renamed[property];
54
+ if (key) return key;
55
+ NSMutableString *camel = [NSMutableString string];
56
+ BOOL upper = NO;
57
+ for (NSUInteger index = 0; index < property.length; index++) {
58
+ unichar character = [property characterAtIndex:index];
59
+ if (character == '-') { upper = YES; continue; }
60
+ [camel appendFormat:@"%C", upper ? (unichar)toupper(character) : character];
61
+ upper = NO;
62
+ }
63
+ return camel;
64
+ }
65
+ static BOOL TFThemeChannel(NSString *text, double scale, double *out) {
66
+ NSString *trimmed = [text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet];
67
+ BOOL percentage = [trimmed hasSuffix:@"%"];
68
+ if (percentage) trimmed = [trimmed substringToIndex:trimmed.length - 1];
69
+ NSScanner *scanner = [NSScanner scannerWithString:trimmed];
70
+ double value;
71
+ if (![scanner scanDouble:&value] || !scanner.isAtEnd || !std::isfinite(value)) return NO;
72
+ *out = percentage ? value / 100.0 * scale : value;
73
+ return YES;
74
+ }
75
+ // Hex, rgb(a), and hsl(a) colours; nil for anything else, which MapLibre then parses itself.
76
+ UIColor *TFThemeColor(NSString *text) {
77
+ NSString *colour = [[text stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceCharacterSet] lowercaseString];
78
+ if ([colour isEqual:@"transparent"]) return [UIColor colorWithRed:0 green:0 blue:0 alpha:0];
79
+ if ([colour hasPrefix:@"#"]) {
80
+ NSString *hex = [colour substringFromIndex:1];
81
+ if (hex.length == 3 || hex.length == 4) {
82
+ NSMutableString *full = [NSMutableString string];
83
+ for (NSUInteger index = 0; index < hex.length; index++) { unichar digit = [hex characterAtIndex:index]; [full appendFormat:@"%C%C", digit, digit]; }
84
+ hex = full;
85
+ }
86
+ if (hex.length != 6 && hex.length != 8) return nil;
87
+ unsigned long long number = 0;
88
+ NSScanner *scanner = [NSScanner scannerWithString:hex];
89
+ if (![scanner scanHexLongLong:&number] || !scanner.isAtEnd) return nil;
90
+ if (hex.length == 6) number = (number << 8) | 0xff;
91
+ return [UIColor colorWithRed:((number >> 24) & 0xff) / 255.0 green:((number >> 16) & 0xff) / 255.0 blue:((number >> 8) & 0xff) / 255.0 alpha:(number & 0xff) / 255.0];
92
+ }
93
+ NSRange open = [colour rangeOfString:@"("];
94
+ if (open.location == NSNotFound || ![colour hasSuffix:@")"]) return nil;
95
+ NSString *function = [colour substringToIndex:open.location];
96
+ NSString *body = [colour substringWithRange:NSMakeRange(open.location + 1, colour.length - open.location - 2)];
97
+ NSMutableArray<NSString *> *parts = [NSMutableArray array];
98
+ for (NSString *part in [body componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@", /"]]) if (part.length) [parts addObject:part];
99
+ if (parts.count < 3 || parts.count > 4) return nil;
100
+ double alpha = 1;
101
+ if (parts.count == 4 && !TFThemeChannel(parts[3], 1, &alpha)) return nil;
102
+ if ([function isEqual:@"rgb"] || [function isEqual:@"rgba"]) {
103
+ double red, green, blue;
104
+ if (!TFThemeChannel(parts[0], 255, &red) || !TFThemeChannel(parts[1], 255, &green) || !TFThemeChannel(parts[2], 255, &blue)) return nil;
105
+ return [UIColor colorWithRed:red / 255.0 green:green / 255.0 blue:blue / 255.0 alpha:alpha];
106
+ }
107
+ if ([function isEqual:@"hsl"] || [function isEqual:@"hsla"]) {
108
+ double hue, saturation, lightness;
109
+ if (!TFThemeChannel(parts[0], 1, &hue) || !TFThemeChannel(parts[1], 1, &saturation) || !TFThemeChannel(parts[2], 1, &lightness)) return nil;
110
+ double chroma = (1 - std::fabs(2 * lightness - 1)) * saturation;
111
+ double sector = std::fmod(std::fmod(hue, 360) + 360, 360) / 60;
112
+ double x = chroma * (1 - std::fabs(std::fmod(sector, 2) - 1));
113
+ double r = 0, g = 0, b = 0;
114
+ if (sector < 1) { r = chroma; g = x; } else if (sector < 2) { r = x; g = chroma; } else if (sector < 3) { g = chroma; b = x; }
115
+ else if (sector < 4) { g = x; b = chroma; } else if (sector < 5) { r = x; b = chroma; } else { r = chroma; b = x; }
116
+ double offset = lightness - chroma / 2;
117
+ return [UIColor colorWithRed:r + offset green:g + offset blue:b + offset alpha:alpha];
118
+ }
119
+ return nil;
120
+ }
121
+ BOOL TFThemeNumbers(NSArray *array) {
122
+ for (id item in array) if (![item isKindOfClass:NSNumber.class] || CFGetTypeID((__bridge CFTypeRef)item) == CFBooleanGetTypeID()) return NO;
123
+ return YES;
124
+ }
125
+ // MapLibre iOS converts expressions through NSExpression, which cannot round-trip every expression
126
+ // that reads feature data or combines conditions. Blend plans set only camera values at run time;
127
+ // anything else is left unchanged rather than risked.
128
+ BOOL TFThemeCameraValue(id value, NSUInteger depth) {
129
+ static NSSet<NSString *> *refused;
130
+ static dispatch_once_t once;
131
+ dispatch_once(&once, ^{
132
+ refused = [NSSet setWithArray:@[@"!", @"!has", @"!in", @"accumulated", @"all", @"any", @"distance", @"feature-state", @"geometry-type",
133
+ @"get", @"global-state", @"has", @"heatmap-density", @"id", @"in", @"line-progress", @"none", @"properties", @"within"]];
134
+ });
135
+ if (![value isKindOfClass:NSArray.class]) return YES;
136
+ if (depth > 32) return NO;
137
+ NSArray *array = value;
138
+ if (array.count && [array[0] isKindOfClass:NSString.class]) {
139
+ if ([refused containsObject:array[0]]) return NO;
140
+ if ([array[0] isEqual:@"literal"]) return YES;
141
+ }
142
+ for (id item in array) if (!TFThemeCameraValue(item, depth + 1)) return NO;
143
+ return YES;
144
+ }
145
+ // A style-specification value as the NSExpression MapLibre iOS expects for `key`; nil resets it.
146
+ NSExpression *TFThemeExpression(id value, NSString *key, BOOL *supported) {
147
+ *supported = YES;
148
+ if (!value || value == NSNull.null) return nil;
149
+ if ([value isKindOfClass:NSString.class]) {
150
+ if ([key hasSuffix:@"Color"]) {
151
+ // MapLibre iOS cannot cast a colour name at run time; such a value is left unchanged.
152
+ UIColor *colour = TFThemeColor(value);
153
+ *supported = colour != nil;
154
+ return colour ? [NSExpression expressionForConstantValue:colour] : nil;
155
+ }
156
+ return [NSExpression expressionForConstantValue:value];
157
+ }
158
+ if ([value isKindOfClass:NSNumber.class]) return [NSExpression expressionForConstantValue:value];
159
+ if ([value isKindOfClass:NSArray.class]) {
160
+ NSArray *array = value;
161
+ if (array.count == 2 && TFThemeNumbers(array) && ([key hasSuffix:@"Translation"] || [key hasSuffix:@"Offset"]))
162
+ return [NSExpression expressionForConstantValue:[NSValue valueWithCGVector:CGVectorMake([array[0] doubleValue], [array[1] doubleValue])]];
163
+ if (TFThemeNumbers(array)) return [NSExpression expressionForConstantValue:array];
164
+ BOOL strings = YES;
165
+ for (id item in array) strings = strings && [item isKindOfClass:NSString.class];
166
+ if (strings && [key isEqual:@"textFontNames"]) return [NSExpression expressionForConstantValue:array];
167
+ }
168
+ return [NSExpression expressionWithMLNJSONObject:value];
169
+ }
170
+ // Pattern artwork mixed in premultiplied pixels: `from` at 1 - t plus `to` at t.
171
+ UIImage *TFThemeMixImage(UIImage *from, UIImage *to, double t) {
172
+ if (t <= 0) return from;
173
+ if (t >= 1) return to;
174
+ CGImageRef a = from.CGImage, b = to.CGImage;
175
+ if (!a || !b || CGImageGetWidth(a) != CGImageGetWidth(b) || CGImageGetHeight(a) != CGImageGetHeight(b)) return t < 0.5 ? from : to;
176
+ size_t width = CGImageGetWidth(a), height = CGImageGetHeight(a);
177
+ CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
178
+ CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, space, (CGBitmapInfo)kCGImageAlphaPremultipliedLast);
179
+ CGColorSpaceRelease(space);
180
+ if (!context) return t < 0.5 ? from : to;
181
+ CGRect rect = CGRectMake(0, 0, width, height);
182
+ CGContextSetAlpha(context, 1 - t); CGContextDrawImage(context, rect, a);
183
+ CGContextSetBlendMode(context, kCGBlendModePlusLighter); CGContextSetAlpha(context, t); CGContextDrawImage(context, rect, b);
184
+ CGImageRef mixed = CGBitmapContextCreateImage(context);
185
+ CGContextRelease(context);
186
+ if (!mixed) return t < 0.5 ? from : to;
187
+ UIImage *image = [UIImage imageWithCGImage:mixed scale:from.scale orientation:UIImageOrientationUp];
188
+ CGImageRelease(mixed);
189
+ return from.renderingMode == UIImageRenderingModeAlwaysTemplate ? [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] : image;
190
+ }
191
+ // The homography as a layer transform for anchor point (0, 0): row vectors, perspective in m14/m24.
192
+ CATransform3D TFThemeTransform(const double h[9]) {
193
+ CATransform3D transform = CATransform3DIdentity;
194
+ transform.m11 = h[0]; transform.m12 = h[3]; transform.m14 = h[6];
195
+ transform.m21 = h[1]; transform.m22 = h[4]; transform.m24 = h[7];
196
+ transform.m41 = h[2]; transform.m42 = h[5]; transform.m44 = h[8];
197
+ return transform;
198
+ }