@octane-xplat/lottie 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,7 +38,7 @@ On AppKit, use inline `data`, raw JSON in `src`, an absolute JSON file path,
38
38
  or a `file://` or HTTPS URL. Relative paths, `~/`/`res://` bundle aliases,
39
39
  HTTP URLs, `.lottie`/`.zip` archives, and external image assets are not
40
40
  supported. See [the platform limits](../../docs/app/primitives.md#lottie-animations)
41
- and [the investigation record](../../docs/notes/lottie-appkit-investigation.md)
41
+ and [the investigation record](../../.agents/docs/notes/lottie-appkit-investigation.md)
42
42
  for the complete boundary and runtime evidence.
43
43
 
44
44
  ```tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octane-xplat/lottie",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Lottie animation leaf for Octane xplat — lottie-web on web, NativeScript on mobile, and Airbnb Lottie on AppKit",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,7 +8,6 @@
8
8
  "directory": "packages/lottie"
9
9
  },
10
10
  "files": [
11
- "dist",
12
11
  "types",
13
12
  "platforms",
14
13
  "src/index.ts",
@@ -20,7 +19,6 @@
20
19
  "src/props.ts",
21
20
  "src/native-elements.d.ts",
22
21
  "src/references.d.ts",
23
- "src/cx.mobile.ts",
24
22
  "src/escape-props.ts",
25
23
  "src/escape-props.web.ts",
26
24
  "src/layout-child.ts",
@@ -37,18 +35,19 @@
37
35
  "exports": {
38
36
  ".": {
39
37
  "macos": {
38
+ "types": "./types/generated/index.d.ts",
40
39
  "default": "./src/index.macos.ts"
41
40
  },
42
41
  "web": {
43
42
  "types": "./types/generated/index.web.d.ts",
44
- "default": "./dist/web/index.js"
43
+ "default": "./src/index.web.ts"
45
44
  },
46
45
  "native": {
47
46
  "types": "./types/generated/index.d.ts",
48
- "default": "./dist/native/index.js"
47
+ "default": "./src/index.ts"
49
48
  },
50
49
  "types": "./types/generated/index.d.ts",
51
- "default": "./dist/native/index.js"
50
+ "default": "./src/index.ts"
52
51
  }
53
52
  },
54
53
  "publishConfig": {
@@ -58,13 +57,14 @@
58
57
  "lottie-web": "5.13.0"
59
58
  },
60
59
  "devDependencies": {
61
- "@nativescript-community/octane": "0.2.1",
60
+ "@nativescript-community/octane": "0.2.5",
62
61
  "@nativescript/core": "9.1.3",
63
62
  "@nativescript/types": "9.1.4",
63
+ "@octane-xplat/macos-renderer": "0.15.0",
64
64
  "@octanejs/vite-plugin": "0.1.61",
65
65
  "@tsrx/typescript-plugin": "0.4.11",
66
66
  "octane": "0.11.0",
67
- "tsrx-typegen": "0.2.2",
67
+ "tsrx-typegen": "0.3.0",
68
68
  "typescript": "5.9.3",
69
69
  "vite": "8.3.0"
70
70
  },
@@ -105,7 +105,7 @@
105
105
  }
106
106
  },
107
107
  "scripts": {
108
- "build": "vite build && vite build --mode native && tsrx-typegen --target octane && node ../../scripts/check-native-dist.mjs",
109
- "test:packed": "node tests/packed-consumer.mjs"
108
+ "test:packed": "node tests/packed-consumer.mjs",
109
+ "typegen": "tsrx-typegen --target octane"
110
110
  }
111
111
  }
@@ -48,6 +48,7 @@ export function Lottie(props: LottieProps) @{
48
48
  () => (globalThis as any).XplatLottieAdapter.new() as AppKitLottieAdapter,
49
49
  [],
50
50
  )
51
+
51
52
  const latest = useRef(props)
52
53
  latest.current = props
53
54
 
@@ -61,6 +62,7 @@ export function Lottie(props: LottieProps) @{
61
62
  current.onLoaded?.({ duration })
62
63
  if (current.playing ?? current.autoPlay ?? true) adapter.play()
63
64
  }
65
+
64
66
  adapter.onEnded = () => latest.current.onEnded?.()
65
67
  adapter.onError = (message) => latest.current.onError?.({ message })
66
68
 
@@ -74,6 +76,7 @@ export function Lottie(props: LottieProps) @{
74
76
  } catch (error) {
75
77
  props.onError?.({ message: String(error) })
76
78
  }
79
+
77
80
  return
78
81
  }
79
82
 
package/src/Lottie.tsrx CHANGED
@@ -4,7 +4,6 @@ import { registerElement } from '@nativescript-community/octane';
4
4
  import { isAndroid } from '@nativescript/core';
5
5
  import { LottieView } from './vendor/ui-lottie/src/lottie';
6
6
  import type { LottieHandle, LottieProps } from './props';
7
- import { cx } from './cx.mobile';
8
7
  import { applyEscapeProps, nativeAccessibilityRole, nativeAccessibilityState } from './escape-props';
9
8
  import { layoutChildProps } from './layout-child';
10
9
 
@@ -148,7 +147,7 @@ export function Lottie(props: LottieProps) @{
148
147
  <xplatlottie
149
148
  ref={bindHost}
150
149
  id={props.id}
151
- className={cx(props.className)}
150
+ className={[props.className]}
152
151
  style={props.style}
153
152
  {...layoutChildProps(props)}
154
153
  loop={props.loop ?? false}
@@ -3,14 +3,15 @@
3
3
  "files": [
4
4
  "Lottie.d.ts",
5
5
  "Lottie.web.d.ts",
6
- "cx.mobile.d.ts",
7
6
  "escape-props.d.ts",
8
7
  "escape-props.web.d.ts",
9
8
  "index.d.ts",
10
9
  "index.web.d.ts",
11
10
  "layout-child.d.ts",
12
11
  "layout-child.web.d.ts",
12
+ "native-elements.d.ts",
13
13
  "props.d.ts",
14
+ "references.d.ts",
14
15
  "vendor/ui-lottie/src/lottie/index.common.d.ts",
15
16
  "vendor/ui-lottie/src/lottie/index.ios.d.ts",
16
17
  "vendor/ui-lottie/src/lottie/utils.d.ts"
@@ -0,0 +1,8 @@
1
+ import { LottieView } from './vendor/ui-lottie/src/lottie'
2
+ import type { Attributes } from '@nativescript-community/octane/intrinsics'
3
+
4
+ declare module '@nativescript-community/octane/intrinsics' {
5
+ interface NativeScriptElements {
6
+ xplatlottie: Attributes<typeof LottieView>
7
+ }
8
+ }
@@ -0,0 +1,3 @@
1
+ /// <reference types="@nativescript/types" />
2
+ /// <reference types="vite/client" />
3
+ /// <reference path="../node_modules/@nativescript/core/global-types.d.ts" />
@@ -1,12 +0,0 @@
1
- //#region src/cx.mobile.ts
2
- /** Native renderers now normalize the declarative `className` prop, but
3
- * imperative `view.className` writes still need a pre-joined string — NS
4
- * splits on ' ' only. Native leaves keep normalizing through cx() before
5
- * handing className to an intrinsic so both paths accept the same values.
6
- * (The DOM renderer already flattens arrays, so web leaves don't need
7
- * this.) */
8
- function cx(...parts) {
9
- return parts.flat(Infinity).filter(Boolean).join(" ");
10
- }
11
- //#endregion
12
- export { cx };
@@ -1,39 +0,0 @@
1
- import { isAndroid, isIOS } from "@nativescript/core";
2
- //#region src/escape-props.ts
3
- /** Apply only the active device's escape bag after the primitive's own props. */
4
- function applyEscapeProps(view, props) {
5
- if (!view) return;
6
- const bag = isIOS ? props.ios : isAndroid ? props.android : void 0;
7
- if (bag) Object.assign(view, bag);
8
- }
9
- var NATIVE_ACCESSIBILITY_ROLES = {
10
- button: "button",
11
- link: "link",
12
- search: "search",
13
- image: "image",
14
- heading: "header",
15
- adjustable: "adjustable",
16
- summary: "summary",
17
- text: "text",
18
- none: "none",
19
- progressbar: "progressBar",
20
- checkbox: "checkbox",
21
- switch: "switch",
22
- radio: "radioButton",
23
- spinbutton: "spinButton",
24
- tab: "button"
25
- };
26
- /** Translate the shared/ARIA spelling to NativeScript's narrower role enum. */
27
- function nativeAccessibilityRole(role) {
28
- return role ? NATIVE_ACCESSIBILITY_ROLES[role] : void 0;
29
- }
30
- /** NativeScript's AccessibilityState is a single enum value, unlike the
31
- * web's independent ARIA booleans. Priority follows the strongest state. */
32
- function nativeAccessibilityState(state) {
33
- if (state?.disabled) return "disabled";
34
- if (state?.selected) return "selected";
35
- if (state?.checked === true) return "checked";
36
- if (state?.checked === false) return "unchecked";
37
- }
38
- //#endregion
39
- export { applyEscapeProps, nativeAccessibilityRole, nativeAccessibilityState };
@@ -1,183 +0,0 @@
1
- import { cx } from "./cx.mobile.js";
2
- import { applyEscapeProps, nativeAccessibilityRole, nativeAccessibilityState } from "./escape-props.js";
3
- import { layoutChildProps } from "./layout-child.js";
4
- import { defineUniversalComponent, hookSlots, registerElement, universalPlan, universalProps, universalValue, useCallback, useEffect, useImperativeHandle, useRef } from "@nativescript-community/octane";
5
- import { isAndroid } from "@nativescript/core";
6
- import { LottieView } from "./vendor/ui-lottie";
7
- //#region src/Lottie.tsrx
8
- var _hs$ = /* @__PURE__ */ hookSlots(10);
9
- var _h$0 = /* @__PURE__ */ Symbol(_hs$);
10
- var _h$1 = /* @__PURE__ */ Symbol(_hs$ + 1);
11
- var _h$2 = /* @__PURE__ */ Symbol(_hs$ + 2);
12
- var _h$3 = /* @__PURE__ */ Symbol(_hs$ + 3);
13
- var _h$4 = /* @__PURE__ */ Symbol(_hs$ + 4);
14
- var _h$5 = /* @__PURE__ */ Symbol(_hs$ + 5);
15
- var _h$6 = /* @__PURE__ */ Symbol(_hs$ + 6);
16
- var _h$7 = /* @__PURE__ */ Symbol(_hs$ + 7);
17
- var _h$8 = /* @__PURE__ */ Symbol(_hs$ + 8);
18
- var _h$9 = /* @__PURE__ */ Symbol(_hs$ + 9);
19
- var __octaneUniversalPlan0 = universalPlan("nativescript", {
20
- "kind": "host",
21
- "type": "xplatlottie",
22
- "propsSlot": 0
23
- });
24
- registerElement("xplatlottie", LottieView);
25
- var clamp01 = (n) => Math.min(1, Math.max(0, n));
26
- var STRETCH = {
27
- contain: "aspectFit",
28
- cover: "aspectFill",
29
- fill: "fill"
30
- };
31
- function durationMs(v) {
32
- const d = v.duration ?? 0;
33
- return isAndroid ? d : Math.max(0, d) * 1e3;
34
- }
35
- function currentProgress(v) {
36
- const nv = v.nativeViewProtected;
37
- return isAndroid ? nv?.getProgress?.() ?? 0 : nv?.currentProgress ?? 0;
38
- }
39
- function stopView(v) {
40
- if (isAndroid) v.cancelAnimation();
41
- else v.nativeViewProtected?.stop();
42
- v.progress = 0;
43
- }
44
- var Lottie = defineUniversalComponent("nativescript", function Lottie(props) {
45
- const host = useRef(null, _h$0);
46
- const appliedSrc = useRef(void 0, _h$1);
47
- const loadedRef = useRef(false, _h$2);
48
- useImperativeHandle(props.ref, () => handle(host.current), [], _h$3);
49
- const bindHost = useCallback((v) => {
50
- host.current = v;
51
- }, [], _h$4);
52
- const wantPlay = () => props.playing ?? props.autoPlay ?? true;
53
- const onReady = () => {
54
- const v = host.current;
55
- if (!v || loadedRef.current) return;
56
- loadedRef.current = true;
57
- if (props.speed !== void 0) v.speed = props.speed;
58
- if (props.progress !== void 0) v.progress = clamp01(props.progress);
59
- props.onLoaded?.({ duration: durationMs(v) });
60
- if (wantPlay() && !v.isAnimating()) v.playAnimation();
61
- };
62
- useEffect(() => {
63
- const v = host.current;
64
- if (!v) return;
65
- applyEscapeProps(v, props);
66
- v.stretch = STRETCH[props.fit ?? "contain"];
67
- v.async = true;
68
- v.on(LottieView.compositionLoadedEvent, () => onReady());
69
- v.on(LottieView.loadFailedEvent, (e) => {
70
- loadedRef.current = false;
71
- props.onError?.({ message: e?.error ? String(e.error) : "load failed" });
72
- });
73
- v.completionBlock = (finished) => {
74
- if (finished) props.onEnded?.();
75
- };
76
- }, [], _h$5);
77
- useEffect(() => {
78
- const v = host.current;
79
- if (!v) return;
80
- const src = props.data ? JSON.stringify(props.data) : props.src;
81
- if (src === appliedSrc.current) return;
82
- appliedSrc.current = src;
83
- loadedRef.current = false;
84
- v.src = src ?? "";
85
- }, [props.src, props.data], _h$6);
86
- useEffect(() => {
87
- const v = host.current;
88
- if (!v || props.playing === void 0) return;
89
- if (props.playing) {
90
- if (loadedRef.current) v.resumeAnimation();
91
- } else v.pauseAnimation();
92
- }, [props.playing], _h$7);
93
- useEffect(() => {
94
- const v = host.current;
95
- if (!v || props.progress === void 0 || !loadedRef.current) return;
96
- v.progress = clamp01(props.progress);
97
- }, [props.progress], _h$8);
98
- useEffect(() => {
99
- const v = host.current;
100
- if (!v || props.speed === void 0 || !loadedRef.current) return;
101
- v.speed = props.speed;
102
- }, [props.speed], _h$9);
103
- const handle = (v) => ({
104
- play: () => v.resumeAnimation(),
105
- pause: () => v.pauseAnimation(),
106
- stop: () => stopView(v),
107
- seekTo: (p) => {
108
- v.progress = clamp01(p);
109
- },
110
- setSpeed: (n) => {
111
- v.speed = n;
112
- },
113
- progress: () => currentProgress(v),
114
- duration: () => durationMs(v),
115
- isPlaying: () => v.isAnimating(),
116
- native: v
117
- });
118
- return universalValue(__octaneUniversalPlan0, [universalProps([
119
- [
120
- "set",
121
- "ref",
122
- bindHost
123
- ],
124
- [
125
- "set",
126
- "id",
127
- props.id
128
- ],
129
- [
130
- "set",
131
- "className",
132
- cx(props.className)
133
- ],
134
- [
135
- "set",
136
- "style",
137
- props.style
138
- ],
139
- ["spread", layoutChildProps(props)],
140
- [
141
- "set",
142
- "loop",
143
- props.loop ?? false
144
- ],
145
- [
146
- "set",
147
- "accessible",
148
- props.accessible
149
- ],
150
- [
151
- "set",
152
- "accessibilityLabel",
153
- props.accessibilityLabel
154
- ],
155
- [
156
- "set",
157
- "accessibilityRole",
158
- nativeAccessibilityRole(props.accessibilityRole)
159
- ],
160
- [
161
- "set",
162
- "accessibilityHint",
163
- props.accessibilityHint
164
- ],
165
- [
166
- "set",
167
- "accessibilityValue",
168
- props.accessibilityValue
169
- ],
170
- [
171
- "set",
172
- "accessibilityState",
173
- nativeAccessibilityState(props.accessibilityState)
174
- ],
175
- [
176
- "set",
177
- "accessibilityLiveRegion",
178
- props.accessibilityLiveRegion
179
- ]
180
- ])]);
181
- }, { "module": "@nativescript-community/octane" });
182
- //#endregion
183
- export { Lottie };
@@ -1,38 +0,0 @@
1
- //#region src/layout-child.ts
2
- var FLEX_JUSTIFY = {
3
- start: "flex-start",
4
- end: "flex-end"
5
- };
6
- var FLEX_ALIGN = {
7
- start: "flex-start",
8
- end: "flex-end"
9
- };
10
- /** NativeScript reads these attributes from each child view during parent
11
- * layout. Values are forwarded with NativeScript's own property names.
12
- * The flex-container props (justifyContent/alignItems/flexWrap/gap) land on
13
- * the host flexboxlayout itself — its own attributes, not child metadata. */
14
- function layoutChildProps(props) {
15
- const result = {};
16
- for (const key of [
17
- "row",
18
- "col",
19
- "rowSpan",
20
- "colSpan",
21
- "dock",
22
- "left",
23
- "top",
24
- "flexGrow",
25
- "flexShrink",
26
- "alignSelf",
27
- "order"
28
- ]) if (props[key] !== void 0) result[key] = props[key];
29
- if (props.justifyContent !== void 0) result.justifyContent = FLEX_JUSTIFY[props.justifyContent] ?? props.justifyContent;
30
- if (props.alignItems !== void 0) result.alignItems = FLEX_ALIGN[props.alignItems] ?? props.alignItems;
31
- if (props.flexWrap !== void 0) result.flexWrap = props.flexWrap === true ? "wrap" : props.flexWrap === false ? "nowrap" : props.flexWrap;
32
- if (props.gap !== void 0) result.gap = props.gap;
33
- if (props.rowGap !== void 0) result.rowGap = props.rowGap;
34
- if (props.columnGap !== void 0) result.columnGap = props.columnGap;
35
- return result;
36
- }
37
- //#endregion
38
- export { layoutChildProps };
@@ -1,8 +0,0 @@
1
- {
2
- "version": 1,
3
- "buildId": "3863aee3-5a2b-45b2-a363-8daf287f1e5f",
4
- "mode": "production",
5
- "capabilities": {
6
- "independentHydration": false
7
- }
8
- }