@symbiote-native/engine 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/build/accessibility-info/index.android.js +17 -29
  2. package/build/accessibility-info/index.ios.js +23 -37
  3. package/build/accessibility-info/shared.d.ts +3 -2
  4. package/build/accessibility-info/shared.js +25 -2
  5. package/build/action-sheet-ios/index.d.ts +6 -0
  6. package/build/action-sheet-ios/index.js +1 -1
  7. package/build/alert/index.android.js +1 -3
  8. package/build/animated/animations/base.js +5 -14
  9. package/build/animated/event.js +7 -9
  10. package/build/animated/graph.d.ts +6 -0
  11. package/build/animated/graph.js +14 -0
  12. package/build/animated/index.d.ts +1 -1
  13. package/build/animated/index.js +1 -1
  14. package/build/animated/interpolation-node.d.ts +0 -1
  15. package/build/animated/interpolation-node.js +5 -4
  16. package/build/animated/native/native-animated.js +1 -3
  17. package/build/animated/operators.d.ts +14 -34
  18. package/build/animated/operators.js +19 -95
  19. package/build/animated/value.d.ts +2 -4
  20. package/build/animated/value.js +17 -5
  21. package/build/app-state/index.js +17 -22
  22. package/build/appearance/index.js +17 -25
  23. package/build/back-handler/index.js +15 -18
  24. package/build/commit.d.ts +1 -3
  25. package/build/commit.js +26 -209
  26. package/build/dimensions/index.js +25 -20
  27. package/build/events/index.js +6 -151
  28. package/build/fabric-props.d.ts +3 -0
  29. package/build/fabric-props.js +186 -0
  30. package/build/i18n-manager/index.js +3 -5
  31. package/build/image-loader/index.d.ts +24 -0
  32. package/build/image-loader/index.js +175 -0
  33. package/build/image-loader.d.ts +24 -0
  34. package/build/image-loader.js +175 -0
  35. package/build/image-source-resolver/index.d.ts +9 -0
  36. package/build/image-source-resolver/index.js +16 -0
  37. package/build/image-source-resolver.d.ts +9 -0
  38. package/build/image-source-resolver.js +16 -0
  39. package/build/index.d.ts +10 -3
  40. package/build/index.js +12 -3
  41. package/build/keyboard/index.js +19 -34
  42. package/build/layout-animation/index.d.ts +1 -0
  43. package/build/layout-animation/index.js +53 -35
  44. package/build/native-modules/index.d.ts +18 -0
  45. package/build/native-modules/index.js +120 -0
  46. package/build/native-modules.d.ts +12 -0
  47. package/build/native-modules.js +64 -1
  48. package/build/node.d.ts +2 -0
  49. package/build/node.js +36 -1
  50. package/build/pan-responder/index.js +1 -3
  51. package/build/permissions-android/index.js +1 -3
  52. package/build/{platform-color.d.ts → platform-color/index.d.ts} +3 -0
  53. package/build/platform-color/index.js +49 -0
  54. package/build/process-background-image/index.js +7 -14
  55. package/build/process-box-shadow/index.js +1 -5
  56. package/build/process-filter.js +2 -8
  57. package/build/registry.js +3 -5
  58. package/build/settings/index.js +1 -3
  59. package/build/share/index.ios.js +1 -1
  60. package/build/status-bar/index.android.js +1 -1
  61. package/build/style/index.js +2 -4
  62. package/build/style-registry/index.d.ts +1 -3
  63. package/build/style-registry/index.js +15 -48
  64. package/build/style-registry/scope/index.d.ts +3 -0
  65. package/build/style-registry/scope/index.js +41 -0
  66. package/build/style-registry/scope.d.ts +3 -0
  67. package/build/style-registry/scope.js +41 -0
  68. package/build/touch-history/index.d.ts +23 -0
  69. package/build/touch-history/index.js +150 -0
  70. package/build/touch-history.d.ts +23 -0
  71. package/build/touch-history.js +150 -0
  72. package/build/type-guards/index.d.ts +4 -0
  73. package/build/type-guards/index.js +15 -0
  74. package/build/type-guards.d.ts +4 -0
  75. package/build/type-guards.js +15 -0
  76. package/package.json +1 -1
  77. package/build/platform-color.js +0 -25
  78. /package/build/animated/{animated-component-shared.d.ts → shared.d.ts} +0 -0
  79. /package/build/animated/{animated-component-shared.js → shared.js} +0 -0
@@ -12,12 +12,10 @@
12
12
  // changed ones on `changedTouches`), so the centroid/velocity math here reads
13
13
  // those directly while keeping RN's accumulate-deltas-over-time behavior.
14
14
  import { dlog } from '../debug.js';
15
+ import { isRecord } from '../type-guards.js';
15
16
  const SINGLE_TOUCH_COUNT = 1;
16
17
  // onShouldBlockNativeResponder defaults to true (RN: block native by default).
17
18
  const DEFAULT_BLOCK_NATIVE_RESPONDER = true;
18
- function isRecord(value) {
19
- return typeof value === 'object' && value !== null;
20
- }
21
19
  function toFiniteNumber(value) {
22
20
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
23
21
  }
@@ -19,6 +19,7 @@
19
19
  // requestMultiplePermissions(permissions): Promise<{[permission]: string}>
20
20
  import { getNativeModule } from '../native-modules.js';
21
21
  import { dlog } from '../debug.js';
22
+ import { isBoolean } from '../type-guards.js';
22
23
  // The native module name RN registers this TurboModule under
23
24
  // (`TurboModuleRegistry.get<Spec>('PermissionsAndroid')`). NOTE: a module name is
24
25
  // only provable on a real host (a headless fake answers to any name); this name
@@ -79,9 +80,6 @@ export const PERMISSIONS = Object.freeze({
79
80
  POST_NOTIFICATIONS: 'android.permission.POST_NOTIFICATIONS',
80
81
  NEARBY_WIFI_DEVICES: 'android.permission.NEARBY_WIFI_DEVICES',
81
82
  });
82
- function isBoolean(value) {
83
- return typeof value === 'boolean';
84
- }
85
83
  // Narrow the native string result into a known RESULTS value. Any unrecognized
86
84
  // string is passed through as a IPermissionStatus only when it matches; otherwise
87
85
  // we fall back to DENIED: a runtime guard at the trust boundary, not an `as`.
@@ -17,3 +17,6 @@ export type IColorValue = string | IOpaqueColorValue;
17
17
  export declare function PlatformColor(...names: string[]): IOpaqueColorValue;
18
18
  export declare function DynamicColorIOS(tuple: IDynamicColorIOSTuple): IOpaqueColorValue;
19
19
  export declare function isOpaqueColorValue(value: unknown): value is IOpaqueColorValue;
20
+ export declare function setColorProcessor(process: (value: IColorValue) => unknown): void;
21
+ export declare function processColor(color: IColorValue): unknown;
22
+ export declare function isProcessableColor(value: unknown): value is IColorValue;
@@ -0,0 +1,49 @@
1
+ // PlatformColor / DynamicColorIOS: opaque color values the native side resolves
2
+ // (iOS UIColor: semantic system colors and appearance-aware light/dark tuples).
3
+ // These are pure object constructors with no native dependency, so they live in
4
+ // shared and every adapter re-exports them. The opaque shape ({ semantic } /
5
+ // { dynamic }) is exactly what RN's processColor and the iOS RCTConvert UIColor
6
+ // path read, so our commit-time color seam routes these objects through the
7
+ // injected processor just like CSS-string colors (see commit.ts processValue).
8
+ export function PlatformColor(...names) {
9
+ return { semantic: names };
10
+ }
11
+ export function DynamicColorIOS(tuple) {
12
+ return {
13
+ dynamic: {
14
+ light: tuple.light,
15
+ dark: tuple.dark,
16
+ highContrastLight: tuple.highContrastLight,
17
+ highContrastDark: tuple.highContrastDark,
18
+ },
19
+ };
20
+ }
21
+ // True for the opaque objects above: the color seam uses this to route them
22
+ // through the platform processor alongside CSS-string colors.
23
+ export function isOpaqueColorValue(value) {
24
+ return typeof value === 'object' && value !== null && ('semantic' in value || 'dynamic' in value);
25
+ }
26
+ // Color props must reach Fabric as platform ints, not CSS strings. Fabric's C++ color
27
+ // parser silently drops strings. The actual conversion (processColor) is RN-platform-specific,
28
+ // so it is injected here rather than imported, keeping shared free of a react-native dependency
29
+ // (and the headless harness working). This module is the sole owner of "run a value through the
30
+ // platform color processor" - every color-touching consumer (commit's fabricProps,
31
+ // process-box-shadow, process-filter, process-background-image, StatusBar android) imports
32
+ // processColor/isProcessableColor from here, never from commit.ts.
33
+ let colorProcessor = value => value;
34
+ export function setColorProcessor(process) {
35
+ colorProcessor = process;
36
+ }
37
+ // Public mirror of RN's processColor: run a color through the injected platform processor (the
38
+ // canary wires RN's own). Off a real host it resolves CSS strings and opaque PlatformColor
39
+ // objects to the platform ints Fabric expects; headless (no processor wired) it is the identity,
40
+ // so smokes see the input unchanged.
41
+ export function processColor(color) {
42
+ return colorProcessor(color);
43
+ }
44
+ // A color-keyed value the platform processor must convert before Fabric: a CSS string, or an
45
+ // opaque PlatformColor / DynamicColorIOS object. Numbers (already platform ints) and undefined
46
+ // are left untouched.
47
+ export function isProcessableColor(value) {
48
+ return typeof value === 'string' || isOpaqueColorValue(value);
49
+ }
@@ -1,15 +1,11 @@
1
1
  // JS-side port of RN's processBackgroundImage (Libraries/StyleSheet/processBackgroundImage.js).
2
2
  // Same root cause as boxShadow/filter/transform: `experimental_backgroundImage` registers with
3
3
  // enableNativeCSSParsing(), which DEFAULTS TO FALSE, so RN's stock path parses the CSS gradient
4
- // string / structured array in JS and sends only the processed array to native — Fabric's C++
4
+ // string / structured array in JS and sends only the processed array to native - Fabric's C++
5
5
  // never sees the raw string. This restores that missing JS parse.
6
- //
7
- // processColor is referenced from ../commit at RUNTIME only (inside function bodies), never at
8
- // module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard — same pattern
9
- // as process-box-shadow/process-filter.
10
- import { processColor } from '../commit.js';
11
- import { isOpaqueColorValue } from '../platform-color.js';
6
+ import { isOpaqueColorValue, processColor } from '../platform-color/index.js';
12
7
  import { dlog } from '../debug.js';
8
+ import { isRecord } from '../type-guards.js';
13
9
  // RN processBackgroundImage.js: pre-compiled patterns.
14
10
  const NEWLINE_REGEX = /\n/g;
15
11
  const GRADIENT_REGEX = /^(linear|radial)-gradient\(((?:\([^)]*\)|[^()])*)\)/;
@@ -23,9 +19,6 @@ const LINEAR_GRADIENT_DEFAULT_DIRECTION = { type: 'angle', value: 180 };
23
19
  const DEFAULT_RADIAL_SHAPE = 'ellipse';
24
20
  const DEFAULT_RADIAL_SIZE = 'farthest-corner';
25
21
  const DEFAULT_RADIAL_POSITION = { top: '50%', left: '50%' };
26
- function isRecord(value) {
27
- return typeof value === 'object' && value !== null;
28
- }
29
22
  function isStringOrNumber(value) {
30
23
  return typeof value === 'string' || typeof value === 'number';
31
24
  }
@@ -221,7 +214,7 @@ function processBackgroundImageArray(rawList) {
221
214
  //#region CSS string form
222
215
  // Split a `background-image` value on its TOP-LEVEL commas only (multiple gradients, or a
223
216
  // color function's internal commas like `rgba(0, 0, 0, .3)`, must not be confused for one
224
- // another) — depth-tracking instead of a lookahead regex because a gradient's own arg list can
217
+ // another) - depth-tracking instead of a lookahead regex because a gradient's own arg list can
225
218
  // itself contain nested parens (`linear-gradient(to right, rgba(0,0,0,.5), blue)`).
226
219
  function splitGradients(input) {
227
220
  const result = [];
@@ -320,7 +313,7 @@ function parseLinearGradientCSSString(gradientContent) {
320
313
  }
321
314
  // The `at <position>` clause of `radial-gradient(... at <position>, <color-stops>)`. Drains
322
315
  // `tokens` (the SAME queue the caller is walking) via `shift()`, mirroring RN's in-place mutation
323
- // of `firstPartTokens` — not a copy, so the caller sees it emptied after this returns.
316
+ // of `firstPartTokens` - not a copy, so the caller sees it emptied after this returns.
324
317
  function parseRadialGradientPositionTokens(tokens) {
325
318
  if (tokens.length === 0)
326
319
  return null;
@@ -511,7 +504,7 @@ function parseRadialGradientCSSString(gradientContent) {
511
504
  position = parsedPosition;
512
505
  break;
513
506
  }
514
- // No shape/size/position token found in this iteration — the rest of the first part is a
507
+ // No shape/size/position token found in this iteration - the rest of the first part is a
515
508
  // color stop, not gradient config.
516
509
  if (!hasShapeSizeOrPositionString)
517
510
  break;
@@ -545,7 +538,7 @@ function parseBackgroundImageCSSString(cssString) {
545
538
  return gradients;
546
539
  }
547
540
  //#endregion CSS string form
548
- // RN processBackgroundImage.js's default export. Returns [] on any invalid gradient — web
541
+ // RN processBackgroundImage.js's default export. Returns [] on any invalid gradient - web
549
542
  // semantics: an invalid `background-image` paints none of it rather than a partial gradient.
550
543
  export function processBackgroundImage(backgroundImage) {
551
544
  if (backgroundImage == null)
@@ -4,11 +4,7 @@
4
4
  // processed array to native; Fabric's C++ never sees the raw string. symbiote was
5
5
  // forwarding the raw value, which native (CSS parsing off) silently ignores: shadow
6
6
  // rendered nothing. This restores the missing JS parse + per-color processColor.
7
- //
8
- // processColor is referenced from ./commit at RUNTIME only (inside the function bodies),
9
- // never at module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard.
10
- import { processColor } from '../commit.js';
11
- import { isOpaqueColorValue } from '../platform-color.js';
7
+ import { isOpaqueColorValue, processColor } from '../platform-color/index.js';
12
8
  import { dlog } from '../debug.js';
13
9
  // RN processBoxShadow.js:16-19: split args only on the delimiters that are NOT inside
14
10
  // a parenthesized color like rgba(0,0,0,1).
@@ -4,12 +4,9 @@
4
4
  // structured result. symbiote forwarded the raw value; the array form already worked
5
5
  // (Fabric accepts it raw, `filter:[{brightness:0.5}]` was device-verified), but the
6
6
  // CSS-string form and drop-shadow color processing were missing. This restores them.
7
- //
8
- // processColor is referenced from ./commit at RUNTIME only (inside function bodies),
9
- // never at module-init, so the cyclic import (commit -> here -> commit) has no TDZ hazard.
10
- import { processColor } from './commit.js';
11
- import { isOpaqueColorValue } from './platform-color.js';
7
+ import { isOpaqueColorValue, processColor } from './platform-color/index.js';
12
8
  import { dlog } from './debug.js';
9
+ import { isRecord } from './type-guards.js';
13
10
  // RN processFilter.js:19-24: pre-compiled patterns.
14
11
  const NEWLINE_REGEX = /\n/g;
15
12
  const FILTER_FUNCTION_REGEX = /([\w-]+)\(([^()]*|\([^()]*\)|[^()]*\([^()]*\)[^()]*)\)/g;
@@ -17,9 +14,6 @@ const ARGS_WITH_UNITS_REGEX = /([+-]?\d*(\.\d+)?)([a-zA-Z%]+)?/g;
17
14
  const WHITESPACE_SPLIT_REGEX = /\s+(?![^(]*\))/;
18
15
  const LENGTH_PARSE_REGEX = /([+-]?\d*(\.\d+)?)([\w\W]+)?/g;
19
16
  const RADIANS_TO_DEGREES = 180 / Math.PI;
20
- function isRecord(value) {
21
- return typeof value === 'object' && value !== null;
22
- }
23
17
  // A length field may be a CSS string or a number; resolve to a number or null.
24
18
  function resolveLength(value) {
25
19
  if (typeof value === 'string')
package/build/registry.js CHANGED
@@ -11,13 +11,14 @@
11
11
  // shared must stay react-native-free (the headless harness runs in plain Node), so
12
12
  // the ViewConfig lookup is INJECTED, exactly like the color processor: the adapter
13
13
  // wires `setNativeViewConfigSource(ReactNativeViewConfigRegistry.get)` on a real
14
- // host (on a real host that one source covers BOTH RN core and every library).
14
+ // host, where that one source covers BOTH RN core and every library.
15
15
  //
16
16
  // The ONLY explicit list is OUR OWN built-in primitives (BUILTIN_COMPONENTS): a
17
17
  // finite set we own, which keep their hand-tuned tables (view-config events, commit
18
18
  // COLOR_PROPS) and are never read from the source, so they can't drift. Everything
19
19
  // NOT in that set derives. The list never grows with the community; it grows only
20
20
  // when we add a core primitive of our own.
21
+ import { isRecord } from './type-guards.js';
21
22
  const EMPTY = { listeners: new Set(), byRaw: new Map(), processors: new Map() };
22
23
  // OUR own primitives: the finite set shared hand-tunes (view-config events,
23
24
  // commit COLOR_PROPS). The source is never consulted for these, so they can't
@@ -59,9 +60,6 @@ export function registerComponent(name, registration = {}) {
59
60
  list.push(registration);
60
61
  resolvedCache.delete(name);
61
62
  }
62
- function isRecord(value) {
63
- return typeof value === 'object' && value !== null;
64
- }
65
63
  // onChange -> change (mirrors node.ts listenerName; the split of the handler prop).
66
64
  function splitListener(handlerProp) {
67
65
  return handlerProp.charAt(2).toLowerCase() + handlerProp.slice(3);
@@ -92,7 +90,7 @@ function deriveFromConfig(config, into) {
92
90
  const attribute = validAttributes[key];
93
91
  if (isRecord(attribute)) {
94
92
  const process = attribute.process;
95
- // The codegen config already carries the right processor (processColor, …);
93
+ // The codegen config already carries the right processor (processColor, ...);
96
94
  // wrap it so the typed Function becomes a PropProcessor without a cast.
97
95
  if (typeof process === 'function')
98
96
  into.processors.set(key, value => process(value));
@@ -8,6 +8,7 @@
8
8
  import { installDeviceEventHub, NativeEventEmitter, } from '../native-events.js';
9
9
  import { getNativeModule } from '../native-modules.js';
10
10
  import { dlog } from '../debug.js';
11
+ import { isRecord } from '../type-guards.js';
11
12
  // The iOS native module name RN registers this under. NOTE: this is the name the
12
13
  // iOS JS wrapper resolves via `TurboModuleRegistry.getEnforcing('SettingsManager')`.
13
14
  // The spec filename is `INativeSettingsManager`. A module name is only provable on a
@@ -19,9 +20,6 @@ const SETTINGS_UPDATED_EVENT = 'settingsUpdated';
19
20
  // watchIds are handed out as the registry index, so the first watcher is 0. A
20
21
  // cleared slot keeps its index (the array never shrinks) so later ids stay stable.
21
22
  const FIRST_WATCH_ID = 0;
22
- function isRecord(value) {
23
- return typeof value === 'object' && value !== null;
24
- }
25
23
  // Lazily resolved so importing this module has no native side effect. `null` when
26
24
  // the module isn't linked (headless): the snapshot then starts empty and `set`
27
25
  // updates only JS state + fires watchers.
@@ -6,8 +6,8 @@
6
6
  // web/headless.
7
7
  import { dlog } from '../debug.js';
8
8
  import { getNativeModule } from '../native-modules.js';
9
+ import { ACTION_SHEET_MANAGER, } from '../action-sheet-ios/index.js';
9
10
  import { validateContent, shareActions, SHARED_ACTION, DISMISSED_ACTION } from './shared.js';
10
- const ACTION_SHEET_MANAGER = 'ActionSheetManager';
11
11
  export const Share = {
12
12
  ...shareActions,
13
13
  // Open the iOS share sheet for `content`. Resolves with the user's action
@@ -11,8 +11,8 @@
11
11
  // relayout survives and the bar updates without blanking (verified on device: show/hide +
12
12
  // light/dark text). See render.ts's installStopSurfaceGlobal.
13
13
  import { getNativeModule } from '../native-modules.js';
14
- import { processColor } from '../commit.js';
15
14
  import { dlog } from '../debug.js';
15
+ import { processColor } from '../platform-color/index.js';
16
16
  import { STATUS_BAR_MANAGER } from './shared.js';
17
17
  // processColor returns `unknown` (its result is platform-dependent); narrow to the
18
18
  // number Fabric/native expects, like RN's invariant before setColor. A non-number
@@ -7,9 +7,7 @@
7
7
  // POSITION only, never on a property VALUE. `transform: [{translateX: 5}]` is an
8
8
  // array-valued prop and `shadowOffset: {width, height}` an object-valued prop; both
9
9
  // are copied through untouched. Only the top-level style slot is flattened.
10
- function isPlainObject(value) {
11
- return typeof value === 'object' && value !== null && !Array.isArray(value);
12
- }
10
+ import { isRecord } from '../type-guards.js';
13
11
  export function flattenStyle(style) {
14
12
  if (Array.isArray(style)) {
15
13
  const result = {};
@@ -22,7 +20,7 @@ export function flattenStyle(style) {
22
20
  }
23
21
  return result;
24
22
  }
25
- if (isPlainObject(style)) {
23
+ if (isRecord(style)) {
26
24
  // Shallow copy of own enumerable keys: values (arrays, nested objects) pass through.
27
25
  return { ...style };
28
26
  }
@@ -5,7 +5,5 @@ export declare function registerStyles(styles: Record<string, IResolvedStyle>):
5
5
  export declare function clearGlobalStyles(): void;
6
6
  export declare function isClassNameValue(value: unknown): value is IClassNameValue;
7
7
  export declare function resolveClassName(className: IClassNameValue): IResolvedStyle;
8
- export type IClassToggleMap = Record<string, boolean | undefined>;
9
- export type IScopableClassValue = string | IClassToggleMap | Array<string | IClassToggleMap> | undefined | null;
10
- export declare function scopeClassName(value: IScopableClassValue, localNames: ReadonlySet<string>, scopeId: string): IScopableClassValue;
8
+ export declare function kebabToCamel(value: string): string;
11
9
  export {};
@@ -1,17 +1,16 @@
1
1
  // Runtime style registry. Side-effect CSS imports (compiled by the sibling CSS-to-style
2
2
  // build package, not this module) call registerStyles() with camelCase keys; components
3
- // look them up by resolveClassName(). No CSS parsing happens here — this is a
4
- // Map<string, ...> lookup, nothing more.
3
+ // look them up via resolveClassName(). No CSS parsing here - just a Map<string, ...> lookup.
5
4
  //
6
- // This registry has no Tailwind-utility detection layer — this repo's style surface has
7
- // no Tailwind layer, so the compound lookup below always runs for 2-4-part class strings
8
- // instead of being gated behind "no part looks like a utility class".
5
+ // No Tailwind-utility detection layer: this repo's style surface has no Tailwind layer,
6
+ // so the compound lookup below always runs for 2-4-part class strings instead of being
7
+ // gated behind "no part looks like a utility class".
9
8
  //
10
9
  // kebab-case authoring: a CSS selector `.section-label` always registers under the
11
10
  // camelCase key `sectionLabel` (@symbiote-native/css-parser's extractClassName), so a template
12
- // can write EITHER `class="sectionLabel"` OR `class="section-label"` — resolveOne below
13
- // falls back to the kebab->camel form on a miss. The fallback matters because assuming
14
- // authors would always match the camelCase key exactly proved wrong in practice.
11
+ // can write EITHER `class="sectionLabel"` OR `class="section-label"` - resolveOne below
12
+ // falls back to the kebab->camel form on a miss, since authors don't reliably write the
13
+ // exact camelCase key.
15
14
  // Compound lookup tries every ordering of 2-4 space-separated class parts joined by
16
15
  // '.' (e.g. "btn primary" -> "btn.primary" / "primary.btn") before falling back to a
17
16
  // per-class merge, mirroring CSS compound-selector registration (`.btn.primary { }`).
@@ -25,14 +24,14 @@ export function registerStyles(styles) {
25
24
  globalStyles.set(name, style);
26
25
  }
27
26
  }
28
- // Clears every registration; used between tests for isolation.
27
+ // Used between tests to reset registry state.
29
28
  export function clearGlobalStyles() {
30
29
  globalStyles.clear();
31
30
  }
32
31
  // A `class`/`className` prop arrives as `unknown` at the routeProp boundary (any adapter can
33
32
  // hand over anything); this narrows before resolveClassName without an `as` cast. Shared with
34
33
  // adapters/vue/src/components/scroll-view/shared.ts's identical need, rather than each keeping
35
- // its own copy — that file imports this one instead of redeclaring it.
34
+ // its own copy - that file imports this one instead of redeclaring it.
36
35
  export function isClassNameValue(value) {
37
36
  return typeof value === 'string' || (typeof value === 'object' && value !== null);
38
37
  }
@@ -101,10 +100,14 @@ function capitalize(value) {
101
100
  }
102
101
  // Duplicated from @symbiote-native/css-parser's identical helper rather than imported: css-parser
103
102
  // pulls in postcss and is build-time only (never shipped in the app bundle), and this registry
104
- // is the opposite — pure runtime, in every app bundle — so importing it here would leak a
103
+ // is the opposite - pure runtime, in every app bundle - so importing it here would leak a
105
104
  // build-time dependency into the shipped app. The conversion itself is two lines; keeping both
106
105
  // copies in sync is a smaller cost than the alternative.
107
- function kebabToCamel(value) {
106
+ //
107
+ // Exported (not just local to this file) because ./scope.ts's Vue `<style scoped>` name
108
+ // rewriter needs the same kebab->camel normalization and is a different responsibility living
109
+ // in a sibling module - see that file's own doc comment for why it's split out of this one.
110
+ export function kebabToCamel(value) {
108
111
  return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
109
112
  }
110
113
  function tryCompoundLookup(parts) {
@@ -123,39 +126,3 @@ function resolveOne(name) {
123
126
  return {};
124
127
  return globalStyles.get(trimmed) ?? globalStyles.get(kebabToCamel(trimmed)) ?? {};
125
128
  }
126
- // Pure NAME rewriter for Vue `<style scoped>`: suffixes every class token that this
127
- // file's scoped block locally defines with `__${scopeId}`, leaving unrecognized tokens
128
- // (globals, external classes) untouched. Runs at the compiled call site BEFORE Vue's own
129
- // normalizeClass() collapses string/object/array `class` values to a final string, so it
130
- // must pre-process all three shapes normalizeClass understands. No registry lookup here —
131
- // resolveClassName still does the actual style lookup, unchanged, against the rewritten name.
132
- export function scopeClassName(value, localNames, scopeId) {
133
- if (value === undefined || value === null)
134
- return value;
135
- if (Array.isArray(value)) {
136
- return value.map(item => scopeClassEntry(item, localNames, scopeId));
137
- }
138
- return scopeClassEntry(value, localNames, scopeId);
139
- }
140
- // A token arrives as either the camelCase registry key (`sectionLabel`) or its kebab-case
141
- // authoring form (`section-label`) — normalize to camelCase FIRST, then decide scoping, so
142
- // `localNames` (always camelCase, built from the css-parser's registered keys) recognizes a
143
- // kebab-written token. The emitted (possibly suffixed) name is always the camelCase form.
144
- function scopeToken(token, localNames, scopeId) {
145
- const camelToken = kebabToCamel(token);
146
- return localNames.has(camelToken) ? `${camelToken}__${scopeId}` : camelToken;
147
- }
148
- function scopeClassEntry(value, localNames, scopeId) {
149
- if (typeof value === 'object') {
150
- const scoped = {};
151
- for (const [name, enabled] of Object.entries(value)) {
152
- scoped[scopeToken(name, localNames, scopeId)] = enabled;
153
- }
154
- return scoped;
155
- }
156
- return value
157
- .split(/\s+/)
158
- .filter(Boolean)
159
- .map(token => scopeToken(token, localNames, scopeId))
160
- .join(' ');
161
- }
@@ -0,0 +1,3 @@
1
+ export type IClassToggleMap = Record<string, boolean | undefined>;
2
+ export type IScopableClassValue = string | IClassToggleMap | Array<string | IClassToggleMap> | undefined | null;
3
+ export declare function scopeClassName(value: IScopableClassValue, localNames: ReadonlySet<string>, scopeId: string): IScopableClassValue;
@@ -0,0 +1,41 @@
1
+ // Vue `<style scoped>` class-name rewriter. Distinct responsibility from the sibling
2
+ // ./index.ts (the CSS class -> style registry): this module does pure NAME rewriting,
3
+ // no registry lookup, no CSS parsing. It runs at the compiled call site of a Vue SFC's
4
+ // scoped-style template - `adapters/vue/metro-vue-transformer.cjs` emits calls to
5
+ // scopeClassName (imported there as `__scopeClass`) - BEFORE Vue's own normalizeClass()
6
+ // collapses string/object/array `class` values to a final string, so it must pre-process
7
+ // all three shapes normalizeClass understands. resolveClassName in ./index.ts still does
8
+ // the actual style lookup, unchanged, against the rewritten (possibly suffixed) name.
9
+ import { kebabToCamel } from '../index.js';
10
+ // Suffixes every class token that this file's scoped block locally defines with
11
+ // `__${scopeId}`, leaving unrecognized tokens (globals, external classes) untouched.
12
+ export function scopeClassName(value, localNames, scopeId) {
13
+ if (value === undefined || value === null)
14
+ return value;
15
+ if (Array.isArray(value)) {
16
+ return value.map(item => scopeClassEntry(item, localNames, scopeId));
17
+ }
18
+ return scopeClassEntry(value, localNames, scopeId);
19
+ }
20
+ // A token arrives as either the camelCase registry key (`sectionLabel`) or its kebab-case
21
+ // authoring form (`section-label`) - normalize to camelCase FIRST, then decide scoping, so
22
+ // `localNames` (always camelCase, built from the css-parser's registered keys) recognizes a
23
+ // kebab-written token. The emitted (possibly suffixed) name is always the camelCase form.
24
+ function scopeToken(token, localNames, scopeId) {
25
+ const camelToken = kebabToCamel(token);
26
+ return localNames.has(camelToken) ? `${camelToken}__${scopeId}` : camelToken;
27
+ }
28
+ function scopeClassEntry(value, localNames, scopeId) {
29
+ if (typeof value === 'object') {
30
+ const scoped = {};
31
+ for (const [name, enabled] of Object.entries(value)) {
32
+ scoped[scopeToken(name, localNames, scopeId)] = enabled;
33
+ }
34
+ return scoped;
35
+ }
36
+ return value
37
+ .split(/\s+/)
38
+ .filter(Boolean)
39
+ .map(token => scopeToken(token, localNames, scopeId))
40
+ .join(' ');
41
+ }
@@ -0,0 +1,3 @@
1
+ export type IClassToggleMap = Record<string, boolean | undefined>;
2
+ export type IScopableClassValue = string | IClassToggleMap | Array<string | IClassToggleMap> | undefined | null;
3
+ export declare function scopeClassName(value: IScopableClassValue, localNames: ReadonlySet<string>, scopeId: string): IScopableClassValue;
@@ -0,0 +1,41 @@
1
+ // Vue `<style scoped>` class-name rewriter. Distinct responsibility from the sibling
2
+ // ./index.ts (the CSS class -> style registry): this module does pure NAME rewriting,
3
+ // no registry lookup, no CSS parsing. It runs at the compiled call site of a Vue SFC's
4
+ // scoped-style template - `adapters/vue/metro-vue-transformer.cjs` emits calls to
5
+ // scopeClassName (imported there as `__scopeClass`) - BEFORE Vue's own normalizeClass()
6
+ // collapses string/object/array `class` values to a final string, so it must pre-process
7
+ // all three shapes normalizeClass understands. resolveClassName in ./index.ts still does
8
+ // the actual style lookup, unchanged, against the rewritten (possibly suffixed) name.
9
+ import { kebabToCamel } from './index.js';
10
+ // Suffixes every class token that this file's scoped block locally defines with
11
+ // `__${scopeId}`, leaving unrecognized tokens (globals, external classes) untouched.
12
+ export function scopeClassName(value, localNames, scopeId) {
13
+ if (value === undefined || value === null)
14
+ return value;
15
+ if (Array.isArray(value)) {
16
+ return value.map(item => scopeClassEntry(item, localNames, scopeId));
17
+ }
18
+ return scopeClassEntry(value, localNames, scopeId);
19
+ }
20
+ // A token arrives as either the camelCase registry key (`sectionLabel`) or its kebab-case
21
+ // authoring form (`section-label`) - normalize to camelCase FIRST, then decide scoping, so
22
+ // `localNames` (always camelCase, built from the css-parser's registered keys) recognizes a
23
+ // kebab-written token. The emitted (possibly suffixed) name is always the camelCase form.
24
+ function scopeToken(token, localNames, scopeId) {
25
+ const camelToken = kebabToCamel(token);
26
+ return localNames.has(camelToken) ? `${camelToken}__${scopeId}` : camelToken;
27
+ }
28
+ function scopeClassEntry(value, localNames, scopeId) {
29
+ if (typeof value === 'object') {
30
+ const scoped = {};
31
+ for (const [name, enabled] of Object.entries(value)) {
32
+ scoped[scopeToken(name, localNames, scopeId)] = enabled;
33
+ }
34
+ return scoped;
35
+ }
36
+ return value
37
+ .split(/\s+/)
38
+ .filter(Boolean)
39
+ .map(token => scopeToken(token, localNames, scopeId))
40
+ .join(' ');
41
+ }
@@ -0,0 +1,23 @@
1
+ interface ITouchRecord {
2
+ touchActive: boolean;
3
+ startPageX: number;
4
+ startPageY: number;
5
+ startTimeStamp: number;
6
+ currentPageX: number;
7
+ currentPageY: number;
8
+ currentTimeStamp: number;
9
+ previousPageX: number;
10
+ previousPageY: number;
11
+ previousTimeStamp: number;
12
+ }
13
+ interface ITouchHistory {
14
+ touchBank: ITouchRecord[];
15
+ numberActiveTouches: number;
16
+ indexOfSingleActiveTouch: number;
17
+ mostRecentTimeStamp: number;
18
+ }
19
+ export declare const touchHistory: ITouchHistory;
20
+ export declare function recordTouchTrack(kind: 'start' | 'move' | 'end', nativeEvent: Record<string, unknown>): void;
21
+ export declare function resetTouchHistory(): void;
22
+ export declare function attachTouchHistory(nativeEvent: Record<string, unknown>): void;
23
+ export {};